文章

这些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 进行授权