MySQL之隱式轉換,回頭有時間了看下

MySQL之隱式轉換 inexplicit conversion

 

以前也總給業務優化SQL,隱式轉換也很是常見,可是下面這個圖,一會兒還真解釋不清楚。mysql

下面的內容,最終就是爲了解釋下面這個圖。sql

 

問題描述

  • 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 |
+---------------------+


做者:JackpGao
連接:https://www.jianshu.com/p/6f34e9708a80
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

MySQL之隱式轉換 inexplicit conversion

 

以前也總給業務優化SQL,隱式轉換也很是常見,可是下面這個圖,一會兒還真解釋不清楚。函數

下面的內容,最終就是爲了解釋下面這個圖。優化

 
 

問題描述

  • 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 |
+---------------------+
做者:JackpGao 連接:https://www.jianshu.com/p/6f34e9708a80 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索