Calcifer Calcifer 2
Sql

mysql条件字符串整形优化

2019/09/11 19:28 6873 次阅读 王梓
打赏
✸ ✸ ✸

一个真实的性能问题

一条简单的查询,执行时间从 0.1 秒飙到 20 多秒,原因竟然只是 WHERE 条件少了一对引号。这就是 MySQL 隐式类型转换的坑。

问题复现

-- 表结构:orid 字段是 varchar 类型
CREATE TABLE test (
    id INT PRIMARY KEY,
    pid INT,
    orid VARCHAR(20),
    INDEX idx_orid (orid)
);

-- 慢查询:用数字去查 varchar 字段
SELECT * FROM test WHERE pid = 42 AND orid = 17526836821;
-- 执行时间: 23.5 秒  (全表扫描)

-- 正确写法:加上引号
SELECT * FROM test WHERE pid = 42 AND orid = '17526836821';
-- 执行时间: 0.1 秒  (走索引)

差距:200 倍以上

为什么会这样

当 WHERE 条件的值类型和字段类型不匹配时,MySQL 会做隐式类型转换

orid = 17526836821  (varchar 字段 vs 数字值)
                         |
                         v
MySQL 的处理方式:将 orid 列的每一行值转为数字再比较
                         |
                         v
相当于: CAST(orid AS SIGNED) = 17526836821
                         |
                         v
对列做了函数运算 --> 索引失效 --> 全表扫描

核心原因:MySQL 在比较 varchar 和 int 时,会把 varchar 转为 int(而不是把 int 转为 varchar)。对列做了转换操作,索引就用不上了。

用 EXPLAIN 验证

-- 不加引号:全表扫描
EXPLAIN SELECT * FROM test WHERE orid = 17526836821;
+----+------+------+------+------+----------+
| id | type | key  | rows | Extra            |
+----+------+------+------+------+----------+
|  1 | ALL  | NULL | 5000000 | Using where  |
+----+------+------+------+------+----------+
-- type=ALL 表示全表扫描,key=NULL 表示没用索引

-- 加引号:走索引
EXPLAIN SELECT * FROM test WHERE orid = '17526836821';
+----+------+----------+------+------+----------+
| id | type | key      | rows | Extra            |
+----+------+----------+------+------+----------+
|  1 | ref  | idx_orid |    1 | Using index cond |
+----+------+----------+------+------+----------+
-- type=ref 表示索引查找,rows=1 只扫描 1 行

隐式转换规则

MySQL 的隐式转换遵循以下规则:

字段类型条件值类型MySQL 的做法索引
VARCHARINT将 VARCHAR 列转为 INT失效
INTVARCHAR将 VARCHAR 值转为 INT正常
VARCHARVARCHAR直接比较正常
INTINT直接比较正常

关键规律:当两边类型不同时,MySQL 倾向于把字符串转为数字。如果转换发生在列上,索引就废了。

更多隐式转换的坑

-- 坑1:字符串字段用数字查
SELECT * FROM users WHERE phone = 13800138000;    -- 慢!索引失效
SELECT * FROM users WHERE phone = '13800138000';  -- 快!走索引

-- 坑2:字符集不匹配的 JOIN
-- 表 A 的 name 是 utf8,表 B 的 name 是 utf8mb4
SELECT * FROM A JOIN B ON A.name = B.name;
-- 可能导致索引失效,因为需要字符集转换

-- 坑3:隐式转换导致的数据错误
SELECT * FROM test WHERE orid = 17526836821;
-- 如果表中有 orid='17526836821abc',也会被匹配到!
-- 因为 CAST('17526836821abc' AS SIGNED) = 17526836821

如何排查隐式转换问题

# 1. 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;

# 2. 用 EXPLAIN 检查执行计划
EXPLAIN SELECT ...;
# 重点看 type 列:ALL=全表扫描,ref/range=走索引

# 3. 用 SHOW WARNINGS 查看 MySQL 的实际执行
EXPLAIN SELECT * FROM test WHERE orid = 17526836821;
SHOW WARNINGS;
# 会显示 MySQL 实际执行的 SQL,能看到类型转换

预防措施

  • 代码规范:查询条件的类型必须和字段类型一致,字符串字段一定加引号
  • ORM 框架:使用参数化查询,ORM 会自动处理类型
  • SQL 审计:上线前用 EXPLAIN 检查所有 SQL 的执行计划
  • 字段设计:手机号、身份证号等纯数字但不做运算的字段,用 VARCHAR 存储
  • JOIN 字段:确保关联字段的类型和字符集完全一致

总结

VARCHAR 字段用数字条件查询,MySQL 会把整列转为数字再比较,导致索引失效、全表扫描。修复方法很简单:给条件值加上引号。这个问题在生产环境中非常常见,尤其是手机号、订单号等"看起来像数字的字符串"字段。养成习惯:字符串字段的查询条件永远加引号。

✸ ✸ ✸

📜 版权声明

本文作者:王梓 | 原文链接:https://www.bthlt.com/note/330-Sqlmysql条件字符串整形优化

出处:葫芦的运维日志 | 转载请注明出处并保留原文链接

📜 留言板

留言提交后需管理员审核通过才会显示