MySQL 8.
MySQLのアップグレードでは、
実害そのものは大きなものではありませんでしたが、切り分けの過程で得た気づきは多く、振り返ってみるとバージョンアップ作業における
なお、本件のバグが再現するのはMySQL 8.
始まり
きっかけは、あるアプリケーションをMySQL 8.
ERROR 1366 (HY000): Incorrect integer value: '' for column 'id' at row 1
内容としては
MySQLをある程度使ったことがある方であれば、この種のエラーを見るとまずstrictモード、特にstrict_
ところが今回のケースでは、こうした理解と異なる挙動が実際に発生していました。
strictモードを確認
もし8.
mysql8.0.28> SELECT @@SQL_MODE; +-----------------------------------------------------------------------------------------------------------------------+ | @@SQL_MODE | +-----------------------------------------------------------------------------------------------------------------------+ | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | +-----------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
mysql8.4.5> SELECT @@SQL_MODE; +-----------------------------------------------------------------------------------------------------------------------+ | @@SQL_MODE | +-----------------------------------------------------------------------------------------------------------------------+ | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | +-----------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
結果として、8.
そこで、当時のログを頼りに原因を特定しようと、同じINSERTを手元で再現してみることにしました。問題となっていたSQLを参考にしつつ8.
mysql8.0.28> CREATE TABLE t (
-> id INT
-> );
Query OK, 0 rows affected (0.01 sec)
mysql8.0.28> INSERT INTO t VALUES ('');
ERROR 1366 (HY000): Incorrect integer value: '' for column 'id' at row 1
8.
一方で、アプリケーションログ上は同等のINSERTが成功したように見える記録が残っていました。
再現方法を確認
そこで思い出したのが、実際にエラーが起きた環境では、アプリケーションがSQLを文字列でそのまま投げているわけではない、という点でした。実際には、フレームワークが内部的に PREPARED STATEMENTを利用しています。手元で再現を試みた際には、素のINSERT文をそのまま実行していたため、アプリケーション環境とは実行経路が異なっていたことになります。言い換えると、PREPARED STATEMENTを使った場合にのみ問題が起きている可能性が考えられました。
そこで改めて、PREPARED STATEMENTを使ったINSERTを試してみることにしました。空文字をパラメータとしてバインドして実行すると、8.
mysql8.0.28> CREATE TABLE t (
-> id INT
-> );
Query OK, 0 rows affected (0.01 sec)
mysql8.0.28> INSERT INTO t VALUES ('');
ERROR 1366 (HY000): Incorrect integer value: '' for column 'id' at row 1
mysql8.0.28> PREPARE stmt FROM 'INSERT INTO t VALUES (?)';
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql8.0.28> SET @a = '';
Query OK, 0 rows affected (0.01 sec)
mysql8.0.28> EXECUTE stmt USING @a;
Query OK, 1 row affected (0.00 sec)
mysql8.0.28> SELECT * FROM t;
+------+
| id |
+------+
| 0 |
+------+
1 row in set (0.01 sec)
同様のバグを調査
ここまで来ると、次に確認すべきなのは
内容を読んでみると、
- 対象は MySQL 8.
0.28および8. 0.29の一部バージョンであること - PREPARED STATEMENT経由で空文字を数値型に渡した場合に、数値変換時のバリデーションが正しく行われないこと
- STRICT_
TRANS_ TABLESが有効であってもエラーにならず、値0として挿入されてしまうこと
といった点が挙げられており、今回手元で確認した挙動と一致していました。
また、MySQL 8.
まとめ
今回の事例は影響範囲こそ限定的でしたが、PREPARED STATEMENTと素のSQLの挙動差、そしてstrictモードの動作が特定のバージョンで想定と異なる形になる場合があることを改めて実感する機会になりました。とりわけ、手動での再現では問題が表に出ず、アプリケーションの実行経路でのみ挙動が変わるケースは、切り分けに時間がかかります。
今後もアップグレード時には、機能追加や非推奨化だけでなく、今回のような細かな、そして盲点になりやすい挙動差にも目を向け、PREPARED STATEMENTを含めた実行経路でのテストを習慣化していきたいところです。