本文做者:葉金榮,知數堂聯合創始人,MySQL DBA課程講師。Oracle MySQL ACE,MySQL佈道師。有多年MySQL及系統架構設計經驗,擅長MySQL企業級應用、數據庫設計、優化、故障處理等。數據庫
疑似類型隱式轉換一例架構
有羣友提了下面這樣的問題數據庫設計
請教個隱式轉換的問題: SELECT count(*) FROM test WHERE time >= 2019-05-17; time列是datetime類型, 這條SQL的執行結果是至關於 where 1, 這個是什麼緣由呢? SQL執行有個warnings: Warning | 1292 | Incorrect datetime value: '1997' for column 'time' at row 從告警信息來看,是把 2019-05-17 作了數學減法運算,獲得常量 1997, 再把常量1997轉換爲 datetime 類型,再跟time字段作比較。 但用函數 cast(1997 as datetime),也是一樣的告警信息,但結果是 NULL 那麼該SQL是否能夠等價爲: SELECT count(*) FROM test WHERE time >= NULL 這個SQL結果會是0,由於跟NULL值比較的結果是NULL。 雖然WHERE條件錯寫成一個算式,但執行時沒有報錯,只有一個告警信息, 感受仍是由於發生了類型隱式轉換,用不到索引,不然不會是全表掃描。
這是個人第一次回覆內容函數
事實上,條件 WHERE time >= 2019-05-17, 的意思是:time >= 1997,這是表達式 2019-05-17 的結算結果。 這個不是類型隱式轉換,是你SQL沒寫對。
咱們看到SQL的執行計劃是這樣的 測試
對於第二個疑問:爲何會走全表掃描計劃呢?優化
個人見解是這樣的:首先,上面的SQL條件至關於 WHERE time >= 1997。其次,MySQL認爲"1997"不是合法的日期時間類型數據,看到執行計劃中有告警lua
Incorrect datetime value: '1997' for column
所以,time >= 1997 這個條件,就會被當作一個邏輯表達式,由於不是 0(FALSE),也不是 NULL,因此就會被認爲是永遠爲真(TRUE)。也就是說,time列中全部不是FALSE或NULL的值都符合條件。架構設計
咱們能夠測試確認這個說法:設計
# 表中dt列是datetime類型,但容許爲NULL [root@yejr.me]> show create table t1\G *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `c1` int NOT NULL, ... `dt` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`c1`), KEY `k2` (`dt`) ) ENGINE=InnoDB; # 查看全部數據 [root@yejr.me]> select * from t1; +-----+-----+---------------------+ | c1 | ... | dt | +-----+-----+---------------------+ | 2 | ... | NULL | | 3 | ... | 2017-11-01 15:44:27 | | 5 | ... | 2020-02-13 16:02:55 | +-----+-----+---------------------+ 3 rows in set (0.00 sec) # c1=2的記錄中,dt列值爲NULL,不符合條件 [root@yejr.me]> select * from t1 where dt >= 1997; +-----+-----+---------------------+ | c1 | ... | dt | +-----+-----+---------------------+ | 3 | ... | 2017-11-01 15:44:27 | | 5 | ... | 2020-02-13 16:02:55 | +-----+-----+---------------------+ 2 rows in set (0.00 sec)
很明顯,只要表中dt列值不爲NULL、不爲0(符合日期時間格式的數據也確定不會是0)的數據都會被讀取到。code
這種狀況下,即使dt列有索引,也會由於須要掃描的數據太多,從而優化器認爲直接走全表掃描的效率要更好,因此也沒法使用索引。
還有個疑問,WHERE條件寫成 time >= cast(1997 as datetime) 時會怎樣?這種狀況下,由於 cast(1997 as datetime) 的結果是 NULL,因此WHERE條件等同於time>= NULL,對NULL的運算是不能這麼寫的,而應該寫成 dt IS NULL 或 t IS NOT NULL纔對。因此,這麼寫的話,這個查詢是不會有任何結果的,包括列值爲NULL的數據。
最後的小結:
一、寫SQL時,WHERE條件值記得老是帶上引號,避免發生意想不到的狀況。
二、對NULL值的判斷,必須是用 IS NULL 或 IS NOT NULL,不能是大小值的判斷。另外,WHERE條件中的NULL實際上是能夠用到索引的,例如:
[root@yejr.me]> desc select * from t1 where dt is NULL\G ************************ 1. row ************************ id: 1 select_type: SIMPLE table: t1 partitions: NULL type: ref possible_keys: k2 key: k2 key_len: 5 ref: const rows: 1 filtered: 100.00 Extra: Using where
三、除了防範類型隱式轉換,還要注意防範字符集隱式轉換,具體參考MySQL手冊12.2 Type Conversion in Expression Evaluation。