隐式类型转换导致索引失效
比较两个不同类型的值时,数据库会按规则做隐式类型转换。如果转换发生在列这一侧——典型是把 varchar 列与数字字面量比较——列值必须逐行转成数字才能比较,索引顺序失效,优化器只能选择全表扫描。反过来,数字列与字符串字面量比较时字符串被转成数字,索引通常仍然可用。这是生产里「索引建了却没用上」最常见的两类原因之一,另一类是列上套了函数。
基本信息
| 适用平台 | MySQL 5.7 / 8.0;PostgreSQL 更严格,多数情况直接报错而非静默转换 |
|---|---|
| 典型触发 | varchar 列与数字比较、日期列与不合格式的字符串比较、JOIN 两侧列类型不一致 |
| 识别方法 | EXPLAIN 中 key 为 NULL、type 为 ALL,rows 接近全表行数 |
| 修复方式 | 字面量加引号与列同类型;JOIN 两侧列类型对齐;必要时显式 CAST |
| 易发字段 | 手机号、身份证号、订单号等看起来像数字但实际存成字符串的列 |
典型使用场景
- 手机号、身份证等必须以 0 开头的编号,只能存字符串,查询时容易写成数字
- 订单号、流水号在不同系统里类型不一致,JOIN 时触发转换
- 迁移后表结构类型不统一,关联查询一边走索引一边全表扫
- 排查「明明加了索引还是慢」:先看 EXPLAIN 的 key 是否为空
- 代码里用参数绑定查询时,参数类型与列定义不一致
失效与修复
失效与修复
# phone 是 varchar:与数字比较导致列被转成数字,索引失效
EXPLAIN SELECT * FROM users WHERE phone = 13800000000;
# type: ALL,key: NULL,rows 接近全表行数
# 修复:字面量与列类型一致,走索引
EXPLAIN SELECT * FROM users WHERE phone = '13800000000';
# type: ref,key: idx_phone
# JOIN 两侧类型不一致,同样会触发转换
SELECT u.id
FROM users u
JOIN phones p ON p.no = u.phone; # 两列类型应一致,必要时显式 CAST其他容易忽略的转换
其他容易忽略的转换
# 日期列与字符串比较:格式合法时通常可用,但格式不符会引入转换
SELECT * FROM orders WHERE created_at >= '2026-01-01 00:00:00';
# 这条不是类型转换而是列上套函数:DATE() 之后索引失效
SELECT * FROM orders WHERE DATE(created_at) = '2026-01-01';
# 改成范围写法才能用索引
SELECT * FROM orders
WHERE created_at >= '2026-01-01 00:00:00'
AND created_at < '2026-01-02 00:00:00';转换方向与索引结果
| 情形 | 转换发生在哪侧 | 索引是否可用 |
|---|---|---|
| varchar 列 = 数字字面量 | 列这一侧 | 失效,退化为全表扫描 |
| int 列 = '123' 字符串 | 字面量这一侧 | 通常仍然可用 |
| JOIN 两列类型不一致 | 按类型优先级转换其中一列 | 被转换的那一侧失效 |
| DATE(col) = '2026-01-01' | 不是类型转换,是函数包裹列 | 同样失效,需改范围写法 |
排查顺序建议固定下来:先 DESCRIBE 表结构确认列类型,再看 EXPLAIN 的 key,最后才怀疑索引本身;靠反复试错改 SQL 通常更慢。
PostgreSQL 行为不同:字符串与 bigint 列比较通常直接报 operator does not exist,不会静默转换;把 MySQL 的写法搬过去会直接报错,需要显式 ::bigint 或统一参数类型。