SQL优化实战:从Explain执行计划到千万级数据查询调优全解析
慢查询拖垮整个系统?一个Explain就能定位90%的性能瓶颈。在实际开发中,我们每天都在和数据库打交道,但真正懂SQL调优的人却少之又少。很多人遇到查询慢的第一反应就是加索引,结果越加越慢,系统反而更卡。今天这篇文章,我会用真实案例带你走一遍从发现问题、分析问题到解决问题的完整流程,看完之后你会发现,SQL优化并没有想象中那么玄乎。
一、SQL优化为什么这么重要
在互联网公司里,数据库几乎是所有业务的核心引擎。订单系统、用户系统、日志系统,全部依赖数据库来读写数据。一旦某条SQL写得不好,轻则接口响应变慢,重则直接把数据库拖死,造成全线服务不可用。
我之前经历过一次线上事故:一条统计报表的SQL在凌晨跑批时把主库CPU干到了100%,导致订单创建接口全部超时,持续了将近20分钟。后来排查发现,就是一个简单的多表关联查询,少写了一个过滤条件,导致全表扫描了一张800万行的表。
所以说,SQL优化不是锦上添花的技能,而是每个后端开发必须掌握的基本功。
二、Explain:SQL优化的第一把钥匙
想要优化SQL,首先得学会"看病"。而MySQL提供的Explain命令,就是最好的诊断工具。很多人写了几年SQL,从来没用过Explain,这是非常可惜的。
我们先来看一个简单的案例。假设有一张用户订单表orders,结构如下:
字段名 类型 说明
id bigint 主键
user_id bigint 用户ID
status tinyint 订单状态
create_time datetime 创建时间
amount decimal 订单金额
现在有一条查询SQL:
sql
SELECT * FROM orders WHERE user_id = 1001 AND status = 1;
我们用Explain看一下它的执行计划:
+----+-------------+--------+------+---------------+------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+-------+------+-------------+
| 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 5000 | Using where |
+----+-------------+--------+------+---------------+------+---------+-------+------+-------------+
☆ 从这张结果中我们能读出几个关键信息:
1、type列显示为ALL,说明这条SQL进行了全表扫描,这是性能最差的情况。
2、possible_keys和key都为NULL,说明没有用到任何索引。
3、rows显示为5000,意味着MySQL需要逐行扫描5000条记录才能找到结果。
这个结果一目了然:这条SQL没有走索引,需要优化。
三、索引策略:不是建得越多越好
很多开发人员有一个误区,觉得索引建得越多查询就越快。其实恰恰相反,索引是一把双刃剑。
☆ 索引的优点很明显:
1、大幅提升查询速度,特别是范围查询和排序操作。
2、能够有效减少需要扫描的数据行数。
☆ 但索引也有代价:
1、每张表上的每个索引都会占用磁盘空间,索引越多占用越大。
2、写入操作(INSERT、UPDATE、DELETE)会变慢,因为每次写数据都要同步更新索引树。
3、过多的索引会让MySQL优化器在选择执行计划时产生困惑,反而可能选错索引。
回到刚才的例子,我们给user_id和status分别加上索引:
sql
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
ALTER TABLE orders ADD INDEX idx_status (status);
再次执行Explain:
+----+-------------+--------+------+---------------------+-------------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------------+-------------+---------+-------+------+--------------------------+
| 1 | SIMPLE | orders | ref | idx_user_id,idx_status | idx_user_id | 8 | const | 120 | Using where; Using index |
+----+-------------+--------+------+---------------------+-------------+---------+-------+------+--------------------------+
☆ 这次的变化非常明显:
1、type从ALL变成了ref,说明走了非唯一索引扫描。
2、key列显示用到了idx_user_id这个索引。
3、rows从5000降到了120,扫描行数减少了97%以上。
不过这里还有一个问题:status这个字段的区分度很低,大部分订单的status都是1(已完成),所以单独建status索引效果并不好。更好的做法是建一个联合索引:
sql
ALTER TABLE orders DROP INDEX idx_user_id;
ALTER TABLE orders DROP INDEX idx_status;
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
再看Explain结果:
+----+-------------+--------+------+---------------------+----------------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------------+----------------+---------+-------+------+--------------------------+
| 1 | SIMPLE | orders | ref | idx_user_status | idx_user_status | 16 | const | 45 | Using where; Using index |
+----+-------------+--------+------+---------------------+----------------+---------+-------+------+--------------------------+
☆ 联合索引的效果更好:
1、rows进一步降到了45行,说明MySQL精准定位到了需要的数据。
2、Extra中出现了Using index,说明这是一个覆盖索引,连回表操作都省了。
四、查询优化案例:一个订单统计SQL的调优之路
下面分享一个我在实际项目中遇到的真实案例。
业务需求是:查询最近30天内,每个用户的订单总金额,并且只统计金额大于100元的订单。
最初开发写的SQL是这样的:
sql
SELECT user_id, SUM(amount) as total_amount
FROM orders
WHERE create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
AND amount > 100
GROUP BY user_id
ORDER BY total_amount DESC
LIMIT 10;
这条SQL在数据量只有几万行的时候跑得还行,但当订单表增长到500万行之后,查询时间直接飙到了8秒多。
💡注意:本文所介绍的软件及功能均基于公开信息整理,仅供用户参考。在使用任何软件时,请务必遵守相关法律法规及软件使用协议。同时,本文不涉及任何商业推广或引流行为,仅为用户提供一个了解和使用该工具的渠道。
你在生活中时遇到了哪些问题?你是如何解决的?欢迎在评论区分享你的经验和心得!
希望这篇文章能够满足您的需求,如果您有任何修改意见或需要进一步的帮助,请随时告诉我!
感谢各位支持,可以关注我的个人主页,找到你所需要的宝贝。
博文入口:https://blog.csdn.net/Start_mswin 复制到【浏览器】打开即可,宝贝入口:https://pan.quark.cn/s/b42958e1c3c0 宝贝:https://pan.quark.cn/s/1eb92d021d17
作者郑重声明,本文内容为本人原创文章,纯净无利益纠葛,如有不妥之处,请及时联系修改或删除。诚邀各位读者秉持理性态度交流,共筑和谐讨论氛围~
📋 复制整篇文章