MySQL隐式类型转换导致索引失效的排查与解决
本文分析MySQL中当查询条件类型与索引列类型不一致时引发的隐式类型转换问题。通过复现VARCHAR主键列接收INT参数导致全表扫描的案例,解释MySQL将字符串与整数比较时转换为浮点数的机制,以及字符串转浮点数时的截取规则。指出在索引列上使用函数会导致索引失效,并提供将参数改为字符串以恢复索引使用的解决方案。
问题
在实际开发中遇到一个接口,该接口仅执行一条SQL查询语句。尽管SQL语句中明确使用了主键列进行查询,但执行速度却异常缓慢。通过MySQL的EXPLAIN工具分析执行计划后发现,该查询并未使用主键索引,而是执行了全表扫描。
复现
数据表的DDL定义如下,其中user_id字段被设为主键索引:
CREATE TABLE `user_message` ( `user_id` varchar(50) NOT NULL COMMENT '用户ID', `msg_id` int(11) NOT NULL COMMENT '消息ID', PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
执行下述查询语句,虽然EXPLAIN结果中的key字段显示使用了主键索引(PRIMARY),但rows字段显示扫描了全表数据,表明主键索引实际上并未生效:
EXPLAIN SELECT COUNT(*) FROM user_message WHERE user_id = 1; id|select_type|table |partitions|type |possible_keys|key |key_len|ref|rows |filtered|Extra | --+-----------+------------+----------+-----+-------------+-------+-------+---+-----+--------+------------------------+ 1|SIMPLE |user_message| |index|PRIMARY |PRIMARY|206 | |10000| 10.0|Using where; Using index|
经过详细排查发现,数据表中user_id字段的数据类型为VARCHAR,而SQL语句中传入的user_id参数类型为INT。MySQL在执行语句时会自动进行类型转换,正是这种隐式的类型转换导致了主键索引失效。
隐式转换
MySQL官方文档https://dev.mysql.com/doc/refman/8.0/en/type-conversion.html详细阐述了MySQL类型隐式转换的规则:
当比较操作两边的操作数类型不一致时,MySQL会执行隐式类型转换以使操作数兼容。以下是比较操作中隐式转换的具体规则:
- 若一个或两个参数均为NULL,则比较结果为NULL;但<=>(安全等于)运算符除外,对于NULL <=> NULL,结果为true,无需进行类型转换。
- 若比较操作中的两个参数均为字符串类型,则将它们作为字符串进行比较。
- 若两个参数均为整数类型,则将它们作为整数进行比较。
- 若未将十六进制值与数字进行比较,则将其视为二进制字符串。
- 若参数之一是TIMESTAMP或DATETIME列,而另一个参数是常量,则在执行比较之前,该常量将转换为时间戳格式。注意,对于IN()函数中的参数不执行此转换。为确保安全,进行比较时请始终使用完整的日期时间、日期或时间字符串。例如,在使用BETWEEN与日期或时间值时,建议使用CAST()将这些值显式转换为所需的数据类型以获得最佳结果。
- 单行子查询不视为常量。例如,若子查询返回的整数需与DATETIME值进行比较,则比较将作为两个整数进行,整数不会转换为时间值。参见上一条,这种情况下请使用CAST()将子查询结果的整数值转换为DATETIME类型。
- 若参数之一是十进制值,则比较结果取决于另一个参数。若另一个参数是十进制或整数值,则将参数作为十进制值进行比较;若另一个参数是浮点值,则将参数作为浮点值进行比较。
- 在所有其他情况下,将参数作为浮点数(实数)进行比较。例如,当字符串和数字操作数进行比较时,将其作为浮点数进行比较。
根据上述规则的最后一条,在前面的SQL语句中,字符串与整数的比较会被转换成两个浮点数的比较。具体而言,左边的字符串类型"1"被转换为浮点数1.0,右边的INT类型值1也被转换为浮点数1.0。
按理说,既然两边都转换为了浮点数且值相等,应该能够命中索引,为什么实际执行时却没有使用到索引?
原因在于MySQL中字符串转换为浮点型时的特定转换规则,具体规则如下:
1、不以数字开头的字符串在转换时都将变为0:
SELECT CAST('abc' AS UNSIGNED)
CAST('abc' AS UNSIGNED)|
-----------------------+
0|
2、以数字开头的字符串在转换时会进行截取,从第一个字符开始截取,直到遇到第一个非数字内容为止:
SELECT CAST(' 0123abc' AS UNSIGNED)
CAST(' 0123abc' AS UNSIGNED)|
----------------------------+
123|
因此,在MySQL中,"1"、" 1"(前有空格)、"1a"、"01"这样的字符串在转换为数字后,结果均为1。
MySQL在执行上述SQL语句时,会对每一行主键列的值执行浮点数转换(实际上是在主键列上应用了CAST函数),然后再与条件参数进行比较。由于在索引列上使用了函数,这会导致索引失效,最终导致了全表扫描。
我们只需将前述SQL中传入的参数类型改为字符串,即可正常使用主键索引:
EXPLAIN SELECT COUNT(*) FROM user_message WHERE user_id = '1'; id|select_type|table |partitions|type|possible_keys|key |key_len|ref |rows|filtered|Extra | --+-----------+------------+----------+----+-------------+-------+-------+-----+----+--------+-----------+ 1|SIMPLE |user_message| |ref |PRIMARY |PRIMARY|202 |const| 135| 100.0|Using index|
总结
1、当条件列是字符串类型时,如果传入的条件参数是整数,MySQL会先将其转换为浮点数,进而导致全表扫描和索引失效; 2、条件参数应尽可能与列的类型保持一致,以避免隐式转换;或者在传入的参数上显式执行转换函数,将其转换为与索引列相同的类型。
参考
1、浅析 MySQL 的隐式转换
到此这篇关于MySQL隐式类型转换导致索引失效的解决的文章就介绍到这了,更多相关MySQL隐式类型转换导致索引失效内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









