从全表扫描到覆盖索引:一次SQL调优的完整复盘

你有没有遇到过这种情况:生产环境突然告警,数据库CPU飙升到99%,整个系统卡成PPT,排查半天发现——就因为一条上线没多久的SQL。说实话,我在这行干了快八年,见过太多因为不懂索引策略和执行计划分析,把数据库搞到崩溃边缘的案例。今天这篇文章,我不讲什么高深理论,就拿真实案例一步步拆给你看,怎么用Explain对比找到问题,怎么设计索引策略让查询从十几秒降到几十毫秒。全是干货,建议收藏反复看。

一、为什么你的SQL总是慢?先搞懂底层逻辑

很多人一说SQL慢,第一反应就是"加索引"。但你知道吗,索引不是万能药,加错了反而更慢。我之前接手过一个项目,订单表有200万条数据,开发同事在create_time和status字段上各建了一个单列索引,结果查询的时候MySQL走的是索引合并,效率比全表扫描还差。

这背后的原因其实很简单:MySQL的查询优化器会根据成本估算选择执行路径。如果你的索引设计不合理,优化器就会做出错误判断。所以调优的第一步,不是盲目加索引,而是先看懂Explain输出的每一列到底在说什么。

二、Explain到底怎么看?逐列拆解

Explain是MySQL提供的执行计划分析工具,在SQL前加上EXPLAIN就能看到优化器打算怎么执行这条查询。很多人只看type和key,但实际上每一列都有信息量。下面我用一个实际案例来拆解。

假设我们有一张订单表orders,结构如下:

字段名 类型 说明

id bigint 主键

user_id bigint 用户ID

status tinyint 订单状态(0待付款,1已付款,2已发货)

create_time datetime 创建时间

amount decimal 订单金额

原SQL如下:

sql

SELECT * FROM orders

WHERE user_id = 10086

AND status = 1

AND create_time > '2024-01-01';

执行EXPLAIN后的输出:

id select_type table type possible_keys key key_len ref rows Extra

1 SIMPLE orders ALL idx_user_id,idx_status NULL NULL NULL 2000000 Using where

看到type = ALL了吗?这说明全表扫描,200万行数据逐行过滤,能不慢吗?key = NULL说明没用上任何索引,rows = 2000000意味着扫描了全部数据。

那我们怎么优化?别急,先看完下面的索引策略部分。

三、索引策略不是随便建的,这几个原则要记死

1、最左前缀原则是铁律

联合索引(a, b, c)相当于建了(a)、(a, b)、(a, b, c)三个索引。但如果你的查询条件是WHERE b = 1 AND c = 2,这个联合索引是完全用不上的,因为跳过了最左边的a。这是最多人踩的坑,没有之一。

2、区分度高的字段放前面

什么叫区分度?就是这个字段的不同值占总行数的比例。比如status字段只有0、1、2三个值,区分度极低,放在联合索引最前面基本等于没用。而user_id有几十万个不同值,区分度高,应该放前面。

3、不要在索引列上做运算

下面这条SQL看着简单,但索引直接失效:

sql

SELECT * FROM orders

WHERE YEAR(create_time) = 2024;

因为对create_time做了函数运算,MySQL没法用索引。正确的写法是:

sql

SELECT * FROM orders

WHERE create_time >= '2024-01-01'

AND create_time < '2025-01-01';

4、覆盖索引能省一大笔IO

如果查询的字段全部包含在索引里,MySQL就不需要回表查数据行,这叫覆盖索引。比如:

sql

-- 普通查询,需要回表

SELECT id, user_id, status, create_time, amount

FROM orders WHERE user_id = 10086 AND status = 1;

-- 覆盖索引查询,不需要回表

SELECT user_id, status, create_time

FROM orders WHERE user_id = 10086 AND status = 1;

四、实战案例:从12秒到38毫秒的全过程

回到刚才那个订单查询的例子。原SQL扫描了200万行,耗时12秒。我的优化思路如下:

1、分析查询条件:user_id是等值查询,status是等值查询,create_time是范围查询。根据最左前缀原则和区分度排序,索引顺序应该是user_id → status → create_time。

2、建立联合索引:

sql

ALTER TABLE orders

ADD INDEX idx_user_status_time (user_id, status, create_time);

3、优化后再次Explain:

id select_type table type possible_keys key key_len ref rows Extra

1 SIMPLE orders ref idx_user_status_time idx_user_status_time 17 const,const 156 Using index condition

看到变化了吗?type从ALL变成了ref,说明走了非唯一索引扫描。rows从200万降到了156,Extra里出现了Using index condition,说明用上了索引下推优化。实际执行时间从12秒降到了38毫秒,提升了将近300倍。

但这还没完,我发现查询里用了SELECT *,其实业务只需要id、user_id、status、create_time这几个字段,amount根本用不到。于是我把查询改成只取需要的字段,让它变成覆盖索引:

sql

SELECT id, user_id, status, create_time

FROM orders

WHERE user_id = 10086

AND status = 1

AND create_time > '2024-01-01';

再次Explain:

id select_type table type possible_keys key key_len ref rows Extra

1 SIMPLE orders ref idx_user_status_time idx_user_status_time 17 const,const 156 Using index

注意Extra从Using index condition变成了Using index,这意味着完全走了覆盖索引,连数据行都不用读了。执行时间进一步降到了12毫秒。

五、两种Explain结果对比,差距一目了然

我把优化前后的Explain放在一起对比,你就能直观感受到差距:

对比项 优化前 优化后

type ALL(全表扫描) ref(索引扫描)

possible_keys idx_user_id, idx_status idx_user_status_time

key NULL idx_user_status_time

rows 2000000 156

Extra Using where Using index

执行时间 12秒 12毫秒

同样一条SQL,优化前后的执行计划完全是两个世界。这就是为什么我一直说,调优不是靠感觉,是靠数据说话。Explain就是你手里最强的武器。

六、几个容易被忽略的调优细节

1、☆ 索引不是越多越好。每多一个索引,写操作(INSERT、UPDATE、DELETE)的开销就多一分。我见过一张表建了12个索引,结果写入性能比没有索引还差。一般来说,单表索引控制在5个以内比较合理。

2、☆ 注意隐式类型转换。如果user_id是bigint类型,但查询时写成了WHERE user_id = '10086',MySQL会把字符串转成数字,索引照样失效。这类问题特别隐蔽,Explain里看不出来,但实际就是不走索引。

3、☆ 善用FORCE INDEX和IGNORE INDEX。有时候优化器的选择并不是最优的,你可以用这两个hint强制指定走哪个索引。但这属于"大招",平时慎用,用之前一定要确认你比优化器更了解数据分布。

4、☆ 定期分析表。ANALYZE TABLE orders;这条命令会更新索引的统计信息,让优化器的成本估算更准确。很多时候SQL突然变慢,就是因为统计信息过期了,跑一下这条命令就能恢复。

七、总结:调优是一门手艺活,得靠练

说到底,SQL调优这件事没有什么银弹。索引策略、Explain分析、执行计划对比,这些都是基本功。但光看文章不动手,永远学不会。我的建议是:拿自己项目里最慢的那条SQL出来,先跑一遍Explain,看懂每一列的含义,然后试着建索引、改写法、再对比执行计划。反复练个十几次,你就会形成直觉,一眼就能看出问题在哪。

希望这篇文章能帮你少走一些弯路。如果你在调优过程中遇到什么奇怪的执行计划,欢迎留言讨论,咱们一起拆。

💡注意:本文所介绍的软件及功能均基于公开信息整理,仅供用户参考。在使用任何软件时,请务必遵守相关法律法规及软件使用协议。同时,本文不涉及任何商业推广或引流行为,仅为用户提供一个了解和使用该工具的渠道。

你在生活中时遇到了哪些问题?你是如何解决的?欢迎在评论区分享你的经验和心得!

希望这篇文章能够满足您的需求,如果您有任何修改意见或需要进一步的帮助,请随时告诉我!

感谢各位支持,可以关注我的个人主页,找到你所需要的宝贝。

博文入口:https://blog.csdn.net/Start_mswin 复制到【浏览器】打开即可,宝贝入口:https://pan.quark.cn/s/b42958e1c3c0 宝贝:https://pan.quark.cn/s/1eb92d021d17

作者郑重声明,本文内容为本人原创文章,纯净无利益纠葛,如有不妥之处,请及时联系修改或删除。诚邀各位读者秉持理性态度交流,共筑和谐讨论氛围~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐