字符串字段不加引号或数字字段传字符串会导致隐式类型转换,破坏b+树索引有序性,使优化器放弃索引而全表扫描,explain显示type=all、key=null;必须确保字段类型与查询值类型严格一致,varchar用单引号、int传数字,并统一join字段字符集与类型。

字符串字段不加引号、数字字段传字符串,基本等于主动关掉索引。
WHERE 条件中 varchar 字段必须用单引号
MySQL 对 varchar 字段执行 WHERE user_id = 12345 时,会把整列转成数字再比对,等价于 CAST(user_id AS SIGNED),B+ 树索引有序性被破坏,优化器直接放弃索引,EXPLAIN 显示 type=ALL、key=NULL。
- 错误写法:
WHERE phone = 13812345678(phone是varchar) - 正确写法:
WHERE phone = '13812345678' - MyBatis 中禁用
${}拼接,一律改用#{}——它会自动加引号并转义 - 上线前用
EXPLAIN验证,重点盯key是否非空、rows是否接近表总行数
INT/BIGINT 字段查询时别传带引号的字符串
反过来也成立:id 是 INT,但写成 WHERE id = '123',MySQL 内部调用 CONVERT(id, CHAR),同样导致索引失效。虽然某些版本下可能“碰巧”走索引,但这不可靠,PostgreSQL 和 Oracle 更严格,直接全表扫描。
- 错误来源常见于:URL 查询参数(
req.query.id总是 string)、JSON body、环境变量 - Node.js 中必须手动
parseInt(req.query.id)再传给查询 - Go 的
pgx驱动里传"123"给INT字段,就是错的;应传int64(123)或显式pgtype.Int4 - Java JDBC 要用
ps.setInt(1, 123),而非ps.setString(1, "123")
JOIN 关联字段类型或字符集不一致
两张表 JOIN 时,哪怕都是 VARCHAR,只要一边是 utf8mb4_general_ci、另一边是 utf8mb4_unicode_ci,或字符集一个是 utf8mb4、另一个是 utf8,MySQL 就无法直接用索引匹配,EXPLAIN 中 possible_keys 有值但 key=NULL,Warning 里会出现 Cannot use range access on index due to type or collation conversion。
- 检查命令:
SHOW FULL COLUMNS FROM table_name,比对Collation列 - 根治方式:
ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci - 临时绕过(仅调试):
ON a.name = b.name COLLATE utf8mb4_unicode_ci,但不能替代修复 - 关联字段类型必须一致:
user.id是INT,order.user_id就不能建为VARCHAR;否则要么改类型,要么在ON里加CAST(但order.user_id索引仍失效)
应用层传参前务必做类型校验和转换
预编译语句(PreparedStatement)只保语法安全,不保类型安全。驱动按变量运行时类型决定发送什么——JS 的 "123" 就是字符串,Go 的 "123" 就是 string,不会自动转成整数。
- 所有外部输入(query、body、header、env)默认视为字符串,必须显式转换后再进 SQL
- Laravel 的
where('id', $x)在$x是字符串时不会自动 cast;Django 的filter(id=x)同理 - 上线前快速检查三件事:
DESCRIBE table看字段真实类型、查参数来源是否全是 string、翻 ORM 调用链确认传的是 int 还是 string - 最隐蔽的坑:PHP 的
PDO::ATTR_EMULATE_PREPARES=true(默认开启)会让参数类型判断失效,建议设为false
类型对齐不是“写对了就行”,而是每次参数流动都要人工干预——数据库不会替你猜意图,它只按规则执行隐式转换,而那个规则,就是索引失效的开关。











