直接让查询值类型和字段类型严格一致,90%的隐式转换问题当场消失;字符串字段(如order_no)必须用单引号传参(where order_no = '123456'),否则mysql/postgresql会逐行转数字导致索引失效(explain显示type=all、key=null)。

直接让查询值类型和字段类型严格一致,90%的隐式转换问题当场消失;别指望数据库替你“猜类型”,它只会放弃索引、逐行转换、还可能返回错结果。
WHERE条件里字符串字段不加引号就传数字
这是线上最常踩的坑:字段是VARCHAR(比如order_no、device_id),SQL却写成WHERE order_no = 123456。MySQL/PostgreSQL会把每一行order_no都转成数字再比,B+树索引彻底失效。
- 现象:
EXPLAIN中type为ALL或key为NULL,rows接近全表行数 - 修复:一律写成
WHERE order_no = '123456',单引号不能省 - MyBatis等ORM注意:
${orderNo}会拼掉引号,必须用#{orderNo};如果参数是数字类型,DAO层要先String.valueOf()再传
JOIN关联字段类型或字符集不一致
EXPLAIN里被驱动表(通常是JOIN右边那张)出现type=ALL且key=NULL,基本就是字段类型、字符集或COLLATION不一致触发了隐式转换。
- 验证方法:查
INFORMATION_SCHEMA.COLUMNS比对四要素:DATA_TYPE、CHARACTER_SET_NAME、COLLATION_NAME、IS_NULLABLE - 常见陷阱:
VARCHAR(32)vsBIGINT、utf8mb4_unicode_civsutf8mb4_0900_as_cs、一边NOT NULL一边允许NULL - 临时绕过(不推荐):
ON a.code = b.code COLLATE utf8mb4_unicode_ci,但CPU开销翻倍,且不稳定 - 根治动作:改表结构,例如MySQL用
ALTER TABLE orders MODIFY user_id BIGINT NOT NULL,PostgreSQL用ALTER TABLE orders ALTER COLUMN user_id TYPE BIGINT USING user_id::BIGINT
应用层传参类型错配
ORM和JDBC驱动不会自动按字段类型发送参数。Java用setString(1, "123")往INT字段赋值,生成的SQL仍是= '123';MyBatis里#{userId}若参数是String,照样出问题。
- 真正起作用的是驱动层是否按类型发送:
setInt(1, 123)才可靠 - MyBatis需配合
jdbcType=INTEGER,或确保参数对象字段类型为Integer而非String - JSON字段更隐蔽:
JSON_EXTRACT(extra_info, '$.age') = '25'会让提取出的数字被当字符串比较,函数索引也救不了 - 前端传来的ID类参数(如URL query string),后端必须在绑定前转成对应类型,不能交给SQL引擎处理
日期字段用字符串字面量直接比较
写WHERE date_col > '2024-05-20'看似省事,实际触发隐式转换:数据库把字符串转成日期再比,可能导致索引失效(尤其当列上有函数索引或分区键时)。
- Oracle推荐用
DATE '2024-05-20'(ANSI日期字面量),最简洁可靠 - MySQL/PostgreSQL建议用
STR_TO_DATE('20240520', '%Y%m%d')显式转换,避免依赖sql_mode或NLS设置 - 千万别在索引列上套函数:
WHERE TO_CHAR(date_col, 'YYYY-MM-DD') > '2024-05-20'——执行计划里一旦出现FULL TABLE SCAN或FUNCTION SCAN,八成是这里动了手脚
最容易被忽略的是触发器和视图里的隐式转换——它们藏得深、难复现、执行计划也不一定暴露,上线前务必用真实数据+真实参数组合跑一遍EXPLAIN。











