1. 慢查询日志:数据库性能的晴雨表
上周排查线上服务响应延迟时,我注意到一个商品列表接口偶尔会出现2秒以上的响应时间。通过监控链路追踪,最终定位到是MySQL查询效率问题。这让我想起去年双十一大促前,我们通过系统化的慢查询分析优化,将核心接口的数据库响应时间降低了87%的经历。今天就来聊聊MySQL慢查询日志这个性能分析利器,以及如何基于日志进行索引优化。
慢查询日志就像是数据库系统的"黑匣子",它会记录所有执行时间超过指定阈值的SQL语句。对于日均百万级查询的电商系统来说,合理的慢查询配置能帮我们精准捕获问题SQL,而不会产生过多的日志噪音。更重要的是,通过分析这些慢查询模式,我们可以发现数据库设计中潜在的索引缺失、不合理的表关联等问题。
2. 慢查询日志配置与采集
2.1 动态开启慢查询日志
在MySQL 5.6及以上版本中,我们无需重启服务即可开启慢查询日志。以下是我常用的配置模板:
sql复制-- 查看当前慢查询配置状态
SHOW VARIABLES LIKE '%slow_query%';
-- 动态开启慢查询日志(重启后失效)
SET GLOBAL slow_query_log = 'ON';
-- 设置慢查询阈值为500毫秒(生产环境建议值)
SET GLOBAL long_query_time = 0.5;
-- 记录未使用索引的查询(重要!)
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 指定日志文件路径
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
注意:在阿里云RDS等托管服务上,可能需要通过控制台修改参数组来实现这些配置。我曾遇到过直接在会话中设置不生效的情况,这时候就需要检查数据库实例的参数组配置优先级。
2.2 永久化配置
对于需要持久化的环境,建议在my.cnf配置文件中添加:
ini复制[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 0.5
log_queries_not_using_indexes = 1
log_output = FILE
配置生效后,可以通过SHOW STATUS LIKE 'Slow_queries'查看当前累计的慢查询数量。这个指标可以纳入监控系统,当数值突增时触发告警。
3. 慢查询日志分析实战
3.1 使用mysqldumpslow工具
MySQL自带的mysqldumpslow工具可以对日志进行初步归类分析:
bash复制# 查看执行时间最长的10个慢查询
mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
# 按照出现次数排序
mysqldumpslow -s c -t 10 /var/log/mysql/mysql-slow.log
# 解析特定类型的查询
mysqldumpslow -g "SELECT" /var/log/mysql/mysql-slow.log
这个工具的输出虽然简单,但能快速定位高频出现的慢查询模式。去年我们就是通过它发现了一个被调用近万次的重复子查询。
3.2 使用pt-query-digest深度分析
Percona Toolkit中的pt-query-digest提供了更专业的分析能力。安装后执行:
bash复制pt-query-digest /var/log/mysql/mysql-slow.log > slow_report.txt
分析报告包含几个关键部分:
- 总体统计:日志时间范围、唯一查询数量、QPS等
- 响应时间分布:显示95%、99%等百分位的响应时间
- 查询明细:每个查询模式的执行统计和示例
我曾用这个工具分析出一个看似简单的UPDATE语句,因为全表扫描导致平均执行时间达1.8秒。报告中的"Tables"部分会显示查询涉及的表和访问类型,这是索引优化的关键线索。
4. 索引优化实战策略
4.1 索引设计原则
通过慢查询分析后,通常会暴露出以下几种索引问题:
- 缺失索引:WHERE条件或JOIN字段没有合适索引
- 冗余索引:多个索引有相同的前缀列
- 低效索引:索引列顺序不合理或使用了低选择性字段
针对商品表的查询优化案例:
sql复制-- 原始慢查询
SELECT * FROM products
WHERE category_id = 5
AND status = 'active'
AND price > 100
ORDER BY create_time DESC
LIMIT 20;
-- 优化后的复合索引
ALTER TABLE products ADD INDEX idx_cat_status_price (category_id, status, price);
这个案例中,索引列的顺序遵循了"等值查询列在前,范围查询列在后"的原则。create_time虽然出现在ORDER BY中,但因为前面有范围查询price > 100,所以不适合加入这个复合索引。
4.2 索引选择性优化
索引选择性是指索引列中不同值的数量与表中记录数的比例。高选择性的字段更适合建立索引:
sql复制-- 计算某列的选择性
SELECT
COUNT(DISTINCT user_id) / COUNT(*) AS selectivity
FROM orders;
在用户评论表中,我们曾为status字段建立了单列索引,但分析发现它的选择性只有0.001(只有3种状态值)。后来改为将status作为复合索引的最后一列,性能提升显著。
4.3 覆盖索引优化
当查询的所有列都包含在索引中时,MySQL可以直接从索引获取数据而无需回表:
sql复制-- 原始查询需要回表
SELECT product_name, price FROM products WHERE category_id = 5;
-- 优化为覆盖索引
ALTER TABLE products ADD INDEX idx_cat_name_price (category_id, product_name, price);
在电商系统的商品搜索功能中,通过创建包含搜索条件和返回字段的覆盖索引,我们将查询时间从120ms降低到了25ms。
5. 高级优化技巧与陷阱规避
5.1 JOIN优化实战
多表关联查询是慢查询的常见来源。一个订单查询的优化案例:
sql复制-- 原始慢查询
SELECT o.order_id, u.username, p.product_name
FROM orders o
JOIN users u ON o.user_id = u.user_id
JOIN products p ON o.product_id = p.product_id
WHERE o.create_time > '2023-01-01'
AND u.status = 'active';
-- 优化措施
1. 确保关联字段都有索引:orders(user_id, product_id), users(user_id), products(product_id)
2. 为orders.create_time添加索引
3. 考虑使用STRAIGHT_JOIN指定表连接顺序
5.2 分页查询优化
深度分页是性能杀手,典型的慢查询如:
sql复制-- 低效写法
SELECT * FROM orders ORDER BY create_time DESC LIMIT 10000, 20;
-- 优化方案1:使用索引覆盖+延迟关联
SELECT * FROM orders
INNER JOIN (
SELECT order_id FROM orders
ORDER BY create_time DESC
LIMIT 10000, 20
) AS tmp USING(order_id);
-- 优化方案2:记录上一页最后一条记录的ID
SELECT * FROM orders
WHERE order_id < 上次最后一条ID
ORDER BY order_id DESC
LIMIT 20;
5.3 常见陷阱与解决方案
-
隐式类型转换:当查询条件的类型与列定义不匹配时,会导致索引失效
sql复制-- user_id是varchar类型但传入数字 SELECT * FROM users WHERE user_id = 123; -- 错误 SELECT * FROM users WHERE user_id = '123'; -- 正确 -
函数操作索引列:对索引列使用函数会导致无法使用索引
sql复制-- 无法使用create_time索引 SELECT * FROM orders WHERE DATE(create_time) = '2023-01-01'; -- 改为范围查询 SELECT * FROM orders WHERE create_time >= '2023-01-01' AND create_time < '2023-01-02'; -
OR条件优化:使用UNION ALL替代部分OR条件
sql复制-- 低效写法 SELECT * FROM products WHERE category_id = 5 OR price > 100; -- 优化写法 SELECT * FROM products WHERE category_id = 5 UNION ALL SELECT * FROM products WHERE price > 100;
6. 持续监控与优化体系
6.1 建立慢查询监控看板
将以下指标纳入监控系统:
- 慢查询数量/分钟
- 平均慢查询时长
- 不同业务模块的慢查询分布
- 索引使用率统计
我们使用Grafana+Prometheus搭建了MySQL监控看板,当慢查询率超过5%时会触发告警。
6.2 定期索引健康检查
每月执行一次全面的索引分析:
sql复制-- 查找冗余索引
SELECT * FROM sys.schema_redundant_indexes;
-- 查找未使用的索引
SELECT * FROM sys.schema_unused_indexes;
-- 索引统计信息
SELECT * FROM mysql.innodb_index_stats
WHERE table_name = 'orders';
6.3 执行计划分析技巧
使用EXPLAIN分析关键查询:
sql复制EXPLAIN FORMAT=JSON
SELECT * FROM orders WHERE user_id = 100;
重点关注:
- type列:最好达到ref或range级别
- key列:确认使用了正确的索引
- rows列:预估扫描行数
- Extra列:是否出现Using filesort或Using temporary
在最近一次优化中,通过EXPLAIN发现一个查询虽然使用了索引,但需要扫描5万行数据。最终通过调整索引列顺序,将扫描行数降到了200行左右。