MySQL数据库慢查询的排查思路和最佳实践

描述

背景与问题

数据库慢查询是导致应用响应缓慢最常见的原因之一。当业务人员反馈“页面加载慢”、“查询超时”、“系统卡顿”时,很多运维人员的第一反应是让开发人员“加个索引”。但加索引只是优化查询的众多手段之一,盲目加索引不仅可能无效,还可能适得其反。

真正的慢查询优化需要系统的方法:首先确认慢查询的事实,然后分析查询的执行计划,理解数据库的查询优化器决策,找出真正的瓶颈所在,最后选择最合适的优化手段。优化手段包括但不限于:创建合适的索引、重写 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 模式;根据业务特点制定优化方案。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分