这些sql可能引起全表扫描,却常被误用
这些sql可能引起全表扫描,却常被误用
大多数人在写SQL的时候,仅会考虑“是否在对应字段上建了索引”,默认数据库会帮程序兜底。
然而,数据库不会替开发判断“业务是否合理”,它只会老老实实将SQL跑完。
下面是一些我在真实项目里见过,且第一次看时都显得挺正常的“SQL坑”。
1.基本错误:对索引字段做函数表达式||对索引进行隐式类型转换
- 需求:查询2025年6月15日的数据(假设create_time存的是date类型)
1
2
3
SELECT *
FROM user
WHERE DATE(create_time) = '2025-06-15';
老生常谈的话题,索引存的是create_time,查的是DATE(create_time),索引直接失效。 结果就是:👉全表扫描+每行计算Date
正确写法:
1
2
WHERE create_time >= '2025-06-15 00:00:00'
AND create_time < '2025-06-06 00:00:00';
- order_no存VARCHAR,查询时传递int:
1
2
3
SELECT *
FROM order
WHERE order_no = 123456;
这个也没啥好说的,mysql会先把字段转成数字,再进行比较,等价于:
1
WHERE CAST(order_no AS SIGNED) = 123456;
索引直接作废。
- 其他:LIKE以’%’开头,不遵守‘最左前缀’等,不赘述
2.进阶错误:or条件把索引拼没了
- 🌰:
1
2
3
4
SELECT *
FROM post
WHERE author_id = 1
OR editor_id = 1;
明明author_id和editor_id都建了索引,为什么会失效?
- 问题在于:
- MySQL很难同时高效利用两个索引,特别是在OR选择性不好时。
- 此时,优化器更偏向放弃索引,采用全表扫描。
这种最好写成如下👉:
1
2
3
SELECT * FROM post WHERE author_id = 1
UNION ALL
SELECT * FROM post WHERE editor_id = 1;
3.看起来高级:NOT IN/ NOT EXIST
🌰:
1
2
3
4
5
SELECT *
FROM user
WHERE id NOT IN (
SELECT user_id FROM blacklist
);
这种写法在一般情况下没有问题,但是如果inner sql查出NULL数据了呢? 而且在大表场景下:
NOT IN 本质是“排除所有不满足条件的行”
这对MySQL来说是一个非常重的操作。
多数情况下,使用LEFT JOIN + IS NULL反而更可控
即使要用示例中的sql,那么必须在 inner sql中加上 WHERE CONDITION
1
WHERE user_id IS NOT NULL;
4.小表join大表,条件写反了
🌰:
1
2
3
4
SELECT *
FROM big_table b
JOIN small_table s ON b.id = s.big_id
WHERE b.status = 1;
如果真正的过滤条件在小表,却写在大表,MySQL可能会:
- 先扫大表
- 匹配小表
在数据量很大的情况下,可能本来只想查100条,MySQL却扫描了100w行。
5.结语
很多时候,全表扫描并不是sql写错了,而是sql的语义已经超出了“索引能帮忙”的范围
全表扫描不一定是错,但无意识的全表扫描,一定是问题。
本文由作者按照 CC BY 4.0 进行授权