1. 程式人生 > >MySQL 8.0 欄位資料型別不對導致查詢錯誤

MySQL 8.0 欄位資料型別不對導致查詢錯誤

背景:
在生產環境命名匯入了相同條數的記錄,但是開發人員查詢出來的資料行數不一樣。
分析:
後來經過比對和執行計劃分析是因為where條件後的表示時間欄位型別不一致導致的。

模擬分析:
mysql> create table ta(id int not null auto_increment primary key,createtime datetime);

mysql> create table tb(id int not null auto_increment primary key,createtime varchar(30));

向表中插入相同條數的記錄:
insert into ta(createtime) select now() union all select date_add(now(),interval 7 day);
insert into tb(createtime) select now() union all select date_add(now(),interval 7 day);

--兩個表查詢:
mysql> select * from ta;
+----+---------------------+
| id | createtime          |
+----+---------------------+
|  1 | 2018-11-07 16:45:32 |
|  2 | 2018-11-14 16:45:32 |
+----+---------------------+
2 rows in set (0.00 sec)

mysql> select * from tb;
+----+---------------------+
| id | createtime          |
+----+---------------------+
|  1 | 2018-11-07 16:46:28 |
|  2 | 2018-11-14 16:46:28 |
+----+---------------------+
2 rows in set (0.00 sec)

--過濾條件查詢:
mysql> select * from ta where createtime >'2018-11-1';
+----+---------------------+
| id | createtime          |
+----+---------------------+
|  1 | 2018-11-07 16:45:32 |
|  2 | 2018-11-14 16:45:32 |
+----+---------------------+
2 rows in set (0.00 sec)

mysql> select * from tb where createtime >'2018-11-1'; 
+----+---------------------+
| id | createtime          |
+----+---------------------+
|  2 | 2018-11-14 16:46:28 |
+----+---------------------+
1 row in set (0.00 sec)

可以看到因為tb表的createtime欄位定義為varchar只過濾了一條記錄。

--正確的寫法:
mysql> select * from tb where createtime >'2018-11-01';
+----+---------------------+
| id | createtime          |
+----+---------------------+
|  1 | 2018-11-07 16:46:28 |
|  2 | 2018-11-14 16:46:28 |
+----+---------------------+
2 rows in set (0.00 sec)
mysql> select * from ta where createtime >'2018-11-01';  
+----+---------------------+
| id | createtime          |
+----+---------------------+
|  1 | 2018-11-07 16:45:32 |
|  2 | 2018-11-14 16:45:32 |
+----+---------------------+
2 rows in set (0.00 sec)


實際上因為資料型別不一致,ta表的查詢發生了隱式資料型別轉換。
建立索引檢視執行計劃:
mysql> create index ix_createtime on ta(createtime);
Query OK, 0 rows affected (0.64 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> create index ix_createtime on tb(createtime); 
Query OK, 0 rows affected (0.12 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from ta where createtime >'2018-11-1';
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key           | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | ta    | NULL       | index | ix_createtime | ix_createtime | 6       | NULL |    2 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.01 sec)

mysql> explain select * from tb where createtime >'2018-11-1'; 
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key           | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | tb    | NULL       | range | ix_createtime | ix_createtime | 123     | NULL |    1 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

結論:當createtime這類明確看字義應該定義datetime型別的結果定義為varchar型別,此時直接寫 >'2018-11-1' 表示的大於2018-11-10 日零點的資料,準確的寫法應是用函式str_to_date進行轉換。