EXPLAIN中key为空但possible_keys有值,表明索引因隐式类型转换未被使用;常见于数值字段用字符串查询、JOIN字段类型不一致等场景,导致全表扫描和性能下降。

EXPLAIN里key为空但possible_keys有值,就是隐式转换的典型信号
当你在MySQL 5.7中执行EXPLAIN,发现key列为NULL,而possible_keys却列出了索引名,这几乎可以断定:索引没被用上,但原因不是没建索引,而是MySQL主动跳过了它。常见诱因就是隐式类型转换——比如WHERE order_id = '12345',而order_id是BIGINT类型。字符串字面量触发了类型转换,导致B+树索引无法做等值查找。
这种转换是静默发生的,不会报错,但执行计划里会暴露:type变成ALL或index,rows_examined飙升。注意,5.7对隐式转换更“宽容”,也更隐蔽,不像8.0那样容易在SHOW WARNINGS里提示Implicit type conversion。
- 检查字段类型:
DESCRIBE order_info确认order_id是数值型(如BIGINT),再看SQL里是否用了引号包裹 - 对比字符集/排序规则:如果字段是
VARCHAR但定义为utf8mb4_general_ci,而查询里写了COLLATE utf8mb4_0900_ai_ci,也会触发转换(虽然5.7默认不用后者,但应用层显式指定仍可能) - 用
SELECT @@collation_database, @@collation_connection确认连接层和库级排序规则是否一致
WHERE条件里混用数字和字符串,5.7会强制转成浮点数比较
MySQL 5.7在遇到WHERE numeric_col = '123'时,会把字符串转成DOUBLE再比对,这个过程无法利用索引的有序性。更糟的是,如果字段是DECIMAL或带精度的类型,还可能因浮点精度丢失导致结果不一致。
实操中常见于ORM生成的SQL,比如MyBatis传参时没指定类型,或前端传来的ID参数没做类型校验直接拼进SQL。你可以在慢日志里搜Rows_examined远大于Rows_sent的语句,这类语句大概率正在全表扫描。
- 修复方式很简单:确保WHERE右边的值类型和字段完全一致。数值字段就用
123,别用'123' - 如果必须从字符串输入(比如URL参数),在应用层先
parseInt()或Long.parseLong(),再传给SQL;不要依赖数据库自动转换 - 对已上线SQL,可用
CAST兜底:WHERE order_id = CAST(? AS SIGNED),但这是权宜之计,性能仍不如原生类型匹配
JOIN关联字段类型不一致,会导致驱动表选错+全表扫描
像LEFT JOIN coupon_usage c ON o.order_id = c.order_id这种语句,如果o.order_id是BIGINT,而c.order_id是VARCHAR(32),MySQL 5.7会把两个字段都转成DOUBLE再比较。结果不只是索引失效,还会让优化器误判数据分布,可能把小表当驱动表,大表当被驱动表,最终对千万级coupon_usage表做嵌套循环扫描。
这个问题在EXPLAIN里表现为:被驱动表的type是ALL,rows列显示扫描行数等于该表总行数,且Extra里出现Using join buffer (Block Nested Loop)。
- 优先统一字段类型:ALTER TABLE coupon_usage MODIFY order_id BIGINT NOT NULL;
- 如果暂时不能改表结构,至少在JOIN条件里显式转换:
ON o.order_id = CAST(c.order_id AS SIGNED),并确保c.order_id上有函数索引(5.7不支持函数索引,所以这招只治标) - 检查
information_schema.COLUMNS确认两表同名字段的DATA_TYPE和COLLATION_NAME是否一致
log_queries_not_using_indexes打开后日志爆炸,要配合min_examined_row_limit用
想抓出所有没走索引的查询?别直接开log_queries_not_using_indexes=ON。在5.7里,它会把SELECT * FROM t LIMIT 1这种本就不该走索引的语句也记下来,日志量瞬间暴涨,磁盘可能一小时就写满。
真正有用的组合是:log_queries_not_using_indexes=ON + min_examined_row_limit=1000。这样只记录那些“本应走索引却没走、且扫描行数≥1000”的语句,精准命中隐式转换类问题。
- 配置写法(my.cnf):
log_queries_not_using_indexes=1和min_examined_row_limit=1000必须在同一节[mysqld]下 - 注意:
min_examined_row_limit启用后,long_query_time会失效,所以得靠扫描行数而非耗时来过滤 - 重启生效后,用
mysqldumpslow -s r -t 5 /path/to/slow.log按扫描行数倒序看前5条,重点盯Rows_examined异常高的那几条
隐式类型转换在5.7里最难缠的地方,是它不报错、不告警,只悄悄拖慢查询。最有效的排查路径就是:先开慢日志+min_examined_row_limit筛出高扫描行数语句,再逐条EXPLAIN看key是否为空,最后回溯SQL里每个WHERE和JOIN条件的左右操作数类型。只要有一个地方类型不匹配,整个索引链就断了。


















