MySQL之隱式轉換

 

隱式轉化規則
mysql

官方文檔中關於隱式轉化的規則是以下描述的:sql

If one or both arguments are NULL, the result of the comparison is NULL, except for the NULL-safe <=> equality comparison operator. For NULL <=> NULL, the result is true. No conversion is needed.ide

  • If both arguments in a comparison operation are strings, they are compared as strings.
  • If both arguments are integers, they are compared as integers.
  • Hexadecimal values are treated as binary strings if not compared to a number.
  • If one of the arguments is a TIMESTAMP or DATETIME column and the other argument is a constant, the constant is converted to a timestamp before the comparison is performed. This is done to be more ODBC-friendly. Note that this is not done for the arguments to IN()! To be safe, always use complete datetime, date, or time strings when doing comparisons. For example, to achieve best results when using BETWEEN with date or time values, use CAST() to explicitly convert the values to the desired data type.
    A single-row subquery from a table or tables is not considered a constant. For example, if a subquery returns an integer to be compared to a DATETIME value, the comparison is done as two integers. The integer is not converted to a temporal value. To compare the operands as DATETIME values, use CAST() to explicitly convert the subquery value to DATETIME.
  • If one of the arguments is a decimal value, comparison depends on the other argument. The arguments are compared as decimal values if the other argument is a decimal or integer value, or as floating-point values if the other argument is a floating-point value.
  • In all other cases, the arguments are compared as floating-point (real) numbers.

翻譯爲中文就是:函數

    1. 兩個參數至少有一個是 NULL 時,比較的結果也是 NULL,例外是使用 <=> 對兩個 NULL 作比較時會返回 1,這兩種狀況都不須要作類型轉換
    2. 兩個參數都是字符串,會按照字符串來比較,不作類型轉換
    3. 兩個參數都是整數,按照整數來比較,不作類型轉換
    4. 十六進制的值和非數字作比較時,會被當作二進制串
    5. 有一個參數是 TIMESTAMP 或 DATETIME,而且另一個參數是常量,常量會被轉換爲 timestamp
    6. 有一個參數是 decimal 類型,若是另一個參數是 decimal 或者整數,會將整數轉換爲 decimal 後進行比較,若是另一個參數是浮點數,則會把 decimal 轉換爲浮點數進行比較
    7. 全部其餘狀況下,兩個參數都會被轉換爲浮點數再進行比較

 

問題描述

  • where 條件語句裏,字段屬性和賦給的條件,當數據類型不同,這時候是無法直接比較的,須要進行一致轉換
  • 默認轉換規則是:
    • 不一樣類型全都轉換爲浮點型(下文都說成整型了,一個意思)
    • 若是字段是字符,條件是整型,那麼會把表中字段全都轉換爲整型(也就是上面圖中的問題,下面有詳細解釋)

轉換總結

  1. 字符轉整型
    • 字符開頭的一概爲0
    • 數字開頭的,直接截取到第一個不是字符的位置
  2. 時間類型轉換
    • date 轉 datetime 或者 timestamp
      • 追加 00:00:00
    • date 轉 time
      • 無心義,直接爲 00:00:00
    • datetime 或者 timestamp 轉 date
      • 直接截取date字段
    • datetime 或者 timestamp 轉 time
      • 直接截取time字段
    • time 轉 datetime 或者 timestamp
      • 按照字符串進行截取
      • 23:12:13 -> 2023-12-13(這個後文有討論)
        • cast函數只能轉datetime,不能轉timestamp
        • 若是按照timestamp來理解,由於timestamp是有範圍的('1970-01-01 00:00:01.000000' to'2038-01-19 03:14:07.999999'),因此只能是2023年,而不能是1923年
      • 對於不符合的時間值,如10:12:32等,會變爲 0000-00-00 或爲 空
    • time和datetime轉換爲數字時,會變爲雙精度,加上ms(版本不一樣不同)

案例分析

  • 表結構,name字段有索引
-- 注意name字段是有索引的 CREATE TABLE `t3` ( `id` int(11) NOT NULL, `c1` int(11) NOT NULL, `name` varchar(100) NOT NULL DEFAULT 'fajlfjalfka', KEY `name` (`name`), KEY `id` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec)
-- 模擬線上一個隱式轉換帶來的全表掃面慢查詢 -- 發生隱式轉換 xxxx.test> select * from t3 where name = 0; +----+----+-------------+ | id | c1 | name | +----+----+-------------+ | 1 | 2 | fajlfjalfka | | 2 | 0 | fajlfjalfka | | 1 | 2 | fajlfjalfka | | 2 | 0 | fajlfjalfka | +----+----+-------------+ 4 rows in set, 4 warnings (0.00 sec) -- 上述SQL執行計劃是全表掃描,掃描後,字符轉整型,都是0,匹配上了條件,所有返回 xxxx.test> desc select * from t3 where name = 0; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | t3 | ALL | name | NULL | NULL | NULL | 4 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec) -- 加上單引號後,是走name索引的,非全表掃描 xxxx.test> desc select * from t3 where name = '0'; +----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+ | 1 | SIMPLE | t3 | ref | name | name | 102 | const | 1 | Using index condition | +----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------+ 1 row in set (0.00 sec) -- 走索引,沒返回 xxxx.test> select * from t3 where name = '1'; Empty set (0.00 sec)

解釋

  • 若是條件寫0或者1,會進行全表掃面,須要把全部的name字段由字符全都轉換爲整型,再和0或者1去比較。因爲都是字母開頭的字符,會全都轉爲爲0,返回的結果就是全部行。
  • 那有人問了,爲何不把條件裏的 0 自動改爲 '0' ?見下文。

轉換舉例

-- 字符開頭,直接是0 xxxx.test> select cast('a1' as unsigned int) as test ; +------+ | test | +------+ | 0 | +------+ 1 row in set, 1 warning (0.00 sec) xxxx.test> show warnings; +---------+------+-----------------------------------------+ | Level | Code | Message | +---------+------+-----------------------------------------+ | Warning | 1292 | Truncated incorrect INTEGER value: 'a1' | +---------+------+-----------------------------------------+ 1 row in set (0.00 sec) -- 開頭不是字符,一直截取到第一個不是字符的位置 xxxx.test> select cast('1a1' as unsigned int) as test ; +------+ | test | +------+ | 1 | +------+ 1 row in set, 1 warning (0.00 sec) xxxx.test> select cast('123a1' as unsigned int) as test ; +------+ | test | +------+ | 123 | +------+ 1 row in set, 1 warning (0.00 sec) -- 直接按照字符截取,補上了20(不能補19) xxxx.test> select cast('23:12:13' as datetime) as test ; +---------------------+ | test | +---------------------+ | 2023-12-13 00:00:00 | +---------------------+ 1 row in set (0.00 sec) -- 爲何不能轉換爲timestamp,沒搞清楚,官方文檔給的轉換類型裏沒有timestamp。若是是這樣的話,上面的datetime就很差解釋爲什不是1923了。難道是檢測了當前的系統時間? xxxx.test> select cast('23:12:13' as timestamp) as test ; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'timestamp) as test' at line 1 -- 這個時間沒法轉換成datetime xxxx.test> select cast('10:12:32' as datetime) as test ; +------+ | test | +------+ | NULL | +------+ 1 row in set, 1 warning (0.00 sec) xxxx.test> show warnings ; +---------+------+--------------------------------------+ | Level | Code | Message | +---------+------+--------------------------------------+ | Warning | 1292 | Incorrect datetime value: '10:12:32' | +---------+------+--------------------------------------+ 1 row in set (0.00 sec) -- 5.5版本下,時間轉字符,會增長ms xxxx.(none)> select version(); +------------+ | version() | +------------+ | 5.5.31-log | +------------+ 1 row in set (0.00 sec) xxxx.(none)> select CURTIME(), CURTIME()+0, NOW(), NOW()+0 ; +-----------+---------------+---------------------+-----------------------+ | CURTIME() | CURTIME()+0 | NOW() | NOW()+0 | +-----------+---------------+---------------------+-----------------------+ | 15:40:01 | 154001.000000 | 2016-05-06 15:40:01 | 20160506154001.000000 | +-----------+---------------+---------------------+-----------------------+ 1 row in set (0.00 sec) -- 5.6 不會 xxxx.test> select version(); +------------+ | version() | +------------+ | 5.6.24-log | +------------+ 1 row in set (0.00 sec) xxxx.test> select CURTIME(), CURTIME()+0, NOW(), NOW()+0 ; +-----------+-------------+---------------------+----------------+ | CURTIME() | CURTIME()+0 | NOW() | NOW()+0 | +-----------+-------------+---------------------+----------------+ | 15:40:55 | 154055 | 2016-05-06 15:40:55 | 20160506154055 | +-----------+-------------+---------------------+----------------+ 1 row in set (0.00 sec)

爲何不把 where name = 0 中的 0 轉換爲 '0'

  • 若是是數字往字符去轉換,如 0 轉'0',這樣查詢出來的結果只能是字段等於 '0',而實際上,表裏的數據,如'a0','00',這其實都是用戶想要的0,畢竟是用戶指定了數字0,因此MySQL仍是以用戶發出的需求爲準,不然,'00'這些都不會返回給用戶。

總結

  • 有了上面的內容,開頭的問題是能夠解釋了。
  • 上圖的例子,是否是能夠用來繞過身份驗證?

補充

-- 上面遺留的問題,跟系統時間並無關係。懷疑雖然指定的是datetime,可是內部仍是按照timestamp去作的。 mysql> select now(); +---------------------+ | now() | +---------------------+ | 1999-08-03 14:16:50 | +---------------------+ 1 row in set (0.00 sec) mysql> select cast('23:12:13' as datetime) as test ; +---------------------+ | test | +---------------------+ | 2023-12-13 00:00:00 | +---------------------+ 1 row in set (0.00 sec)
做者:JackpGao 連接:http://www.jianshu.com/p/6f34e9708a80 來源:簡書 著做權歸做者全部
相關文章
相關標籤/搜索