索引失效指mysql优化器因类型不匹配放弃索引而全表扫描;字段为varchar时传数字、bigint时传字符串,均触发隐式转换,等价于对索引列用函数,破坏b+树有序性,导致type=all。

WHERE条件里字段和值类型不一致,索引直接失效
MySQL优化器要求索引列必须“独立出现在WHERE中”,不能被任何运行时转换包裹。一旦字段是VARCHAR却用数字字面量(如WHERE phone = 13800138000),MySQL就得对每一行执行CAST(phone AS SIGNED);反过来,字段是BIGINT却传字符串(如WHERE id = '123'),也会触发CONVERT(id, CHAR)。这两种情况都等价于在索引列上套了函数,B+树的有序性被破坏,优化器只能放弃索引,走type: ALL。
EXPLAIN UPDATE不可靠,必须用SELECT模拟验证
MySQL 5.6.2之前根本不支持EXPLAIN UPDATE,报错ERROR 1064 (42000)不是SQL写错了,是版本太低;5.6.2+虽支持,但统计信息不准或优化器误判常导致结果失真——明明全表扫,EXPLAIN UPDATE却显示type: range。
- 正确做法:把原
UPDATE的WHERE条件原样搬到EXPLAIN SELECT *里,确保没加函数(比如别写WHERE DATE(create_time) = '2024-01-01') - 关键看
type字段:const/ref表示走了索引,ALL就是全表扫 -
key: NULL≠ 没建索引,可能是联合索引最左前缀没满足(比如索引是(a,b,c),但只写了WHERE b = 1)
ORM和预编译参数最容易悄悄触发隐式转换
MyBatis的#{userId}、Spring Data JPA的findByCode(123)这类写法,表面安全,实则危险:Java传Long给VARCHAR字段,或传String给BIGINT主键,JDBC驱动默认仍按变量原始类型发送——setString(1, "123")发过去,服务端收到的就是带引号的字符串,而字段是数字型,转换就发生了。
- 真正起作用的是驱动层是否按类型发送:
setInt(1, 123)才可靠 - MyBatis中需配合
jdbcType=INTEGER,或确保参数对象字段类型为Integer - Node.js
mysql2里,URL参数req.query.id一定是string,必须手动parseInt()再传
不只是慢,还可能锁住几千行
全表扫描更新时,InnoDB会逐行加X锁、写undo log。哪怕UPDATE ... WHERE id = 123只命中1行,只要因类型不匹配退化为全表扫,就会锁住扫描范围内的所有行——其他事务查这些行就会卡在waiting for table metadata lock,甚至超时。
- 现象:
SHOW PROCESSLIST显示状态是Updating或Locked,持续几秒不动 - 验证:
SELECT * FROM information_schema.INNODB_TRX查当前长事务,看有没有未提交的UPDATE占着资源 - 真实影响:主从延迟飙升、CPU持续90%+、从库
Seconds_Behind_Master拉到几百秒
字符集不一致的隐式转换最难防——比如一张表用utf8mb4,另一张JOIN的表用latin1,或连接字段字符集不同,MySQL会在运行时做字符集转换,同样绕过索引,但EXPLAIN里只显示Using where; Using temporary; Using filesort,不会报错,也看不出类型问题。











