阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

MySQL之隐式转换

145次阅读
没有评论

共计 7112 个字符,预计需要花费 18 分钟才能阅读完成。

隐式转化规则

官方文档中关于隐式转化的规则是如下描述的:

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.

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

本文永久更新链接地址 :http://www.linuxidc.com/Linux/2017-10/147406.htm

正文完
星哥说事-微信公众号
post-qrcode
 
星锅
版权声明:本站原创文章,由 星锅 2022-01-22发表,共计7112字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中