Go-MySQL-Driver v1.8.0 をリリースしました

github.com/go-sql-driver/mysql (a.k.a. Go-MySQL-Driver)のv1.8.0をリリースしました。

個人的に重要だと思う点を紹介していきます。

charset/collationの扱い

ちょうどGREEさんのBlogで話題になっていた件です。

go-sql-driver/mysqlMySQL 8.0 もデフォルトのままだと、collation_connectionはutf8mb4_general_ci、collation_serverはutf8mb4_0900_ai_ciになります。いずれも character set としてはutf8mb4ですが、utf8mb4_general_ciとutf8mb4_0900_ai_ciは振る舞いが大きく異なるので、Goでutf8mb4を使うなら、意図したcollationが適用されているか注意が必要です。

v1.7まではREADMEに次のように書かれていました。

Version 1.0 of the driver recommended adding &charset=utf8 (alias for SET NAMES utf8) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The collation parameter should be preferred to set another collation / charset than the default.

DSNで charset を指定するのは非推奨で collation を書け、ということですね。ハンドシェイクではそのcollationに対応したcollation idを使います。 charset を指定した場合はそのcharsetに対するデフォルトのcollationを選びますし、両方を指定した場合は collation が優先されました。

これには、 collation を理解して正しく設定しないといけないという問題と、ドライバー側でサーバーと一致したcollationの名前とidのマッピングを持っていないといけないという問題がありました。MySQL 8がどんどん進化する中、MySQLプロトコルを利用する他の製品も多様化していくので、問題は徐々に悪化してきました。

v1.8では charset を指定したときの動作が変わります。ハンドシェイクでデフォルトのcollation_idを送るのはそのままですが、接続後に SET NAMES <charset> を実行するので、そのcharsetに対するサーバー側のデフォルトのcollationが利用されます。 skip-character-set-client-handshake や、指定したcollation_idをサーバーが知らなかったなどの理由で暗黙のうちに完全に無視されることはありません。そのcharsetをサーバーが知らないならちゃんとエラーになります。

ということで、サーバー側のデフォルトのcollationをそのまま使えばいいほとんどのケースで、 charset のみを指定する設定を推奨します。 collation も指定したい場合は、charsetとcollationの両方を指定してやれば、 SET NAMES <charset> COLLATE <collation> というクエリを実行するので、確実にそのcollationを使えます。

より詳しい解説を去年Zennで MySQL接続のcollation不整合の原因と対策 という記事を書いたので読んでみてください。

Scan()にanyを渡した時の挙動を改善しました。

v1.7では次のように、内部でテキストプロトコルが使われているかバイナリプロトコルが使われているかでanyにScan()した時の結果が異なりました。

// db の接続設定で interpolateParams が false の場合
var v any
db.QueryRow("SELECT 123 WHERE ? = 1", 1).Scan(&v)
fmt.Printf("%T %v\n", v, v)  // int64 123

db.QueryRow("SELECT 123 WHERE 1 = 1").Scan(&v)
fmt.Printf("%T %v\n", v, v)  // []uint8 [49 50 51]

整数やfloatなど、変換してもアロケーションが増えない型に限定して、テキストプロトコルでもバイナリプロトコルと同じ結果になるようにしました。

これも詳細は去年Zennに書いたので、Go-MySQL-DriverでScan()にanyを渡した時の挙動を改善しました を読んでみてください。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。