現在管理しているシステムで「たまに覚えのない更新がある」というお話がありました。
調べてみると、ある更新処理のWHERE
句でstring
型カラムを指定していました。
そして、その条件になる値に0
という数値が渡される場合がありました。
string
型のカラムに対して、WHERE
句で0
を指定するとどうなるでしょうか。
実は、MySQLでは式評価で別の型同士の値を比較する際に型変換します。 どういうことかというと、両辺の型が同じ場合はそのまま比較しますが、 両辺の型が違う場合は、都合よく型を変換して比較するということをします。
演算子が別の型のオペランドとともに使用されると、オペランドの互換性を保つために型変換が発生します。 一部の変換は暗黙的に発生します。たとえば、MySQL では必要に応じて数字が文字列 (またはその逆) に自動的に変換されます。
引用 - MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.2 式評価での型変換 (https://dev.mysql.com/doc/refman/5.6/ja/type-conversion.html)
例えば以下のようなテーブルがあったとします。
animal
はvarchar
型です。
zoo
+----+--------+-----+------------+
| id | animal | cnt | date |
+----+--------+-----+------------+
| 1 | 0dog | 1 | 2017-01-07 |
| 2 | 1cat | 2 | 2017-12-08 |
| 3 | 3tiger | 3 | 2017-08-01 |
| 4 | lion | 4 | 2017-05-01 |
| 5 | wolf5 | 5 | 2017-07-01 |
+----+--------+-----+------------+
このテーブルに対して以下のクエリを実行すると1件のデータが返ってきます。
SELECT * FROM zoo WHERE animal = 1;
+----+--------+-----+------------+
| id | animal | cnt | date |
+----+--------+-----+------------+
| 2 | 1cat | 2 | 2017-12-08 |
+----+--------+-----+------------+
1 row in set (0.00 sec)
暗黙の型変換が発生し、1cat
が1
として判定されるため1件の結果が返ってきます。
何故1cat
が1
になるかというと、
文字列の場合、先頭の文字から数値に変換出来る箇所までを数値として扱います。
そのため、1cat
の場合は1
になってしまいます。
以下のクエリの場合、先頭の文字が5
始まるデータが無いので1件も返ってきません。
SELECT * FROM zoo WHERE animal = 5;
Empty set (0.00 sec)
今度は以下のクエリを実行すると3件のデータが返ってきます。
SELECT * FROM zoo WHERE animal = 0;
+----+--------+-----+------------+
| id | animal | cnt | date1 |
+----+--------+-----+------------+
| 1 | 0dog | 1 | 2017-01-07 |
| 4 | lion | 4 | 2017-05-01 |
| 5 | wolf5 | 5 | 2017-07-01 |
+----+--------+-----+------------+
3 rows in set (0.00 sec)
0dog
は先程と同じ理由です。
なぜlion
とwolf5
が返ってくるのでしょうか。
lion
を数値に変換しても数字になりません。すると0
として扱われることになります。
つまりWHERE 0 = 0
になるので、lion
はtrue
扱いになってしまい、結果に出力されます。
wolf5
も同じです。
何年も動いているシステムですが、前任者も気づかなかったようです。 幸いなことにバックアップもありましたし、変更されたデータは重要なものでは無かったので事なきを得ました。
調べてみると古い記事も多いので、基本的で当たり前のことなのかもしれません。 詳しい人からしたら「何だ今更か」と思われるかもしれませんが、知らなかったのでとても勉強になりました。 ただ、とてもとても怖い。