背景与问题
数据库慢查询是导致应用响应缓慢最常见的原因之一。当业务人员反馈“页面加载慢”、“查询超时”、“系统卡顿”时,很多运维人员的第一反应是让开发人员“加个索引”。但加索引只是优化查询的众多手段之一,盲目加索引不仅可能无效,还可能适得其反。
真正的慢查询优化需要系统的方法:首先确认慢查询的事实,然后分析查询的执行计划,理解数据库的查询优化器决策,找出真正的瓶颈所在,最后选择最合适的优化手段。优化手段包括但不限于:创建合适的索引、重写 SQL 语句、调整数据库配置、优化表结构、分表分库、引入缓存等。
本文以 MySQL 为例,详细讲解慢查询的排查思路、分析方法、优化手段和最佳实践。这些方法论同样适用于 PostgreSQL、Oracle 等主流数据库,只是具体命令和语法有所不同。
1 慢查询的发现与确认
1.1 开启慢查询日志
MySQL 的慢查询日志是排查慢查询的基础工具。默认情况下,慢查询日志是关闭的。需要通过配置启用。
查看当前慢查询配置:
-- 查看慢查询相关变量 SHOWVARIABLESLIKE'slow_query%'; SHOWVARIABLESLIKE'long_query_time'; SHOWVARIABLESLIKE'log_output'; -- 查看是否启用了慢查询日志 SHOWVARIABLESLIKE'slow_query_log'; -- 查看慢查询日志文件路径 SHOWVARIABLESLIKE'slow_query_log_file';
配置慢查询日志:
-- 开启慢查询日志 SET GLOBAL slow_query_log = 'ON'; -- 设置慢查询阈值为 1 秒(可以是浮点数) SET GLOBAL long_query_time = 1; -- 设置日志输出格式(TABLE 或 FILE) SET GLOBAL log_output = 'FILE'; -- 设置慢查询日志文件路径 SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
上述配置重启后会丢失,需要写入配置文件永久生效。编辑 MySQL 配置文件(/etc/mysql/my.cnf 或 /etc/my.cnf):
[mysqld] # 开启慢查询日志 slow_query_log = 1 # 慢查询日志文件路径 slow_query_log_file = /var/log/mysql/slow.log # 慢查询阈值(秒) long_query_time = 1 # 记录没有使用索引的查询 log_queries_not_using_indexes = 1 # 将慢查询记录到表(mysql.general_log) log_output = FILE
重启 MySQL 服务使配置生效:
systemctl restart mysql systemctl restart mysqld
1.2 慢查询日志格式解读
MySQL 慢查询日志记录了每次慢查询的详细信息:
# Time: 2024-01-15T1045.123456Z # User@Host: app_user[app_user] @ localhost [] # Query_time: 5.234567 Lock_time: 0.001234 Rows_sent: 100 Rows_examined: 50000 SET timestamp=1705315845; SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid' ORDER BY created_at DESC LIMIT 20;
关键字段说明:Time 是查询执行的时间;User@Host 是执行查询的用户和主机;Query_time 是查询实际执行时间(秒),这是最核心的指标;Lock_time 是等待锁的时间;Rows_sent 是返回给客户端的行数;Rows_examined 是扫描的行数,这个数字与 Rows_sent 的比值反映了查询效率。
Rows_examined 大而 Rows_sent 小,说明查询在扫描大量数据后才找到符合条件的记录,这是需要优化的典型特征。高效的查询应该让 Rows_examined 尽可能接近 Rows_sent。
1.3 使用 mysqldumpslow 分析慢查询日志
直接阅读原始慢查询日志非常困难,mysqldumpslow 工具可以对日志进行分析汇总:
# 安装 MySQL 后即可使用 mysqldumpslow -t 10 /var/log/mysql/slow.log # 参数说明: # -t N 显示前 N 条最慢的查询 # -s C 按平均查询时间排序(c=计数,t=时间,l=锁时间,r=返回行数) # -s S 按总查询时间排序 # -s R 按平均扫描行数排序 # -a 不聚合相同的查询 # -g PAT 只显示匹配 pattern 的查询 # 示例:显示最慢的 10 条查询 mysqldumpslow -t 10 -s t /var/log/mysql/slow.log # 显示扫描行数最多的查询 mysqldumpslow -t 10 -s r /var/log/mysql/slow.log # 显示查询次数最多的查询 mysqldumpslow -t 10 -s c /var/log/mysql/slow.log # 过滤特定表的查询 mysqldumpslow -t 10 -g 'orders' /var/log/mysql/slow.log # 使用正则过滤 mysqldumpslow -t 10 -a -g 'SELECT.*FROM.*WHERE' /var/log/mysql/slow.log
1.4 使用 pt-query-digest 进行深度分析
pt-query-digest 是 Percona Toolkit 中的专业分析工具,比 mysqldumpslow 功能更强大:
# 安装 Percona Toolkit # Ubuntu/Debian apt-get install percona-toolkit # RHEL/CentOS yum install percona-toolkit # 基本用法 pt-query-digest /var/log/mysql/slow.log # 输出到文件 pt-query-digest /var/log/mysql/slow.log > /tmp/query_analysis.txt # 只分析最近 24 小时的慢查询(需要日志中有时间戳) pt-query-digest --since '24h' /var/log/mysql/slow.log # 分析特定时间的查询 pt-query-digest --since '2024-01-15 1000' --until '2024-01-15 1200' /var/log/mysql/slow.log # 输出查询响应时间分布 pt-query-digest --type genlog /var/log/mysql/slow.log
pt-query-digest 的输出包括:查询执行时间分布直方图;按响应时间排序的查询列表;每种查询的出现次数、平均执行时间、扫描行数统计;查询执行计划摘要;可能导致问题的查询特征标记。
2 使用 EXPLAIN 分析查询执行计划
2.1 EXPLAIN 基本用法
EXPLAIN 是分析 SQL 查询执行计划的核心命令,它告诉 MySQL 优化器将如何执行查询,是排查慢查询最重要的工具。
-- 基本格式 EXPLAINSELECT * FROM orders WHERE user_id = 12345; -- 更详细的输出(包括扩展信息) EXPLAINFORMAT=JSONSELECT * FROM orders WHERE user_id = 12345; -- 查看 UPDATE、DELETE、INSERT 的执行计划 EXPLAINUPDATE orders SETstatus = 'shipped'WHERE order_id = 100; EXPLAINDELETEFROM orders WHEREstatus = 'cancelled';
2.2 EXPLAIN 输出字段详解
EXPLAIN 输出的每一列都包含重要的优化信息:
EXPLAIN SELECT o.*, u.name FROM orders o JOIN users u ON o.user_id = u.id WHERE o.user_id = 12345 AND o.status = 'paid' ORDER BY o.created_at DESC LIMIT 20;
输出示例:
+----+-------------+-------+------+-------------+------+--------+-------+---------------------+ | id | select_type| table | type | key | rows | filtered| Extra | +----+-------------+-------+------+-------------+------+--------+-------+ | 1 | SIMPLE | o | ref | idx_user | 50 | 100.00| Using | | 1 | SIMPLE | u | const| PRIMARY | 1 | 100.00| | +----+-------------+-------+------+-------------+------+--------+-------+
id 是查询中 SELECT 的序号,复合查询中 id 越大优先级越高。select_type 是查询类型:SIMPLE 是简单查询(不含子查询和 UNION);PRIMARY 是外层查询;SUBQUERY 是子查询;DERIVED 是派生表(FROM 中的子查询);UNION 是 UNION 操作的查询。table 是涉及的表名。type 是访问类型,表示 MySQL 决定如何查找表中的行,这是最关键的字段之一。possible_keys 是可供选择的索引列表。key 是实际选择的索引。key_len 是使用的索引长度。ref 是与索引比较的列。rows 是预计需要扫描的行数。filtered 是按条件过滤后剩余的百分比。Extra 是额外信息,包含优化提示。
2.3 type 字段详解
type 字段反映了查询的效率,从最好到最差依次是:system、const、eq_ref、ref、ref_or_null、index_merge、unique_subquery、index_subquery、range、index、ALL。
const 是最优的类型,表示只匹配一行,通常是通过主键或唯一索引查找。eq_ref 表示通过主键或唯一索引关联查询,每个索引值只对应一行记录。ref 表示通过普通索引查找,返回所有匹配索引值的行。range 表示使用索引范围查询(>、<、BETWEEN、IN 等)。index 表示全索引扫描,虽然比全表扫描好但仍然很慢。ALL 是最差的全表扫描,意味着没有使用任何索引。
如果查询的 type 是 ALL,说明没有使用索引,这是需要重点优化的对象。
-- 查看 type 为 ALL 的查询(需要优化的) EXPLAIN SELECT * FROM orders WHERE created_at > '2024-01-01'; -- 创建索引后,type 变为 range CREATE INDEX idx_orders_created_at ON orders(created_at); EXPLAIN SELECT * FROM orders WHERE created_at > '2024-01-01';
2.4 Extra 字段详解
Extra 字段包含 MySQL 解析查询的额外信息,常见的值及其含义:
Using filesort 表示 MySQL 无法利用索引完成排序,需要额外的排序操作。这是需要优化的信号。Using temporary 表示查询需要创建临时表来存储结果,通常发生在 ORDER BY 和 GROUP BY 操作中。Using index 表示使用了覆盖索引,查询只需要索引就能完成,不需要回表。Using index condition 表示使用了索引下推优化。Using where 表示在存储引擎层使用 WHERE 条件过滤。Using join buffer 表示使用了连接缓存。
-- 出现 Using filesort,需要优化排序 EXPLAIN SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC; -- 优化方案:利用索引消除 filesort CREATE INDEX idx_orders_user_created ON orders(user_id, created_at DESC);
2.5 使用 EXPLAIN ANALYZE(MySQL 8.0+)
MySQL 8.0 引入了 EXPLAIN ANALYZE,它不仅显示执行计划,还实际执行查询并报告真实的统计数据:
EXPLAIN ANALYZE SELECT o.*, u.name FROM orders o JOIN users u ON o.user_id = u.id WHERE o.user_id = 12345 AND o.status = 'paid' ORDER BY o.created_at DESC LIMIT 20;
输出包括预估的 rows 和实际执行的 rows,以及实际执行时间。通过对比预估和实际,可以发现统计信息过时或执行计划错误的问题。
3 索引的创建与优化
3.1 索引原理概述
理解索引的原理是正确使用索引的前提。MySQL 中最常用的是 B+Tree 索引,它将数据按照 B+Tree 数据结构组织,每个叶子节点包含所有数据(聚簇索引)或数据指针(非聚簇索引)。
B+Tree 索引的特点:所有叶子节点在同一层级,树的高度低,查询效率稳定;叶子节点之间通过链表连接,支持范围查询;每个节点可以存储多个键值,宽度大,层级浅。
索引的优势:大幅减少查询需要扫描的数据量;避免排序和临时表生成;支持索引覆盖扫描,直接返回结果。
索引的代价:占用额外的磁盘空间;写入操作(INSERT、UPDATE、DELETE)需要维护索引,降低写入性能;创建索引需要加锁,可能阻塞其他操作。
3.2 创建索引的原则
创建索引需要遵循以下原则:
选择区分度高的列创建索引。区分度指列中不重复值的比例,比例越高,索引效率越好。使用 SELECT COUNT(DISTINCT column) / COUNT(*) FROM table 计算区分度。
-- 查看列的区分度 SELECT COUNT(DISTINCT status) / COUNT(*) FROM orders; SELECT COUNT(DISTINCT user_id) / COUNT(*) FROM orders; -- status 区分度低(只有几种状态),user_id 区分度高 -- 应该为 user_id 创建索引,而不是 status
复合索引遵循最左前缀原则。复合索引 INDEX idx(a, b, c) 可以支持 WHERE a = ?、WHERE a = ? AND b = ?、WHERE a = ? AND b = ? AND c = ? 的查询,但不支持 WHERE b = ? 或 WHERE c = ? 的查询。
-- 创建复合索引 CREATEINDEX idx_orders_user_status ON orders(user_id, status, created_at); -- 这些查询可以使用该索引 SELECT * FROM orders WHERE user_id = 123; SELECT * FROM orders WHERE user_id = 123ANDstatus = 'paid'; SELECT * FROM orders WHERE user_id = 123ANDstatus = 'paid'AND created_at > '2024-01-01'; -- 这个查询无法使用该索引(最左前缀不满足) SELECT * FROM orders WHEREstatus = 'paid';
为主键和唯一约束创建唯一索引。唯一索引保证列值的唯一性,同时提供快速的唯一性查找。
3.3 索引创建实战
根据慢查询日志和 EXPLAIN 分析结果,为慢查询创建合适的索引:
-- 分析慢查询 -- SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid' ORDER BY created_at DESC LIMIT 20; -- 根据 WHERE 条件和 ORDER BY 创建复合索引 -- 1. 区分度高的列靠前:user_id > status -- 2. ORDER BY 的列需要与 WHERE 条件一起创建索引 -- 3. 需要倒序排序,如果是 MySQL 8.0+ 可以创建倒序索引 CREATEINDEX idx_orders_user_status_created ON orders(user_id, status, created_at DESC); -- 如果是 MySQL 5.7,需要创建两个索引 CREATEINDEX idx_orders_user_status ON orders(user_id, status); CREATEINDEX idx_orders_user_created ON orders(user_id, created_at DESC);
3.4 索引使用注意事项
避免在索引列上使用函数或进行计算,这会导致索引失效:
-- 索引失效 SELECT * FROM orders WHERE YEAR(created_at) = 2024; SELECT * FROM orders WHERE created_at + INTERVAL 1 DAY > NOW(); -- 正确的做法 SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
避免使用 LIKE 开头是通配符的查询:
-- 索引失效 SELECT * FROM orders WHERE order_no LIKE '%123%'; SELECT * FROM orders WHERE order_no LIKE '%123'; -- 可以使用索引 SELECT * FROM orders WHERE order_no LIKE 'ABC123%';
数据类型要匹配:
-- 如果 user_id 是 INT 类型 -- 错误:字符串与数字比较 SELECT * FROM orders WHERE user_id = '12345'; -- 正确:使用相同类型 SELECT * FROM orders WHERE user_id = 12345;
3.5 查看索引使用情况
创建索引后,需要确认索引是否被使用:
-- 查看表的索引 SHOWINDEXFROM orders; -- 使用 EXPLAIN 检查是否使用索引 EXPLAINSELECT * FROM orders WHERE user_id = 12345; -- 查看索引的基数(Cardinality,反映区分度) SHOWTABLESTATUSLIKE'orders'; -- MySQL 8.0+ 使用 INDEX_STATISTICS SELECT * FROM mysql.index_statistics WHERE table_name = 'orders';
4 SQL 语句优化
4.1 常见低效 SQL 模式
避免 SELECT *,只查询需要的列:
-- 低效:返回所有列 SELECT * FROM orders WHERE order_id = 12345; -- 高效:只查询需要的列 SELECT order_id, user_id, status, total_amount, created_at FROM orders WHERE order_id = 12345;
使用 LIMIT 限制返回行数:
-- 低效:没有 LIMIT,可能返回大量数据 SELECT * FROM orders WHERE user_id = 12345 ORDER BY created_at DESC; -- 高效:使用 LIMIT SELECT * FROM orders WHERE user_id = 12345 ORDER BY created_at DESC LIMIT 20;
分解大查询,减少单次查询的扫描范围:
-- 低效:一个复杂查询处理大量数据 SELECT o.*, u.*, p.* FROM orders o JOINusers u ON o.user_id = u.id JOIN products p ON o.product_id = p.id WHERE o.created_at > '2024-01-01'; -- 高效:分步处理,先筛选出符合条件的订单,再关联其他表 SELECT * FROM orders WHERE created_at > '2024-01-01'LIMIT1000; -- 然后用 IN 子句关联用户和产品 SELECT u.* FROMusersWHEREidIN (SELECTDISTINCT user_id FROM orders WHERE created_at > '2024-01-01');
4.2 优化 ORDER BY 和 GROUP BY
ORDER BY 使用索引可以避免 filesort:
-- 创建复合索引使 ORDER BY 使用索引 CREATE INDEX idx_orders_user_status_created ON orders(user_id, status, created_at DESC); -- 这个查询可以使用索引排序 SELECT * FROM orders WHERE user_id = 123 ORDER BY status, created_at DESC LIMIT 20;
GROUP BY 同样可以利用索引:
-- 如果查询经常按时间分组统计,创建按时间的索引 CREATE INDEX idx_orders_created_at ON orders(created_at); -- 分组查询 SELECT DATE(created_at), COUNT(*), SUM(total_amount) FROM orders GROUP BY DATE(created_at);
4.3 优化 JOIN 操作
确保 JOIN 条件有索引:
-- orders.user_id 和 users.id 都应该有索引 CREATE INDEX idx_orders_user_id ON orders(user_id); CREATE INDEX idx_users_id ON users(id); -- 使用 EXPLAIN 检查 JOIN 类型 EXPLAIN SELECT o.*, u.name FROM orders o JOIN users u ON o.user_id = u.id WHERE o.user_id = 12345;
小表驱动大表(让小表先过滤,减少被驱动表的扫描次数):
-- orders 表有 1000 万条记录,users 表有 10 万条记录 -- 正确:用小表驱动大表 SELECT o.*, u.name FROM users u JOIN orders o ON o.user_id = u.id WHERE u.status = 'vip'; -- 可能效率更低 SELECT o.*, u.name FROM orders o JOIN users u ON o.user_id = u.id WHERE u.status = 'vip';
4.4 使用 EXISTS 替代 IN
-- 子查询表很大的情况下,IN 的效率可能很低 SELECT * FROM orders WHERE user_id IN (SELECTidFROMusersWHEREstatus = 'vip'); -- 使用 EXISTS 通常更高效 SELECT * FROM orders o WHEREEXISTS (SELECT1FROMusers u WHERE u.id = o.user_id AND u.status = 'vip');
5 数据库配置优化
5.1 关键配置参数
MySQL 的配置文件对性能有重大影响。关键参数包括:
[mysqld] # InnoDB 缓冲池大小,通常设置为可用内存的 70-80% innodb_buffer_pool_size = 12G # 日志文件大小 innodb_log_file_size = 1G # 刷新日志的策略(影响写入性能和数据安全) innodb_flush_log_at_trx_commit = 1 # 最大连接数 max_connections = 500 # 查询缓存(MySQL 8.0 已移除) query_cache_size = 0 query_cache_type = 0 # 临时表和内存表大小 tmp_table_size = 256M max_heap_table_size = 256M # 慢查询日志 slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 # 记录未使用索引的查询 log_queries_not_using_indexes = 1
5.2 InnoDB 缓冲池优化
InnoDB 缓冲池是 InnoDB 最重要的性能参数,它缓存表数据和索引。缓冲池越大,可以缓存的数据越多,磁盘 I/O 越少。
-- 查看缓冲池使用情况 SHOWSTATUSLIKE'Innodb_buffer_pool%'; -- 查看缓冲池大小配置 SHOWVARIABLESLIKE'innodb_buffer_pool_size'; -- 动态调整缓冲池大小(MySQL 5.7+) SETGLOBAL innodb_buffer_pool_size = 12873741824; -- 12GB -- 设置缓冲池实例数量(建议 CPU 核心数) innodb_buffer_pool_instances = 8 -- 预热缓冲池(数据库重启后自动加载热数据到内存) innodb_buffer_pool_load_at_startup = 1
5.3 连接数管理
连接数过多会导致资源耗尽,连接数过少会限制并发能力。
-- 查看当前连接数
SHOWSTATUSLIKE'Threads_connected';
SHOWSTATUSLIKE'Max_used_connections';
-- 查看最大连接数
SHOWVARIABLESLIKE'max_connections';
-- 调整最大连接数
SETGLOBAL max_connections = 1000;
-- 查看连接来源
SHOWPROCESSLIST;
SHOWFULLPROCESSLIST;
-- 杀掉长时间空闲的连接
SELECTCONCAT('KILL ', id, ';')
FROM information_schema.processlist
WHERE Command = 'Sleep'ANDTime > 3600;
6 慢查询优化实战案例
6.1 案例一:分页查询优化
后台管理系统中常见的需求是分页查询订单列表:
-- 低效的深分页查询(页面越大越慢) SELECT * FROM orders ORDERBY order_id DESCLIMIT1000000, 20; -- 问题分析: -- 1. LIMIT offset 很大时,MySQL 需要扫描 offset + limit 条记录 -- 2. rows_examined 会非常大 EXPLAINSELECT * FROM orders ORDERBY order_id DESCLIMIT1000000, 20; -- 优化方案一:使用游标分页(基于上一页最后一条记录的 ID) SELECT * FROM orders WHERE order_id < 1234567 ORDERBY order_id DESC LIMIT20; -- 优化方案二:记录上次查询的位置 -- 前端记住 last_id = 1234567 -- 后端使用 WHERE order_id < last_id LIMIT 20 -- 优化方案三:如果必须使用 offset,使用覆盖索引 SELECT order_id FROM orders ORDERBY order_id DESCLIMIT1000000, 20; -- 确认使用了索引而不是 filesort
6.2 案例二:统计查询优化
需要统计每天的订单数量和金额:
-- 低效:实时计算,每次查询都扫描全表 SELECTDATE(created_at) ASday, COUNT(*) AS order_count, SUM(total_amount) AS total_amount FROM orders WHERE created_at >= '2024-01-01' GROUPBYDATE(created_at); -- 优化方案一:使用汇总表 CREATETABLE orders_daily_summary ( stat_date DATE PRIMARY KEY, order_count INTNOTNULLDEFAULT0, total_amount DECIMAL(15,2) NOTNULLDEFAULT0, updated_at TIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP ); -- 定期汇总(可以使用事件或 cron 任务) INSERTINTO orders_daily_summary (stat_date, order_count, total_amount) SELECTDATE(created_at), COUNT(*), SUM(total_amount) FROM orders WHEREDATE(created_at) = '2024-01-15' GROUPBYDATE(created_at) ONDUPLICATEKEYUPDATE order_count = VALUES(order_count), total_amount = VALUES(total_amount); -- 查询汇总表 SELECT * FROM orders_daily_summary WHERE stat_date >= '2024-01-01';
6.3 案例三:模糊搜索优化
用户表需要支持按用户名模糊搜索:
-- 低效:LIKE 开头是通配符导致全表扫描
SELECT * FROMusersWHEREnameLIKE'%zhang%';
-- 优化方案一:使用全文索引(MySQL 5.6+)
ALTERTABLEusersADD FULLTEXT INDEX ft_users_name(name);
SELECT * FROMusersWHEREMATCH(name) AGAINST('zhang');
-- 优化方案二:使用 Elasticsearch 等专业搜索引擎
-- 适用于模糊搜索需求复杂、对性能要求高的场景
-- 优化方案三:使用前缀索引(适用于前缀固定的场景)
CREATEINDEX idx_users_name_prefix ONusers(name(10));
-- 如果 name 通常以姓开头
SELECT * FROMusersWHEREnameLIKE'zhang%'; -- 可以使用索引
7 监控与预防
7.1 持续监控慢查询
使用 pt-query-digest 定期分析慢查询日志:
#!/bin/bash
# 每天凌晨分析昨天的慢查询日志
DATE=$(date -d "yesterday" +%Y-%m-%d)
SLOW_LOG="/var/log/mysql/slow.log"
REPORT="/var/log/mysql/slow_query_report_${DATE}.txt"
pt-query-digest --since "$(date -d 'yesterday 0000' +%s) seconds"
--until "$(date -d 'yesterday 2359' +%s) seconds"
--report-format=query_report
$SLOW_LOG > $REPORT
# 如果有新的慢查询,发送告警
if [ -s "$REPORT" ]; then
count=$(grep -c "Query"$REPORT || true)
if [ "$count" -gt 10 ]; then
echo"Found $count slow queries in the report" | mail -s "Slow Query Alert" ops@example.com
fi
fi
7.2 使用 Performance Schema
MySQL 5.6+ 提供了 Performance Schema,可以实时监控查询性能:
-- 启用相关的 instrument 和 consumer UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERENAMELIKE'statement/%'; UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERENAMELIKE'events_statements%'; -- 查看当前最慢的查询 SELECT DIGEST, COUNT_STAR, SUM_TIMER_WAIT / 1000000000000AS total_time_sec, AVG_TIMER_WAIT / 1000000000000AS avg_time_sec, SUM_ROWS_EXAMINED, SUM_ROWS_SENT, SUBSTR(DIGEST_TEXT, 1, 100) AS query_sample FROM performance_schema.events_statements_summary_by_digest ORDERBY SUM_TIMER_WAIT DESC LIMIT10;
7.3 建立慢查询治理流程
生产环境的慢查询治理应该成为日常工作的一部分:
日常监控:每天检查新增的慢查询
分析根因:使用 EXPLAIN 分析执行计划
制定优化方案:索引优化、SQL 改写、配置调整
上线验证:确认优化效果
归档记录:记录问题和解决方案,形成知识库
8 结论
数据库慢查询优化是一个系统性的工作,需要综合运用多种方法和工具。
首先,通过慢查询日志发现慢查询,使用 mysqldumpslow 或 pt-query-digest 进行分析;其次,使用 EXPLAIN 分析执行计划,找出性能瓶颈;然后,根据分析结果选择优化手段:创建合适的索引、重写 SQL 语句、调整数据库配置;最后,通过持续监控防止慢查询复发。
加索引只是优化手段之一,不是万能药。正确的优化思路是:先分析,再决策,最后实施。不加分析的盲目加索引可能适得其反。
运维工程师应该掌握的慢查询排查技能:开启和配置慢查询日志;使用 EXPLAIN 分析执行计划;创建和分析索引;识别常见的低效 SQL 模式;根据业务特点制定优化方案。
全部0条评论
快来发表一下你的评论吧 !