注意!字段数据类型不匹配,这个sql会很慢
在数据库优化中,我们经常会关注索引的创建、SQL的写法,但有一个隐蔽的“性能杀手”却容易被忽略——字段数据类型与查询值类型不匹配。
一、问题现象:一个“简单”查询的异常性能
我们有一张交易表 plat_order,其中 order_id 字段定义为 VARCHAR(20),并且在该字段上建立了索引 index_order_id。
当执行以下查询时:
-- 查询A:使用数字字面量
SELECT * FROM plat_order WHERE order_id = 1669233832303042562;
这条看似简单的查询却不会走索引,而是进行了全表扫描,执行时间可能长达数秒甚至更久。
而当我们稍作修改:
-- 查询B:使用字符串字面量
SELECT * FROM plat_order WHERE order_id = '1669233832303042562';
同样的查询条件,只是将数字改为字符串,执行时间立即降至毫秒级。
二、执行计划对比:触目惊心的差异
通过 EXPLAIN 分析两条SQL的执行计划,差异一目了然:
| 查询语句 | type | key | rows | Extra | 性能评估 |
|---|---|---|---|---|---|
WHERE order_id = 16692... |
ALL | (null) | 8,533,253 | Using where | 全表扫描,极慢 |
WHERE order_id = '16692...' |
ref | index_order_id | 1 | (null) | 索引扫描,极快 |
从执行计划可以清晰看到:
- 查询A:
type=ALL表示全表扫描,key=(null)表示未使用任何索引,需要处理853万行数据 - 查询B:
type=ref表示使用索引等值查找,key=index_order_id表示使用了该索引,仅需处理1行数据
三、技术原理:MySQL的隐式类型转换机制
3.1 什么是隐式类型转换?
当SQL语句中参与比较运算的两个数据类型不一致时,MySQL会自动将它们转换为相同类型,这个过程称为隐式类型转换。
3.2 数字与字符串比较的转换规则
MySQL遵循一个核心原则:总是尝试将值转换为表达式中另一操作数的类型。具体到数字与字符串的比较:
-
当字符串列与数字比较时(
VARCHAR = 数字):- MySQL会将数字转换为字符串
- 但这种转换是在运行时对每一行数据动态计算的
- 结果:索引失效
-
当数字列与字符串比较时(
INT = '字符串'):- MySQL会尝试将字符串转换为数字
- 如果转换成功(如'123'→123),可能使用索引
- 如果转换失败(如'abc'→0),按转换结果查询
3.3 为什么索引会失效?
这是问题的关键。当执行 WHERE order_id = 1669233832303042562 时:
- MySQL需要将数字
1669233832303042562转换为字符串 - 这个转换不是对索引列的简单函数包裹,而是对比较值的处理
- 但由于转换发生在比较操作上,优化器无法确定转换后的值是否仍满足索引的有序性
- 出于保守策略,MySQL选择放弃使用索引,改为全表扫描
-- MySQL实际执行的近似逻辑(概念上):
SELECT * FROM plat_order
WHERE CONVERT(order_id USING utf8mb4) = CONVERT(1669233832303042562 USING utf8mb4);
-- 注意:order_id上的函数操作导致索引失效
3.4 反向问题解答:数字列与字符串比较
如果 order_id 是数字类型(如 BIGINT),执行 WHERE order_id = '1669233832303042562':
- MySQL会尝试将字符串
'1669233832303042562'转换为数字 - 如果转换成功,转换仅作用于常量值,不涉及索引列
- 因此,可以使用索引
- 但是,如果字符串无法转换为数字(如
'ABC123'),会转换为0,可能导致错误的结果
-- 示例:数字列与字符串比较
CREATE TABLE order_num (
id BIGINT PRIMARY KEY,
order_id BIGINT,
INDEX idx_order_id(order_id)
);
-- 可以走索引(字符串转为数字)
EXPLAIN SELECT * FROM order_num WHERE order_id = '1669233832303042562';
-- type: ref, key: idx_order_id
-- 也可以走索引(直接使用数字)
EXPLAIN SELECT * FROM order_num WHERE order_id = 1669233832303042562;
-- type: ref, key: idx_order_id
四、核心规律总结
| 场景 | 转换方向 | 索引是否可用 | 性能影响 |
|---|---|---|---|
| 字符串列 = 数字值 | 数字→字符串 | 否 | 全表扫描,性能极差 |
| 数字列 = 字符串值 | 字符串→数字 | 是 | 正常使用索引 |
| 字符串列 = 字符串值 | 无转换 | 是 | 最佳性能 |
| 数字列 = 数字值 | 无转换 | 是 | 最佳性能 |
黄金法则:永远让查询值的类型与列定义的类型保持一致。
五、最佳实践建议
- 严格类型匹配:在编写SQL时,确保查询条件的类型与字段类型一致 --- 在CI/CD流程中可加入SQL静态分析,自动化检测类型不匹配模式。
- 参数化查询:使用PreparedStatement,避免手动拼接SQL时忽略类型。
- 保持一致性:系统中的相同含义字段应使用相同的数据类型。合理选择字段类型也很重要,如果字段存储的是纯数字标识,优先考虑使用数字类型。
- 使用EXPLAIN验证:对复杂查询或性能关键查询,使用EXPLAIN确认索引使用情况。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/19612588
浙公网安备 33010602011771号