MySQL慢查询优化实战:执行计划分析与联合索引设计

MySQL慢查询定位方法

MySQL慢查询日志(Slow Query Log)记录执行时间超过long_query_time阈值的所有SQL语句。生产环境中,少数慢查询即可拖垮整个数据库实例,导致连接池耗尽和应用超时。通过慢查询日志分析、EXPLAIN执行计划解读和索引优化,可将90%以上的慢查询响应时间降低一个数量级。

慢查询日志配置与采集

开启慢查询日志需要配置以下参数。建议long_query_time设为0.1秒(100ms),捕获足够多的慢查询样本用于分析:

-- 查看当前慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 动态开启(运行时生效,重启失效)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 0.1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
SET GLOBAL min_examined_row_limit = 100;

-- 持久化配置(my.cnf)
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.1
log_queries_not_using_indexes = 1
min_examined_row_limit = 100

log_queries_not_using_indexes开启后,未使用索引的查询即使执行时间未超阈值也会记录。min_examined_row_limit=100排除扫描行数少于100的查询,减少日志噪声。生产环境高负载时,慢查询日志写入会影响性能,建议通过Filebeat采集后关闭文件直写。

使用pt-query-digest分析慢查询日志,按总耗时排序找出影响最大的SQL:

# 分析慢查询日志,输出TOP SQL
pt-query-digest /var/log/mysql/slow.log --report --limit 10

# 按查询次数排序
pt-query-digest /var/log/mysql/slow.log --order-by Query_time:sum --limit 10

# 分析特定时间段
pt-query-digest /var/log/mysql/slow.log --since "2026-08-06 00:00:00" --until "2026-08-07 00:00:00"

EXPLAIN执行计划关键字段解读

EXPLAIN输出包含12个字段,以下5个是判断查询效率的核心:

字段 含义 关注点
type 访问类型 ALL(全表扫描)需优化,ref/range/eq_ref为佳
key 实际使用的索引 NULL表示未走索引
rows 预估扫描行数 越小越好,与实际行数对比判断选择性
Extra 附加信息 Using filesort、Using temporary需重点优化
key_len 索引使用长度 判断联合索引用了几列
-- 分析慢查询执行计划
EXPLAIN SELECT order_id, user_id, amount, status
FROM orders
WHERE user_id = 10086 
  AND status = 1 
  AND created_at >= '2026-07-01'
ORDER BY created_at DESC
LIMIT 20;

-- 查看实际执行成本(MySQL 8.0+)
EXPLAIN ANALYZE SELECT order_id, user_id, amount, status
FROM orders
WHERE user_id = 10086 AND status = 1 AND created_at >= '2026-07-01'
ORDER BY created_at DESC LIMIT 20;

EXPLAIN ANALYZE输出实际执行时间和行数,比普通EXPLAIN的预估值更准确。type=ref且rows接近LIMIT值时说明索引设计合理。若出现Using filesort,说明排序操作未使用索引,需要调整索引顺序。

联合索引与最左前缀原则

联合索引遵循最左前缀原则,查询条件必须从索引最左列开始连续匹配。以上面的查询为例,分析三种索引设计的效率差异:

-- 索引方案A: (user_id, status, created_at) - 最优
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, created_at);

-- 索引方案B: (user_id, created_at) - 次优,status需要回表过滤
ALTER TABLE orders ADD INDEX idx_user_time (user_id, created_at);

-- 索引方案C: (created_at, user_id, status) - 最差,created_at选择性低
ALTER TABLE orders ADD INDEX idx_time_user (created_at, user_id, status);

方案A中,user_id作为等值查询条件定位到索引范围,status进一步缩小范围,created_at用于排序,三者完全覆盖查询条件,无需回表和额外排序。方案C把created_at放在最前面,created_at范围查询无法精确定位,索引利用效率低。

判断索引列顺序的经验规则:等值查询列在前,范围查询列在后,排序列与范围列一致。选择性高的列优先,即 cardinality/总行数 比值大的列放前面。

覆盖索引消除回表操作

InnoDB的二级索引存储主键值,查询非索引列需要回表到聚簇索引获取完整数据行。覆盖索引指查询所需的所有列都包含在索引中,无需回表。EXPLAIN结果中Extra显示Using index即为覆盖索引:

-- 查询只需要order_id(主键)和user_id
SELECT order_id, user_id FROM orders WHERE user_id = 10086;

-- 索引方案: (user_id)
-- 执行计划: Using index(因为order_id是主键,已包含在二级索引中)

-- 查询需要order_id, user_id, status
SELECT order_id, user_id, status FROM orders WHERE user_id = 10086;

-- 索引方案: (user_id, status) - 覆盖索引
-- Extra: Using index
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);

覆盖索引trade-off:索引列增多会增大索引体积和写入开销。写多读少的表不适合添加大量覆盖索引,读多写少的表可适当放宽。通过以下查询统计读写比例,辅助索引决策:

-- 统计表的读写比例(InnoDB缓冲池统计)
SELECT 
    OBJECT_SCHEMA AS db,
    OBJECT_NAME AS table_name,
    COUNT_READ, COUNT_WRITE,
    ROUND(COUNT_READ / (COUNT_READ + COUNT_WRITE) * 100, 2) AS read_pct
FROM performance_schema.table_io_waits_summary_by_table
WHERE OBJECT_SCHEMA = 'your_db'
ORDER BY COUNT_READ + COUNT_WRITE DESC;

分页查询优化方案

LIMIT offset分页在offset较大时性能急剧下降,MySQL需要扫描offset+N行然后丢弃前offset行。LIMIT 1000000, 20的查询即使有索引也需扫描百万行数据。

延迟关联(Deferred Join)通过子查询先获取主键,再关联查询减少回表次数:

-- 差: 深度分页,扫描1000020行
SELECT * FROM orders 
WHERE user_id = 10086 
ORDER BY created_at DESC 
LIMIT 1000000, 20;

-- 好: 延迟关联,子查询走覆盖索引
SELECT t.* FROM orders t
INNER JOIN (
    SELECT order_id FROM orders 
    WHERE user_id = 10086 
    ORDER BY created_at DESC 
    LIMIT 1000000, 20
) tmp ON t.order_id = tmp.order_id;

游标分页(Cursor Pagination)利用上一页最后一条记录的值定位,避免offset扫描:

-- 第一页
SELECT * FROM orders 
WHERE user_id = 10086 
ORDER BY created_at DESC, order_id DESC
LIMIT 20;

-- 第二页(基于第一页最后一条记录的created_at和order_id)
SELECT * FROM orders 
WHERE user_id = 10086 
  AND (created_at < '2026-07-15 10:30:00' 
       OR (created_at = '2026-07-15 10:30:00' AND order_id < 12345))
ORDER BY created_at DESC, order_id DESC
LIMIT 20;

游标分页需要 (user_id, created_at, order_id) 联合索引,order_id作为唯一并列条件避免同一created_at多条记录时漏翻。这套方案在不支持跳页的场景下(如无限滚动)性能最优,固定时间复杂度。

Online DDL与索引变更

生产环境添加索引需要考虑锁表影响。MySQL 8.0的Online DDL支持 inplace + concurrent 模式,添加二级索引时不阻塞DML操作:

-- 在线添加索引(MySQL 8.0+,ALGORITHM=INPLACE不锁表)
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, created_at), 
ALGORITHM=INPLACE, LOCK=NONE;

-- 查看DDL进度
SELECT * FROM performance_schema.events_stages_current 
WHERE EVENT_NAME LIKE 'stage/innodb%';

大表(亿级数据)添加索引仍可能耗时数小时,推荐使用pt-online-schema-change或gh-ost工具在影子表上操作,减少主库负载和数据一致性风险。操作前评估索引大小增加比例,预留磁盘空间:

-- 预估索引大小
SELECT 
    ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS current_size_mb,
    ROUND(SUM(data_length) * 0.3 / 1024 / 1024, 2) AS estimated_new_index_mb
FROM information_schema.tables
WHERE table_name = 'orders';

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-man-cha-xun-you-hua-shi-zhan-zhi-xing-ji-hua-fen-xi/

(0)
小编小编
上一篇 2026年8月7日
下一篇 2026年8月7日

相关推荐

MySQL慢查询优化实战:执行计划分析与索引调优方法

慢查询是数据库性能问题的头号杀手。一个没有索引的全表扫描可能让查询从毫秒级退化到秒级,在高并发下迅速拖垮整个数据库。MySQL提供了EXPLAIN执行计划分析工具,通过解读执行计划可以精确定位性能瓶颈,针对性优化。

EXPLAIN执行计划关键字段解读

EXPLAIN是MySQL优化器的查询执行计划展示工具。在SQL语句前加EXPLAIN即可查看。重点关注以下字段:

EXPLAIN SELECT * FROM orders 
JOIN users ON orders.user_id = users.id 
WHERE orders.status = 'paid' AND users.city = '上海'
ORDER BY orders.created_at DESC LIMIT 20;
+----+-------------+--------+------+---------------+---------+---------+-------------------+--------+----------------------------------------------+
| id | select_type | table  | type | possible_keys | key     | key_len | ref               | rows   | Extra                                        |
+----+-------------+--------+------+---------------+---------+---------+-------------------+--------+----------------------------------------------+
|  1 | SIMPLE      | users  | ref  | PRIMARY,idx_city| idx_city| 102     | const             |  5000  | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | orders | ref  | idx_user       | idx_user| 8       | test.users.id     |  20000 | Using where                                  |
+----+-------------+--------+------+---------------+---------+---------+-------------------+--------+----------------------------------------------+

type字段表示访问类型,性能从好到差依次为:system > const > eq_ref > ref > range > index > ALL。ALL是全表扫描,必须优化。index是全索引扫描,通常也需要优化。range和ref是常见的合理访问类型。

key字段显示实际使用的索引。如果为NULL说明没有使用索引,需要排查原因。possible_keys列出了可能使用的索引,如果没有使用可能是因为统计信息过期或索引设计不合理。

rows字段是优化器估算的扫描行数,越少越好。如果rows很大但实际结果很少,说明索引选择性差或有更优的索引方案。

Extra字段包含额外信息。Using index表示索引覆盖查询,不需要回表,是最理想的状态。Using temporary表示用了临时表,通常出现在GROUP BY和DISTINCT场景。Using filesort表示需要额外排序,通常出现在ORDER BY字段没有索引的场景。Using temporary和Using filesort同时出现是性能危险信号。

索引设计原则与联合索引优化

索引设计遵循最左前缀原则。联合索引(a, b, c)可以用于a、(a,b)、(a,b,c)三种查询条件,但不能用于b或(b,c)查询。索引列顺序按区分度从高到低排列,区分度高的列放前面能更有效过滤数据。

-- 查看索引区分度
SELECT 
    COUNT(DISTINCT user_id) / COUNT(*) AS user_id_selectivity,
    COUNT(DISTINCT status) / COUNT(*) AS status_selectivity,
    COUNT(DISTINCT created_at) / COUNT(*) AS created_at_selectivity
FROM orders;

-- 假设结果:user_id=0.8, status=0.01, created_at=0.99
-- 联合索引应按 (status, user_id) 或 (user_id, status) 创建
-- status区分度低但常用于等值查询,放前面可以让后续索引列更有效过滤

上面的查询计划中users表出现了Using filesort,因为ORDER BY的是orders表的created_at字段,而JOIN后无法利用索引排序。优化方案是创建覆盖索引:

-- 为orders表创建联合索引,覆盖查询、排序需求
ALTER TABLE orders ADD INDEX idx_user_status_created (user_id, status, created_at);

-- 优化后的查询计划
EXPLAIN SELECT * FROM orders 
JOIN users ON orders.user_id = users.id 
WHERE orders.status = 'paid' AND users.city = '上海'
ORDER BY orders.created_at DESC LIMIT 20;

-- orders表执行计划变为:
-- type: ref, key: idx_user_status_created
-- Extra: Using where; Using index(索引覆盖,无需回表)

覆盖索引与索引下推优化

覆盖索引指查询的所有字段都包含在索引中,不需要回表读取数据行。InnoDB的聚簇索引结构决定了二级索引存储的是主键值,查询非索引字段需要先从二级索引取主键,再从聚簇索引取完整行数据。覆盖索引消除了回表操作,对IO密集型查询提升显著。

-- 查询只需要user_id和status两个字段
SELECT user_id, status FROM orders WHERE user_id = 123 AND status = 'paid';

-- 如果有索引idx_user_status_created(user_id, status, created_at)
-- user_id和status都在索引中,直接从索引返回,不需要回表
-- Extra: Using index 表示覆盖索引生效

索引下推(Index Condition Pushdown,ICP)是MySQL 5.6引入的优化。在没有ICP时,存储引擎根据联合索引的第一个列找到记录后返回给Server层,Server层再根据其他条件过滤。ICP将WHERE条件下推到存储引擎层,在索引遍历时就做过滤,减少回表次数。

-- 联合索引 (last_name, first_name)
SELECT * FROM employees 
WHERE last_name LIKE '张%' AND first_name LIKE '三%';

-- 无ICP:存储引擎找到所有last_name以"张"开头的记录,全部回表,Server层再过滤first_name
-- 有ICP:存储引擎在索引层同时检查last_name和first_name,只对满足条件的记录回表
-- Extra: Using index condition 表示ICP生效

分页查询深度翻页优化

LIMIT偏移量过大是常见的慢查询场景。LIMIT 1000000, 20需要扫描前100万行再丢弃,效率极低。优化方案有延迟关联和游标分页两种。

延迟关联:先通过子查询用覆盖索引取出主键,再JOIN原表取完整数据:

-- 优化前:扫描100万行
SELECT * FROM orders ORDER BY created_at DESC LIMIT 1000000, 20;

-- 优化后:子查询走覆盖索引取出主键,再JOIN
SELECT t.* FROM orders t
INNER JOIN (
    SELECT id FROM orders ORDER BY created_at DESC LIMIT 1000000, 20
) tmp ON t.id = tmp.id;

-- 假设created_at有索引,子查询走idx_created_at覆盖索引
-- 只需扫描索引取出20个主键,再回表20次,效率提升数百倍

游标分页:记住上一页最后一条记录的排序值,下一页从该值之后查询。完全避免OFFSET:

-- 第一页
SELECT * FROM orders ORDER BY created_at DESC LIMIT 20;

-- 第二页(假设第一页最后一条created_at = '2026-08-01 10:30:00', id = 5000)
SELECT * FROM orders 
WHERE created_at < '2026-08-01 10:30:00'
   OR (created_at = '2026-08-01 10:30:00' AND id < 5000)
ORDER BY created_at DESC, id DESC LIMIT 20;

-- 联合索引 (created_at, id) 让查询走索引范围扫描,恒定扫描20行

慢查询日志配置与分析

MySQL慢查询日志记录执行时间超过阈值的SQL,是发现性能问题的第一手段。配置方式如下:

-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 1;  -- 超过1秒记录
SET GLOBAL log_queries_not_using_indexes = 'ON';  -- 记录未用索引的查询

-- 永久生效写入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

mysqldumpslow工具可以聚合分析慢查询日志,按耗时或次数排序找出TOP SQL:

# 按总耗时排序
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

# 按返回行数排序
mysqldumpslow -s r -t 10 /var/log/mysql/slow.log

# 按次数排序
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log

对于更深入的分析,pt-query-digest(Percona Toolkit)提供了更详细的统计信息,包括SQL指纹、执行时间分布、97%分位等。优化SQL时应优先处理耗时排名靠前且出现频率高的查询。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-man-cha-xun-you-hua-shi-zhan-zhi-xing-ji-hua-fen-xi/

(0)
小编小编
上一篇 2026年8月6日
下一篇 2026年8月6日

相关推荐

MySQL慢查询优化实战:执行计划分析与索引调优方法论

MySQL慢查询是数据库运维中最常见的性能瓶颈来源。一条低效SQL在高并发下可能拖垮整个数据库实例,引发连锁超时。本文从执行计划解读入手,系统讲解SQL查询优化的方法论和实操技巧。

慢查询日志配置与采集

定位慢查询的第一步是启用慢查询日志,记录所有执行时间超过阈值的SQL语句。

-- 动态开启慢查询日志(无需重启)
SET GLOBAL slow_query_log = ON;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 0.5;
SET GLOBAL log_queries_not_using_indexes = ON;
SET GLOBAL min_examined_row_limit = 100;

-- 查看当前配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

使用pt-query-digest分析慢查询日志,按总耗时排序找出最需要优化的SQL:

# 安装Percona Toolkit
yum install percona-toolkit

# 分析慢查询日志,按总耗时排序
pt-query-digest /var/log/mysql/slow.log --order-by Query_time:sum

# 只统计最近1小时的慢查询
pt-query-digest --since "1h ago" /var/log/mysql/slow.log

EXPLAIN执行计划深度解读

拿到慢SQL后,使用EXPLAIN分析其执行计划是SQL查询优化的核心步骤。执行计划中的关键字段决定了查询的效率。

-- 查看执行计划
EXPLAIN SELECT o.id, o.total, u.name 
FROM orders o 
JOIN users u ON o.user_id = u.id 
WHERE o.status = 'paid' AND o.created_at > '2026-07-01'
ORDER BY o.created_at DESC 
LIMIT 20;

-- 查看完整执行计划(包含成本估算)
EXPLAIN FORMAT=JSON SELECT ...;

执行计划输出中需重点关注以下字段:

-- type字段:访问类型,性能从好到差
-- system > const > eq_ref > ref > range > index > ALL

-- const:主键或唯一索引等值查询,最快
EXPLAIN SELECT * FROM users WHERE id = 1;

-- eq_ref:JOIN时被驱动表使用主键或唯一索引
EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id;

-- ref:非唯一索引等值查询
EXPLAIN SELECT * FROM orders WHERE user_id = 100;

-- range:索引范围扫描
EXPLAIN SELECT * FROM orders WHERE created_at BETWEEN '2026-01-01' AND '2026-08-01';

-- index:全索引扫描
-- ALL:全表扫描(最差,必须优化)

key字段显示实际使用的索引,rows字段是预估扫描行数,Extra字段提供附加信息。以下Extra值需要注意:

-- Using index:覆盖索引,不需要回表,最优
-- Using where:通过索引查找后还需过滤
-- Using temporary:使用临时表(GROUP BY、DISTINCT常见)
-- Using filesort:文件排序(需要优化排序逻辑或索引)
-- Using join buffer:JOIN使用Block Nested Loop(缺少索引)

-- 糟糕示例:Using temporary; Using filesort
EXPLAIN SELECT user_id, COUNT(*) 
FROM orders 
WHERE status = 'paid' 
GROUP BY user_id 
ORDER BY COUNT(*) DESC;

复合索引设计与最左前缀原则

MySQL性能调优中,索引设计的核心原则是覆盖查询条件和排序需求。复合索引遵循最左前缀匹配规则,列顺序直接影响索引利用率。

-- 场景:订单查询,常见WHERE条件组合
-- WHERE status = ? AND created_at > ? ORDER BY created_at DESC
-- WHERE user_id = ? AND status = ? ORDER BY created_at DESC
-- WHERE user_id = ? AND created_at BETWEEN ? AND ?

-- 错误索引:单独建三个单列索引
-- 查询时MySQL最多选择一个索引,无法同时覆盖多条件
ALTER TABLE orders ADD INDEX idx_status (status);
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
ALTER TABLE orders ADD INDEX idx_created (created_at);

-- 正确索引:根据查询模式设计复合索引
-- 索引1:覆盖status + created_at查询和排序
ALTER TABLE orders ADD INDEX idx_status_created (status, created_at);

-- 索引2:覆盖user_id + status + created_at
ALTER TABLE orders ADD INDEX idx_user_status_created (user_id, status, created_at);

-- 验证索引使用情况
EXPLAIN SELECT * FROM orders 
WHERE user_id = 100 AND status = 'paid' 
ORDER BY created_at DESC LIMIT 20;
-- 期望:type=ref, key=idx_user_status_created, Extra=Using index

索引列顺序的经验法则:等值条件列在前,范围条件列在后,排序列最后。前缀匹配可以完整利用索引,排序也可以通过索引有序性避免filesort。

覆盖索引消除回表开销

InnoDB的二级索引存储的是主键值,通过二级索引查找数据需要先查索引拿到主键,再用主键回主键索引获取完整行。如果查询字段全部包含在索引中,可以跳过回表操作。

-- 原始查询:需要回表获取total字段
SELECT id, user_id, status, total FROM orders WHERE user_id = 100;
-- 索引 idx_user_status_created (user_id, status, created_at)
-- user_id走索引,但total不在索引中,需要回表

-- 创建覆盖索引
ALTER TABLE orders ADD INDEX idx_user_status_total (user_id, status, total);

-- 再次执行:Extra显示Using index,无需回表
EXPLAIN SELECT id, user_id, status, total FROM orders WHERE user_id = 100;
-- type: ref, Extra: Using index

覆盖索引的代价是索引体积增大和写入开销增加。在读取远多于写入的场景下,这是数据库高可用架构中提升读性能的有效手段。

分页查询优化与深分页问题

LIMIT offset在小偏移量时性能尚可,但当offset达到数万甚至百万级别时,MySQL需要扫描并丢弃前offset条记录,效率极低。分库分表方案中这个问题更加突出。

-- 慢查询:深分页,offset越大越慢
SELECT * FROM orders ORDER BY created_at DESC LIMIT 100000, 20;
-- 扫描100020行,丢弃前100000行

-- 方案1:游标分页(推荐)
SELECT * FROM orders 
WHERE created_at < '2026-08-05 10:30:00' 
ORDER BY created_at DESC 
LIMIT 20;
-- 只扫描20行,时间复杂度O(1)

-- 方案2:延迟关联
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders 
    WHERE user_id = 100 
    ORDER BY created_at DESC 
    LIMIT 100000, 20
) t ON o.id = t.id;
-- 子查询使用覆盖索引快速定位主键,外层关联20行

-- 方案3:缓存热门页
-- 前几页数据缓存在Redis中,避免重复查询

JOIN优化与驱动表选择

MySQL优化器自动选择驱动表,但有时选择并非最优。数据备份恢复后的统计信息不准确会导致优化器误判。必要时通过STRAIGHT_JOIN强制指定驱动表。

-- 查看JOIN的执行计划
EXPLAIN SELECT o.*, p.name 
FROM orders o 
JOIN products p ON o.product_id = p.id 
WHERE o.user_id = 100;

-- 关键看:
-- 1. 哪个表是驱动表(id=1的表)
-- 2. 被驱动表是否走索引(ref类型)
-- 3. rows乘积是否过大

-- 强制小表驱动大表
SELECT STRAIGHT_JOIN o.*, p.name 
FROM orders o 
JOIN products p ON o.product_id = p.id 
WHERE o.user_id = 100;

-- 更新统计信息
ANALYZE TABLE orders, products;

监控与持续优化

数据库运维中应建立慢查询持续监控机制。通过performance_schema实时捕获执行统计,配合自动化告警在问题恶化前发现并修复。

-- 启用statements_summary
UPDATE performance_schema.setup_consumers 
SET ENABLED = 'YES' 
WHERE NAME LIKE '%statements_summary%';

-- 查询平均执行时间最长的SQL
SELECT DIGEST_TEXT, COUNT_STAR, 
       ROUND(AVG_TIMER_WAIT/1000000000, 2) AS avg_ms,
       ROUND(SUM_TIMER_WAIT/1000000000, 2) AS total_ms
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME = 'production'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

-- 查看索引使用情况(识别未使用索引)
SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, 
       COUNT_READ, COUNT_FETCH, COUNT_INSERT, COUNT_UPDATE
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = 'production'
ORDER BY COUNT_READ DESC;

-- 删除未使用的索引减少写入开销
ALTER TABLE orders DROP INDEX idx_unused;

SQL查询优化是一个持续过程。国产数据库如OceanBase、TiDB在执行计划展示和索引行为上与MySQL存在差异,但核心方法论相通。建立慢查询巡检SOP,每周分析Top 10慢SQL并跟踪优化效果,是保障数据库稳定运行的基本功。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-man-cha-xun-you-hua-shi-zhan-zhi-xing-ji-hua-fen-xi/

(0)
小编小编
上一篇 2026年8月6日
下一篇 2026年8月6日

相关推荐