類型隱式轉換致使的?No,並非

本文做者:葉金榮,知數堂聯合創始人,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 NULLt 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

相關文章
相關標籤/搜索