2015年02月16日

go-sql-driver/mysql でプレースホルダ置換をサポートしました

はてなブックマークに登録

前回の記事で少し触れましたが、 go-sql-driver/mysql にドライバ側でのプレースホルダ置換を実装するプルリクエストを出していました。

それがマージされたので、背景のおさらいと利用方法を紹介しておきます。

背景

Go の database/sql の概要については前回の記事で解説しました。

そこで説明したとおり、 DB.Prepare() を使わずに直接 DB.Exec()DB.Query() を使った場合、 ドライバ側でのプレースホルダ置換に対応していないドライバでは prepare, exec, close で3回のラウンドトリップが発生することになり、パフォーマンスが悪くなります。

基本的には DB.Prepare() を使えばいいのですが、前回の記事で修正したスケーラビリティの問題は Go 1.5 になるまで直りませんし、 IN 句があるSQL文などで事前に Prepare するのが難しいケースも有ります。

また、O/Rマッパーやクエリビルダーを使う場合、そのライブラリが Prepared Statement のキャッシュに対応していないと、 アプリケーションプログラマが制御できない部分で DB.Prepare() を使わないで DB.Query() が使われるケースが出てきます。

ちょうど prepared statement をクエリの都度発行するデメリットが 幾つかの 場所で 話題になったこともあり、 go-sql-driver/mysql にオプションを付与する RFC (Request For Comment) だったプルリクエストがマージに向けて動き始めました。(マージされるまでに行ったチューニングについてはまた次回紹介しようと思います。)

go-sql-driver/mysql のプレースホルダ置換について

go-sql-drvier/mysqlgo get -u github.com/go-sql-driver/mysql で取得します。 まだこの機能はマージされたばかりなので、すでに利用されている方は再度このコマンドを実行して更新してください。

sql.Open() の第二引数に渡す dsl で interpolateParams=true というオプションをURLのクエリパラメタの形で渡すことにより、プレースホルダ置換が有効になります。 ただし、文字列をエスケープする際にバイト単位で処理しているので、 collation オプションで cp932big5 など危険なエンコーディングを指定した場合はエラーにしています。

(ちなみに、 go-sql-driver/mysql でコネクションのエンコーディングを指定する charset オプションは非推奨なので、 collation オプションを利用してください) collationは指定しない場合デフォルトで utf8_general_ci ですが、次の例では utf8mb4_bin に指定しています。

package main

import (
	"database/sql"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	// localhost の 3306 番ポートで動いている MySQL に、ユーザー名=root, パスワード=password, データベース=test で接続する。
	// interpolateParams=true オプションを利用する。
	db, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/test?interpolateParams=true&collation=utf8mb4_bin")
        // プレースホルダ置換を行わない場合はこちら
	//db, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/test?collation=utf8mb4_bin")
	if err != nil {
		log.Fatal(err)
	}
	if _, err := db.Exec("SELECT SLEEP(?)", 42); err != nil {
		log.Fatal(err)
	}
}

このプログラムを実行してる時、別のセッションから SHOW FULL PROCESSLIST を実行すると、オプションの効果が確認できます。 interpolateParams=true を付けない場合は、次のように CommandExecute になり、 Info ではプレースホルダがそのまま表示されます。(とてもタイミングが良ければ、 Execute コマンドじゃなくて Prepare コマンドが見えるかもしれません)

mysql> show full processlist;
+------+------+-----------------+------+---------+------+------------+-----------------------+
| Id   | User | Host            | db   | Command | Time | State      | Info                  |
+------+------+-----------------+------+---------+------+------------+-----------------------+
| 7508 | root | localhost       | NULL | Query   |    0 | init       | show full processlist |
| 7511 | root | localhost:55283 | test | Execute |    2 | User sleep | SELECT SLEEP(?)       |
+------+------+-----------------+------+---------+------+------------+-----------------------+
2 rows in set (0.00 sec)

interpolateParams=true を付けた場合は、次のように CommandQuery になり、 Info でプレースホルダの部分に値が挿入されていることを確認できます。

mysql> show full processlist;
+----+------+-----------------+------+---------+------+------------+-----------------------+
| Id | User | Host            | db   | Command | Time | State      | Info                  |
+----+------+-----------------+------+---------+------+------------+-----------------------+
|  2 | root | localhost       | NULL | Query   |    0 | init       | show full processlist |
|  3 | root | localhost:49283 | test | Query   |    1 | User sleep | SELECT SLEEP(42)      |
+----+------+-----------------+------+---------+------+------------+-----------------------+
2 rows in set (0.00 sec)

文字列のエスケープでは、 sql_modeNO_BACKSLASH_ESCAPES を設定した場合にも対応しています。 次の結果は db.Exec("SELECT ?, SLEEP(?)", "Hel'lo\nWorld", 42) した時のものです。

mysql> SELECT @@GLOBAL.sql_mode;
+-----------------------------------------------------------------+
| @@GLOBAL.sql_mode                                               |
+-----------------------------------------------------------------+
| NO_BACKSLASH_ESCAPES,STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> show full processlist;
+------+------+-----------------+------+---------+------+------------+-----------------------------------+
| Id   | User | Host            | db   | Command | Time | State      | Info                              |
+------+------+-----------------+------+---------+------+------------+-----------------------------------+
| 7515 | root | localhost       | NULL | Query   |    0 | init       | show full processlist             |
| 7517 | root | localhost:55400 | test | Query   |    2 | User sleep | SELECT 'Hel''lo
World', SLEEP(42) |
+------+------+-----------------+------+---------+------+------------+-----------------------------------+
2 rows in set (0.00 sec)

@methane

songofacandy at 12:58│Comments(0)TrackBack(0)golang 

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント