서버에서 db에 쿼리를 할 때 일반 statement와 prepared statement의 차이점에 대해 설명한다.
기본적으로 db에 쿼리가 도착하면 db에서는 분석, 컴파일, 실행 3가지 과정이 일어난다고 한다.
그리고 이 실행을 하고 난 뒤 db cache에 해당 정보를 저장해두고 사용하는데 cache hit 기준은 쿼리 문자열이 100% 일치하였는가이다.
예를 들어, select name from test where no=30; 이 구문과 select name from test where no=31; 이 구문은 다르게 인식 되기 때문에 같은 유형의 쿼리임에도 불구하고 cache hit이 되지 않는다.
이 cache hit을 시키기 위해 사용하는 것이 바로 prepared statement 이다.
따라서, prepared statement는 for문과 같이 쿼리가 반복적으로 수행되면서 변수만 바뀔 때 성능이 향상된다.
다만, 쿼리가 한 번만 발생하는 경우에는 prepared statement의 수행시간이 더 길 것이다. (최근에 단일 쿼리의 경우에도 prepared statement가 더 성능이 좋다는 말을 들었는데, 기본적으로 prepared statement의 경우 db 접근이 두 번 일어나기 때문에 성능이 더 좋을수는 없는 것 같다.)
package main
import (
"bufio"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"fmt"
"os"
"time"
)
func main() {
fmt.Println("test")
db, err := sql.Open("mysql", "root:devOps!23@tcp(127.0.0.1:3306)/test")
if err != nil {
panic(err.Error())
}
defer db.Close()
fmt.Println("db.Begin")
tx, err := db.Begin()
if err != nil {
panic(err.Error())
}
defer tx.Rollback()
reader := bufio.NewReader(os.Stdin)
start := time.Now()
fmt.Print("tx.Prepare: ")
text, _ := reader.ReadString('\n')
fmt.Println(text)
stmt, err := tx.Prepare("SELECT no FROM test WHERE no=?")
if err != nil {
panic(err.Error())
}
defer stmt.Close()
//
fmt.Print("stmt.Exec: ")
text, _ = reader.ReadString('\n')
fmt.Println(text)
_, err = stmt.Exec(2)
if err != nil {
panic(err.Error())
}
fmt.Print("tx.Commit: ")
text, _ = reader.ReadString('\n')
fmt.Println(text)
err = tx.Commit()
if err != nil {
panic(err.Error())
}
end := time.Now()
fmt.Println(end.Sub(start))
}
show global status like '%stmt%';
위 golang 코드와 mariadb 쿼리를 활용하면 위 정보에 대한 검증이 가능하다.
반복문을 활용하면 일반 쿼리보다 prepraed statement를 사용할 경우 성능이 더 좋아진다는 것을 확인 가능하다.
구글링을 해보면 prepared statement는 성능 뿐만 아니라 sql injection 방지에 대한 장점도 있다는 것을 알 수 있는데, golang에서는 일반 query도 단순히 string을 합치는게 아니라 parameter를 사용하여 쿼리할 수 있도록 지원하기 때문에 sql injection에 대한 장점은 있는 것 같지 않다.
'Server' 카테고리의 다른 글
Unicode vs UTF-8 차이점 및 golang rune이란? 쉽게 이해하기 (0) | 2020.11.22 |
---|