Menu

메모용 개발 블로그

전체보기 > 데이터베이스 >

개발 시 데이터베이스 바인드 변수

2022-05-17 23:46:50

데이터베이스 쿼리와 실행 계획

대부분의 관계형 데이터베이스(RDBMS)에서는 SQL이라고 하는 데이터 질의를 위한 언어를 별도로 사용한다.

그렇기 때문에 데이터를 질의할 때 SQL을 파싱하는 작업을 거치고 해석한 문장으로 어떻게 데이터를 조회해낼 것인지에 대한 실행계획을 세우고 계획대로 실행하여 조회한 뒤 값을 넘겨준다.

하지만, 동일한 SQL문에 대해서 또 파싱하고 실행계획을 세울 필요가 있을까? 아니다.

동일한 쿼리문의 경우에는 기존에 세워둔 실행계획을 그대로 사용하여 데이터를 조회하게 된다.

그렇기 때문에 동일한 SQL문의 경우 다음과 같이 동작한다.

  1. 동일한 SQL문이 있는지 찾는다.
  2. 찾은 실행계획을 토대로 조회를 실행한다.
  3. 반환

만약 다를 경우

  1. 동일한 SQL문이 있는지 찾는다.
  2. 없으므로 실행계획을 세운다.
  3. 세운 실행계획을 토대로 조회를 실행한다.
  4. 반환

다음과 같이 동작하게 된다.

동일한 SQL이 없어 계획 부터 다시 세우고 실행 프로그램을 다시 만들어야 할 경우에는 "하드파싱"이라고 한고, 반대로 기존에 실행계획이 있어서 바로 실행할 경우 "소프트파싱"이라고 한다.

하드파싱은 일련의 과정들로 인하여 당연히 DB가 위치한 서버에 CPU에 부담을 주기 때문에 이를 줄일 수 있다면 줄이는 것이 좋다.

바인드 변수 활용

위의 서론들을 통해서 데이터베이스 활용 프로그램 개발에 있어서 빈번한 하드파싱보다 소프트파싱이 적절하게 발생하도록 하기 위해서 바인드 변수를 활용에 대해서 남기고자 한다.

사실 보통의 데이터베이스 라이브러리를 사용하면 대부분 아래와 같은 방식의 사용방식을 알려주고 있고 자연스럽게 사용하고 있었을 것이다.

conn.query("SELECT id, name FROM user WHERE id=?", 1)

위 코드에서 ? 부분이 바인드 변수로 되고 1은 매개변수로 되어 데이터베이스에 전달되게 된다.

# 쿼리
SELECT id, name FROM user WHERE id=:var1

# 매개변수
var1 -> 1

위는 예시이다.

이렇게되면 매개변수가 달라지더라도 쿼리는 변하지 않으므로 실행계획을 그대로 가져와서 인수만 바꿔 넘겨주고 그대로 사용할 수 있다.

이에 대해서 가상의 코드를 통해 시나리오를 예상해보면

# 1번
conn.query("SELECT id, name FROM user WHERE id=?", 1)
conn.query("SELECT id, name FROM user WHERE id=?", 2)
conn.query("SELECT id, name FROM user WHERE id=?", 3)
conn.query("SELECT id, name FROM user WHERE id=?", 4)
conn.query("SELECT id, name FROM user WHERE id=?", 5)

# 2번
conn.query("SELECT id, name FROM user WHERE id=" + "1")
conn.query("SELECT id, name FROM user WHERE id=" + "2")
conn.query("SELECT id, name FROM user WHERE id=" + "3")
conn.query("SELECT id, name FROM user WHERE id=" + "4")
conn.query("SELECT id, name FROM user WHERE id=" + "5")

위의 1번과 2번을 각각 실행한다고 가정하고 각 쿼리는 단 한번도 실행해본적이 없는 것으로 가정한다.

최초 1행에서는 한번도 실행된 적 없기에 둘 다 하드파싱이 발생하게 된다.

그러나 두번째 줄 부터 차이가 발생하게 되는데.

1번 코드에서 2행부터는 동일한 쿼리가 존재하므로 기존의 실행계획을 활용한 소프트파싱이 발생하여 동작하게 된다.

그러나, 2번코드에서는 전혀 다른 쿼리문이므로 2행 역시 하드파싱이 발생하며 5행까지 모두 하드파싱이 발생하게 된다.

결론은 동적인 쿼리가 필요하면 라이브러리에서 제공하는 방식대로 바인드변수를 활용한 쿼리를 하도록하고 프로그램에서의 문자열을 조작하는 것으로 동적인 쿼리를 만드는 것을 지양하는 것이다.

코드 컨벤션 준수

다른 DBMS는 모르겠으나, 오라클에서는 동일 쿼리를 빠르게 찾기 위해서 쿼리를 해시하여 동일 쿼리를 찾는다.

그렇기 때문에 대소문자 구분이나 공백 등에 따라서도 전혀 다른 쿼리로 인식하기 때문에 코드 컨벤션을 정하고 이를 최대한 준수하는 것도 나름의 도움이라고 볼 수 있다. 어쩌다 같은 쿼리가 여러 곳에 존재한다면 코드 컨밴션을 준수한다면 하나의 실행계획만으로 될테니까..?

근데 큰 의미는 없어보인다.

MyBatis, IBatis

자바에서 주로 사용하는 라이브러리인데. 여기서는 #{var} 방식이 바인드 변수이다. 반대로 ${var} 방식은 리터럴 변수이므로 바인드변수 활용 단락에서 문자열을 합쳐서 쿼리하던 것과 다름없게 된다.

여담

바인드 변수를 활용하면 SQL 인젝션 공격 방어에도 도움이 되니 쓰자.