1. 业务场景描述
在某电商平台,对商品订单数据进行统计分析时,后台报表接口响应时间经常超过5秒,严重影响业务体验。进一步定位发现,涉及千万级别的order和order_item表,多表join和聚合查询导致mysql查询性能瓶颈。为了保证统计接口的实时性与可用性,需要对慢查询进行系统优化。
关键痛点:
- 表数据量大(订单表超过2000万行)
- 多表关联和复杂聚合(sum、group by)
- 高并发读请求影响主库负载
2. 技术选型过程
为了解决上述问题,我们评估了以下几种方案:
方案a:在主库打开慢查询日志+使用explain手动优化
方案b:使用mysql proxy/中间件做sql路由及分片
方案c:引入elasticsearch做离线统计
方案d(最终选型):主库+备库读写分离 + 组合索引优化 + sql重写 + 分区分表方案
选型理由:
- a方案可快速定位并优化单条sql,但无法构建整体可扩展体系
- b方案需要中间件改造成本高,团队不具备足够维护经验
- c方案脱离mysql生态,数据同步延迟高,无法满足实时性
- d方案在现有架构上扩展成本较低,可渐进式上线,兼顾实时性与可维护性
3. 实现方案详解
3.1 开启慢查询日志与收集数据
在my.cnf中开启慢查询日志,并设置合理阈值(例如2秒):
[mysqld] slow_query_log = on slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 log_queries_not_using_indexes = on
重启后,让mysql开始记录慢查询。
3.2 使用pt-query-digest分析日志
借助percona toolkit:
pt-query-digest /var/log/mysql/slow.log > slow_report.txt
报告中会列出最耗时、最频繁的sql以及全表扫描等信息。
3.3 explain分析瓶颈sql
以典型慢查询为例:
select oi.product_id, sum(oi.quantity) as total_sold from order_item oi join `order` o on oi.order_id = o.id where o.status = 'completed' and o.created_at between '2023-01-01' and '2023-06-30' group by oi.product_id;
执行explain:
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra |
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+-------+----------+-------------+
| 1 | simple | o | null | all | idx_status | null | null | null |2000000| 10.00 | using where |
| 1 | simple | oi | null | ref | idx_order_id | idx_order_id | 4 | test.o.id | 500000| 100.00 | using index |
+----+-------------+-------+------------+------+---------------+------+---------+----------------------+-------+----------+-------------+
可以看到订单表o全表扫描,需要优化索引。
3.4 添加组合索引
针对order(status, created_at)添加组合索引:
alter table `order` add index idx_status_created_at (status, created_at);
再次执行explain:
| type: range, key: idx_status_created_at, rows: 50000, extra: using where; using index
大幅减少扫描行数。
3.5 sql重写与分区
分区表:
alter table `order` partition by range ( year(created_at) ) ( partition p2021 values less than (2022), partition p2022 values less than (2023), partition pmax values less than maxvalue );
重写sql使分区裁剪生效:
... where created_at >= '2023-01-01' and created_at < '2023-07-01' ...
保证时间范围在单个或少数分区。
3.6 读写分离
使用mysql proxy或中间件(如atlas、mycat)将读请求路由到从库,减轻主库压力。
js配置示例(sequelize+xorm):
const sequelize = new sequelize('db', 'user', 'pass', {
dialect: 'mysql',
replication: {
read: [{ host: 'slave1', username: 'user', password: 'pass' }],
write: { host: 'master', username: 'user', password: 'pass' }
}
});
4. 踩过的坑与解决方案
坑1:索引列顺序错误导致无效索引。
解决:严格按照where和group by字段顺序设计组合索引。
坑2:分区表改造在线迁移复杂。
解决:采用pt-online-schema-change工具在线拆分分区、添加索引。
坑3:读写分离一致性问题。
解决:针对关键业务使用session.pin或读写同连接,确保读到最新数据。
坑4:过度使用in子查询引起临时表。
解决:改写为join或exists,或使用窗口函数(mysql 8.0+)。
5. 总结与最佳实践
- 常规优化步骤:慢日志→分析报告→explain→补索引→sql重写。
- 大表建议分区分表,结合分区裁剪减少扫描范围。
- 生产环境上线前使用
pt-query-digest+explain验证性能。 - 读写分离及缓存(如redis)配合使用,可进一步提升读性能。
- 定期回顾慢日志:随着数据增长,不断迭代优化。
通过以上实战方法,可以将统计接口响应时间从5秒优化至500ms以内。在实际项目中,建议结合自身业务特点,灵活运用上述手段,持续监控并优化数据库性能。
到此这篇关于mysql性能优化之慢查询优化实战指南的文章就介绍到这了,更多相关mysql慢查询优化内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论