1.1 为什么需要关心SQL性能
SQL性能优化,核心就解决三个问题:
响应时间:大促报表能不能在分钟级跑出来
资源消耗:查询会不会把CPU和内存打满,影响其他业务
稳定性:大数据量下查询会不会超时或失败
1.2 学习前的准备工作
1.2.1 准备一份千万级测试数据
为了真实感受性能差异,我们需要一份接近生产环境的数据。可以用以下方式生成:
步骤1:创建订单表结构
步骤2:用存储过程生成千万级数据
1.2.2 学习使用EXPLAIN分析执行计划
EXPLAIN是SQL优化的核心工具,展示MySQL如何执行你的查询。
基础用法:
EXPLAIN输出关键字段:
type:访问类型,从好到差依次是 const > eq_ref > ref > range > index > ALL(全表扫描)
possible_keys:可能用到的索引
key:实际使用的索引
rows:预估扫描的行数
Extra:额外信息,Using where、Using index、Using filesort等
第2章 索引核心原理
2.1 索引的本质
索引就像书的目录,让你快速定位到需要的内容,不用一页页翻。MySQL的InnoDB引擎使用B+树结构,索引本身也是一张表,存储了排好序的键值和对应的行指针。
2.2 聚集索引与非聚集索引
聚集索引:数据和索引存放在一起,每张表只能有一个。InnoDB中,主键就是聚集索引。如果没有定义主键,MySQL会选第一个唯一索引,都没有则自动生成隐藏行ID。
非聚集索引:索引和数据分开存储,索引叶子节点存储的是主键值(回表需要)。一张表可以有多个非聚集索引。
电商场景:订单表通常用order_id做主键(聚集索引)。如果经常按user_id查询,应该为user_id创建非聚集索引。
2.3 联合索引
联合索引是对多个字段建立的索引,比如(shop_id, create_time)。最左前缀原则:查询条件必须从索引的最左列开始,索引才能生效。
电商场景:经常按店铺和时间查询订单,可以建联合索引(shop_id, create_time)。查询WHERE shop_id = 123 AND create_time BETWEEN ...能用上索引;但WHERE create_time BETWEEN ...用不上,因为跳过了最左列。
2.4 索引设计原则
选择性高的字段优先:user_id选择性高,order_status只有几个值,选择性低,单独建索引效果差
频繁作为查询条件的字段建索引
频繁排序、分组的字段建索引(ORDER BY、GROUP BY)
避免过多索引:每个索引都会占用磁盘空间,降低写入性能
索引列避免函数计算和隐式类型转换
第3章 执行计划EXPLAIN详解与实操
3.1 EXPLAIN的使用方法
在SQL前加上EXPLAIN,MySQL会返回执行计划,而不是真正执行查询。
步骤1:查看全表扫描的执行计划
结果:type = ALL,rows接近全表行数,说明没有索引,全表扫描。
步骤2:创建索引后再看
结果:type = ref,key = idx_user_id,rows大幅减少。
3.2 关键字段解读
Using filesort:需要额外的排序操作,通常发生在ORDER BY没有索引支持时。
Using temporary:使用临时表,常见于GROUP BY、DISTINCT没有索引优化时。
第4章 电商场景高频SQL优化技巧
4.1 避免索引失效的常见场景
4.1.1 索引列使用函数
4.1.2 隐式类型转换
4.1.3 使用!=或<>
!=和<>通常不走索引,用IN或BETWEEN替代。
4.1.4 LIKE以通配符开头
4.2 大促订单数据日期范围查询优化
业务场景:双11当天,需要查询11月1日至11月11日的订单。
优化前:
如果create_time没有索引,全表扫描。
优化后:
4.3 用户ID精准查询优化
优化前:没有索引,全表扫描。
优化后:
如果只需要少量字段,甚至可以建覆盖索引,避免回表。
第5章 复杂SQL优化方法
5.1 子查询优化
场景:找出购买了销量TOP10商品的用户。
优化前:子查询每次都要重新计算TOP10商品,性能差。
优化后:用JOIN+临时表
5.2 多表关联优化
场景:统计每个店铺的GMV,关联订单表和店铺表。
优化前:关联字段没有索引,产生笛卡尔积。
优化后:确保关联字段有索引,并且先在子查询中过滤数据,减少关联数据量。
5.3 聚合查询优化
场景:按月统计GMV。
优化前:GROUP BY没有索引,产生临时表和文件排序。
优化后:先按日期字段分组,再用函数格式化。
第6章 电商海量数据查询的最佳实践
6.1 分区表
当一张表数据量极大(如几亿条),即使有索引,查询也可能慢。分区表可以将数据按时间、范围等逻辑切分到不同物理文件。
电商场景:订单表按年份分区。
查询WHERE create_time >= '2025-01-01'时,MySQL只扫描p2025分区。
6.2 数据范围限制
在开发查询时,养成加范围限制的习惯,避免扫描全表。
6.3 避免全表扫描的核心技巧
EXPLAIN用检查:任何生产查询前,先用EXPLAIN确认不走全表扫描
优先用覆盖索引:查询的字段都在索引中,直接返回索引值,不回表
LIMIT合理使用:在验证SQL时,先加LIMIT 100测试性能
定期维护索引:删除无用索引,重建碎片多的索引
第7章 综合实操案例:双11千万级订单数据慢查询优化
7.1 案例背景
业务场景:双11活动结束后,需要统计以下指标:
按小时统计GMV趋势
按店铺统计GMV TOP10
高价值用户(累计消费>5000元)名单
原始SQL在没有优化的情况下,在1000万订单数据上运行超过5分钟。
7.2 步骤1:分析原始SQL执行计划
原始SQL(按小时统计):
执行计划:
全表扫描+临时表+文件排序,性能极差。
7.3 步骤2:添加索引优化
步骤1:创建索引(order_status, create_time),因为WHERE条件包含这两个字段。
步骤2:再次分析执行计划。
结果:type: range,key: idx_status_time,rows: 200000(只扫描符合条件的行),Extra: Using index condition; Using temporary; Using filesort。虽然还有临时表,但扫描行数从1000万降到20万。
7.4 步骤3:优化聚合方式
GROUP BY按小时需要转换create_time,导致无法利用索引排序。改用YEAR、MONTH、DAY、HOUR分组。
执行计划:Extra中Using temporary消失,因为分组字段是create_time的前缀,可以利用索引有序性。
7.5 步骤4:按店铺统计GMV TOP10优化
原始SQL:
优化:在(order_status, create_time, shop_id)上建联合索引,覆盖WHERE和GROUP BY。
执行计划显示Using index,全部从索引获取数据,不回表。
7.6 步骤5:高价值用户查询优化
原始SQL:
优化:先过滤出符合条件的订单(用索引),再分组。
执行计划:type: range,key: idx_status_time_user,Extra: Using index,覆盖索引避免了回表。
7.7 优化前后性能对比
第8章 本章踩坑清单与合规总结
8.1 新手常见踩坑
8.2 电商数据合规提示
海量数据导出限制:性能优化后,查询快了,但不要因此随意导出全量数据。公司通常有数据导出审批流程,即使技术可行,也要遵守合规要求。
生产环境操作规范:创建索引、修改表结构等操作,需要在业务低峰期进行,并提前通知相关方。有些公司要求必须在变更窗口执行,并通过DBA审核。
敏感数据查询:在优化查询时,如果涉及用户ID、手机号等敏感字段,记得在测试环境中用脱敏数据,不要直接用生产库调试。优化完成后,确认查询结果不包含不应暴露的敏感信息。
第9章 结语
SQL性能优化不是一蹴而就的,需要理解索引原理、掌握执行计划分析、不断实践。电商场景下的海量数据,往往只需几个索引就能让查询从分钟级降到秒级。
有问题的评论区留言,我看到会回复。