user_id = '123' 走不了索引是因为MySQL对INT类型列隐式转为字符串比较,导致全表扫描;PostgreSQL则直接报错。EXPLAIN中type为ALL/index、Extra仅含Using where即为典型表现,SHOW WARNINGS可见CAST痕迹。

WHERE 条件里 user_id = '123' 为什么走不了索引
字符串和数字混用时,MySQL 会把列隐式转成字符串再比对,user_id 是 INT 类型,但传了字符串 '123',引擎不得不对整列做类型转换,索引失效是必然结果。PostgreSQL 更严格,直接报错;而 MySQL 默默执行却慢得离谱。
- 查
EXPLAIN的type字段:如果是ALL或index(非ref/range),大概率有隐式转换 - 看
Extra列:出现Using where; Using index是健康的;若只有Using where,说明索引没被用于定位,只是最后过滤 - 用
SHOW WARNINGS查优化器重写后的语句,常能看到类似CAST(user_id AS CHAR)这样的痕迹
PreparedStatement 参数绑定不等于类型安全
Java 用 setString(1, "123") 给 INT 字段赋值,JDBC 驱动默认仍会发字符串过去——MySQL 收到后照旧隐式转换。真正起作用的是驱动层的类型提示,不是应用层的 setXxx 方法名。
- MySQL Connector/J 8.0+ 可加连接参数:
useServerPrepStmts=true&cachePrepStmts=true&rewriteBatchedStatements=true,并确保serverPreparedStatementDiscardThreshold合理 - 更可靠的做法是显式调用
setInt(1, 123),哪怕变量来源是字符串,也先Integer.parseInt()(注意空值和异常) - MyBatis 中
不够,得配合AND user_id = #{userId} jdbcType=INTEGER或让参数对象字段类型为Integer
JSON 字段里的数字查询最容易翻车
JSON 类型字段(如 extra_info)存了 {"age": 25},写 JSON_EXTRACT(extra_info, '$.age') = '25',MySQL 会把提取出的数字转成字符串比较,索引(哪怕建了函数索引)也白搭。
- 函数索引必须和查询表达式完全一致:建的是
INDEX idx_age ON t ((CAST(JSON_EXTRACT(extra_info, '$.age') AS UNSIGNED))),那查询就得写成CAST(JSON_EXTRACT(extra_info, '$.age') AS UNSIGNED) = 25 - 避免在 JSON 路径里混用单双引号:
'$.age'和"$.age"在某些版本解析行为不同 - PostgreSQL 的
->>返回 text,->返回 jsonb,想数值比较得接::int,漏掉就全表扫描
ORM 自动生成 SQL 时的类型“失联”
Django 的 filter(user_id="123")、SQLAlchemy 的 .filter(User.user_id == "123") 看似无害,实际生成的 SQL 很可能带引号。ORM 不会主动推断字段类型,它只信模型定义——如果 user_id 字段声明为 Integer,但传入字符串,多数 ORM 仍原样拼进 SQL。
- Django:开启
DEBUG=True看django.db.connection.queries,确认生成的 SQL 是WHERE user_id = 123还是= '123' - SQLAlchemy:用
cast(User.user_id, Integer) == "123"强制类型对齐,或提前int()转换输入 - Go 的
sqlx或gorm默认按 Go 类型推导,但如果结构体字段是string,哪怕数据库是INT,也会传字符串——别依赖“自动适配”
最麻烦的不是发现不了,是线上跑了半年才发现某条慢查询始终没走索引,因为所有测试数据都是数字字符串混着来,开发环境根本压不出问题。










