MySQL性能调优实战:慢查询定位与SQL查询优化全流程

慢查询定位是MySQL性能调优的第一步

数据库运维中,MySQL性能调优的核心工作不是调整参数,而是定位和消除慢查询。一条执行时间超过1秒的SQL,在高并发场景下可能拖垮整个数据库实例。系统化的慢查询定位流程,是数据库性能治理的基石。

慢查询日志的配置与分析

开启慢查询日志是第一步。线上环境建议的配置参数:

# my.cnf配置
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
log_queries_not_using_indexes = 1
min_examined_row_limit = 100

long_query_time设为0.5秒而非默认的10秒,是因为0.5秒以上的查询在OLTP系统中已经算异常。配合log_queries_not_using_indexes记录所有未使用索引的查询,即使它执行很快——这类查询在数据量增长后会突然变慢。

分析慢查询日志,pt-query-digestmysqldumpslow强大得多:

pt-query-digest /var/log/mysql/slow.log --since '24h' --limit 10

输出按总执行时间排序的Top 10慢查询,包含执行次数、平均时间、扫描行数等关键指标。关注”执行次数多但单次不慢”的查询,它们的总资源消耗可能比”偶尔执行一次但极慢”的查询更高。

EXPLAIN执行计划深度解读

拿到慢SQL后,用EXPLAIN查看执行计划。需要逐字段分析的关键指标:

type列:从好到差依次是system > const > eq_ref > ref > range > index > ALL。ALL表示全表扫描,必须优化。index表示全索引扫描,虽然比ALL好,但依然是O(n)操作。

key列:实际使用的索引。如果是NULL,说明没有使用任何索引,需要检查WHERE条件是否命中索引。

rows列:预估扫描行数。如果rows值远大于实际返回行数,说明索引选择性差,需要优化索引设计或添加更精确的复合索引。

Extra列:重点关注Using filesort(额外排序操作)和Using temporary(临时表),这两个都意味着查询需要额外的内存或磁盘操作,性能开销大。

SQL查询优化的六个常见手法

手法一:复合索引的最左前缀原则。索引(a,b,c)可以覆盖a(a,b)(a,b,c)三种查询条件,但无法覆盖(b,c)查询。复合索引的字段顺序应按选择性从高到低排列。

手法二:避免索引列上做函数运算。WHERE YEAR(create_time) = 2026会阻止索引使用,改写为WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01'

手法三:覆盖索引减少回表。如果SELECT的字段全部包含在索引中,InnoDB可以直接从索引树返回数据,不需要回表查主键索引。

手法四:分页查询优化。LIMIT 100000, 10会扫描100010行然后丢弃前100000行,改用游标分页:WHERE id > 100000 LIMIT 10

-- 传统分页(慢)
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;

-- 游标分页(快)
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 10;

手法五:子查询改写为JOIN。MySQL对子查询的优化能力有限,很多场景下IN (SELECT ...)的性能远不如INNER JOIN

手法六:UNION ALL替代UNION。UNION会对结果去重,需要额外排序操作。如果确定各查询结果不会重复,用UNION ALL跳过去重步骤。

数据库高可用架构中的读写分离配置

慢查询优化到极限后,数据库高可用架构的读写分离是下一步。MySQL的主从复制延迟是读写分离最大的坑——写入主库后立即从从库读取,可能读到旧数据。解决方案:关键业务强路由到主库读,非关键业务接受短暂延迟。代理层(如ProxySQL)可以根据SQL特征自动路由:

-- ProxySQL路由规则示例
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (1, 1, '^SELECT.*FOR UPDATE', 0, 1),
       (2, 1, '^SELECT', 1, 1);

SQL查询优化是持续工程

MySQL性能调优不是一次性的工作。数据量增长、业务逻辑变化、查询模式演变都会让之前优化过的SQL重新变慢。建立周期性的慢查询巡检流程,每周review一次Top 20慢查询,把新出现的慢查询消灭在萌芽期,比等到用户投诉后再紧急优化有效得多。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-xing-neng-diao-you-shi-zhan-man-cha-xun-ding-wei-yu/

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

相关推荐

MySQL性能调优实战:慢查询定位与索引优化全流程解析

MySQL慢查询诊断的标准流程

数据库运维中,MySQL性能调优的第一步永远是定位慢查询。靠经验猜测”哪个SQL有问题”效率极低且容易遗漏。标准做法是开启慢查询日志,用事实数据驱动优化决策。

开启慢查询日志并设置合理阈值:

-- my.cnf 配置
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
log_queries_not_using_indexes = 1
min_examined_row_limit = 100

生产环境建议 long_query_time 设为 0.5-1秒。设得太低(如0.1秒)会产生大量日志影响I/O,设得太高会漏掉累积影响的慢查询。

pt-query-digest分析慢查询日志

Percona Toolkit的pt-query-digest是分析慢查询日志的最佳工具:

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

# 只看Top 10慢查询
pt-query-digest --limit 10 /var/log/mysql/slow.log

输出报告中重点关注三个指标:

Query Time的P95和P99值——平均值可能被少量极端值拉偏,P95/P99更能反映真实体验。

Rows Examined / Rows Sent 比值——比值超过100说明扫描了大量无关行,索引缺失或失效的典型信号。

Exec Count x Avg Time——单次慢但执行频率高的SQL,优化收益最大。

EXPLAIN执行计划深度解读

拿到慢SQL后,用EXPLAIN分析执行计划。以下是高频出现的问题类型及对应的索引优化方案:

问题1:type列显示ALL(全表扫描)

EXPLAIN SELECT * FROM orders WHERE user_id = 12345 AND status = "paid";
-- 结果: type=ALL, rows=2000000

-- 优化: 创建联合索引
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
-- 优化后: type=ref, rows=50

问题2:type=ref但Extra出现Using filesort

EXPLAIN SELECT * FROM orders
WHERE user_id = 12345
ORDER BY created_at DESC LIMIT 20;
-- Extra: Using where; Using filesort

-- 优化: 联合索引覆盖排序字段
ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at);
-- 优化后: Extra: Using where; Backward index scan

问题3:索引存在但未命中(隐式类型转换)

-- user_id是VARCHAR类型,传入整数导致隐式转换
SELECT * FROM users WHERE user_id = 12345;  -- 全表扫描!

-- 正确写法
SELECT * FROM users WHERE user_id = "12345";  -- 命中索引

SQL查询优化:减少回表的实战技巧

回表(Bookmark Lookup)是InnoDB性能损耗的主要来源之一。当查询列不完全包含在索引中时,需要回主键索引取数据,每次回表都是一次随机I/O。

覆盖索引是消除回表最直接的手段:

-- 回表查询
SELECT order_no, amount FROM orders WHERE user_id = 12345 AND status = "paid";

-- 创建覆盖索引
ALTER TABLE orders ADD INDEX idx_cover (user_id, status, order_no, amount);

-- EXPLAIN显示 Extra: Using index (不再回表)

另一个减少回表的有效策略是延迟关联(Deferred Join):

-- 原始查询:扫描10万行回表
SELECT * FROM orders
WHERE status = "pending"
ORDER BY created_at LIMIT 50000, 20;

-- 延迟关联:先在索引中定位20条主键,再回表取数据
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders WHERE status = "pending"
    ORDER BY created_at LIMIT 50000, 20
) AS tmp ON o.id = tmp.id;

子查询只在索引中扫描,避免了大量无效回表,深分页场景性能提升可达10-50倍。

分库分表方案的SQL优化适配

分库分表后,跨分片查询和排序是最大挑战:

跨分片排序:在应用层合并排序。每个分片返回Top N,应用层归并取全局Top N:

def merge_sort_queries(shard_results, n):
    import heapq
    heap = []
    for shard_idx, results in enumerate(shard_results):
        if results:
            heapq.heappush(heap, (results[0], shard_idx, 0))
    merged = []
    while heap and len(merged) < n:
        val, shard_idx, pos = heapq.heappop(heap)
        merged.append(val)
        if pos + 1 < len(shard_results[shard_idx]):
            next_val = shard_results[shard_idx][pos + 1]
            heapq.heappush(heap, (next_val, shard_idx, pos + 1))
    return merged

避免跨分片COUNT:维护汇总表。写分片数据时,通过消息中间件异步更新汇总计数器,查询时直接读取汇总表。

数据库高可用架构下的调优注意事项

主从架构中,慢查询优化需区分主库和从库的不同瓶颈:

主库写密集场景关注锁等待和死锁:

SELECT * FROM information_schema.INNODB_LOCK_WAITS
SHOW ENGINE INNODB STATUS

从库读密集场景关注复制延迟:

SHOW SLAVE STATUS
pt-query-digest /var/log/mysql/slow-replica.log

MySQL性能调优不是一次性工作,而是持续的过程。每次变更索引后,用pt-query-digest重新生成报告,与变更前对比,确认优化有效。记录每次调优的操作、原因、效果,逐步积累成本库的调优知识,才能形成可复用的SQL查询优化方法论。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-xing-neng-diao-you-shi-zhan-man-cha-xun-ding-wei-yu/

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

相关推荐

MySQL性能调优实战:慢查询定位与索引优化的系统化方法论

MySQL慢查询诊断为什么不能只看慢查询日志

慢查询日志记录的是执行时间超阈值的语句,但生产环境中有大量”不慢但低效”的查询——单次执行100ms不触发告警,QPS到500时就是灾难。完整的性能诊断体系需要三个数据源:慢查询日志(捕捉明显慢的)、performance_schema(捕捉高频低效的)、pt-query-digest(聚合分析模式)。

慢查询日志的精细配置与分析

默认的long_query_time=10秒对生产环境毫无意义,建议设0.1秒甚至更低:

# my.cnf
slow_query_log = ON
long_query_time = 0.1
log_queries_not_using_indexes = ON
min_examined_row_limit = 100
slow_query_log_file = /var/log/mysql/slow.log

# 动态修改(不重启)
SET GLOBAL long_query_time = 0.1;
SET GLOBAL log_queries_not_using_indexes = ON;

log_queries_not_using_indexes会记录所有全表扫描的语句,即使执行时间未超阈值。min_examined_row_limit=100过滤掉扫描行数少于100的轻量查询,避免日志膨胀。

用pt-query-digest做聚合分析:

pt-query-digest /var/log/mysql/slow.log \
  --since "1 hour ago" \
  --limit 20 \
  --order-by Query_time:sum

# 输出按总执行时间排序的Top 20语句
# 重点关注:
# - Query_time总和占比高的(资源消耗大户)
# - Rows_examined/Rows_sent比值大的(扫描效率低)
# - Execute count极高的(高频查询)

EXPLAIN执行计划的深度解读

拿到慢SQL后用EXPLAIN分析执行计划,关注五个关键字段:

EXPLAIN SELECT o.*, u.name FROM orders o 
JOIN users u ON o.user_id = u.id 
WHERE o.status = 'pending' AND o.created_at > '2026-07-01';

+----+-------------+-------+------------+------+---------------+------------+
| id | select_type | table | type       | key  | rows          | Extra      |
+----+-------------+-------+------------+------+---------------+------------+
|  1 | SIMPLE      | o     | ref        | idx_status_created | 5000 | Using where |
|  1 | SIMPLE      | u     | eq_ref     | PRIMARY            |    1 | NULL        |
+----+-------------+-------+------------+------+---------------+------------+

type字段的效率排序:system > const > eq_ref > ref > range > index > ALL。出现ALL(全表扫描)必须优化。Extra中出现Using filesort或Using temporary要重点排查——filesort代表额外排序,temporary代表临时表,两者都是性能杀手。

索引设计的黄金法则与常见反模式

法则1:最左前缀匹配决定索引可用性

联合索引(a, b, c)的匹配规则:查询条件用a、(a,b)、(a,b,c)都能命中,但(b,c)和(b)无法命中。这是索引失效的第一大原因。

-- 索引:idx_status_created (status, created_at)
-- 命中索引
SELECT * FROM orders WHERE status = 'pending' AND created_at > '2026-07-01';
-- 无法命中(跳过了最左列status)
SELECT * FROM orders WHERE created_at > '2026-07-01';

法则2:索引列上的函数和隐式转换导致失效

-- 索引失效:对列使用了函数
SELECT * FROM orders WHERE DATE(created_at) = '2026-07-30';
-- 优化:改写为范围查询
SELECT * FROM orders WHERE created_at >= '2026-07-30' AND created_at < '2026-07-31';

-- 索引失效:隐式类型转换(user_id是varchar,传入int)
SELECT * FROM orders WHERE user_id = 12345;
-- 优化:类型匹配
SELECT * FROM orders WHERE user_id = '12345';

法则3:覆盖索引减少回表

-- 需要回表:SELECT的列不在索引中
SELECT * FROM orders WHERE status = 'pending';
-- 覆盖索引:所有需要的列都在索引中,Extra显示Using index
SELECT id, status, created_at FROM orders WHERE status = 'pending';
-- 对应索引:idx_cover (status, created_at, id)

Online DDL与索引变更的安全操作

大表加索引会锁表,MySQL 8.0的Online DDL允许在ALGORITHM=INPLACE下并行DML:

-- 安全的在线加索引方式
ALTER TABLE orders 
  ADD INDEX idx_status_created (status, created_at),
  ALGORITHM=INPLACE, 
  LOCK=NONE;

-- 监控DDL进度
SHOW ALTER TABLE orders;  -- MySQL 8.0+

-- 超大表(千万级以上)用pt-online-schema-change
pt-online-schema-change \
  --alter "ADD INDEX idx_status_created (status, created_at)" \
  D=production,t=orders \
  --max-load Threads_running=50 \
  --critical-load Threads_running=100 \
  --chunk-size 1000 \
  --execute

pt-online-schema-change通过创建影子表+增量同步+rename切换的方式,在不停服的情况下完成索引变更。--max-load参数控制在主库负载高时自动暂停拷贝,避免影响在线业务。

性能调优的持续化监控体系

MySQL性能优化不是一次性工作。部署Prometheus + mysqld_exporter采集核心指标:QPS、慢查询数、连接池使用率、InnoDB缓冲池命中率。缓冲池命中率低于95%是加内存的信号,慢查询数突增是索引失效或数据倾斜的信号。建立每周巡检制度,用pt-query-digest对比前后周Top 10查询的执行计划变化,在问题恶化前拦截。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-xing-neng-diao-you-shi-zhan-man-cha-xun-ding-wei-yu/

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

相关推荐

MySQL性能调优实战:慢查询定位与索引优化全流程指南

MySQL性能调优是数据库运维中最核心的技能。线上系统随着数据量增长,慢查询逐渐成为性能瓶颈。本文系统讲解慢查询定位、EXPLAIN执行计划分析、索引优化策略和SQL改写技巧,提供一套可复用的调优流程。适用于MySQL 8.0+环境。

慢查询定位:开启Slow Query Log与pt-query-digest分析

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

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

-- 动态开启慢查询日志(运行时生效,重启失效)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
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 = 1
-- log_queries_not_using_indexes = 1

-- 查看慢查询数量
SHOW GLOBAL STATUS LIKE 'Slow_queries';

收集一段时间后,使用Percona Toolkit的pt-query-digest分析慢查询日志,按执行频次和总耗时排序,快速定位TOP N问题SQL:

# 分析慢查询日志,生成报告
pt-query-digest /var/log/mysql/slow.log > slow_report.txt

# 只分析最近1小时的慢查询
pt-query-digest --since 1h /var/log/mysql/slow.log

# 按数据库过滤
pt-query-digest --filter '$event->{db} eq "production"' /var/log/mysql/slow.log

# 输出示例(重点关注部分):
# Profile
# Rank Query ID                     Response time  Calls  R/Call  V/M
# ==== ============================ ============== ====== ======= =====
#    1 0x1E8A3F5B2C7D4A6B  152.3400 45.2%    3421 0.0445  0.08
#    2 0x7B2C4D5E6F1A3B8C   78.9200 23.4%     156 0.5062  0.15
#    3 0x3A5B6C7D8E9F0A1B   35.6700 10.6%    8934 0.0040  0.02
#
# Query 1: 按总响应时间排第一,调优优先级最高

EXPLAIN执行计划深度解读:type字段与Extra字段分析

拿到问题SQL后,用EXPLAIN分析执行计划。重点关注type、key、rows和Extra四个字段:

-- 分析单条SQL执行计划
EXPLAIN SELECT * FROM orders
WHERE user_id = 10086 AND status = 'PAID'
ORDER BY create_time DESC
LIMIT 20;

-- 查看详细执行计划(JSON格式)
EXPLAIN FORMAT=JSON SELECT * FROM orders
WHERE user_id = 10086 AND status = 'PAID'
ORDER BY create_time DESC LIMIT 20;

-- 查看实际执行成本(MySQL 8.0+)
EXPLAIN ANALYZE SELECT * FROM orders
WHERE user_id = 10086 AND status = 'PAID'
ORDER BY create_time DESC LIMIT 20;

type字段的性能排序(从好到差):

-- system > const > eq_ref > ref > range > index > ALL
--
-- system: 表只有一行(系统表)
-- const:  通过主键或唯一索引等值查询,最多匹配一行
-- eq_ref: JOIN时被驱动表使用主键或唯一索引
-- ref:    通过非唯一索引等值查询
-- range:  索引范围扫描(BETWEEN, >, <, IN)
-- index:  全索引扫描(扫描整棵索引树)
-- ALL:    全表扫描(最差,必须优化)
--
-- Extra关键字段:
-- Using index:        覆盖索引,不回表(最优)
-- Using where:        使用WHERE条件过滤
-- Using temporary:    使用临时表(需优化)
-- Using filesort:     文件排序(需优化)
-- Using join buffer:  JOIN使用BNL算法(需优化)

索引优化策略:联合索引设计与最左前缀原则

索引设计是MySQL调优的核心。联合索引的字段顺序决定其使用效率。遵循最左前缀原则,将区分度高的字段放在前面:

-- 问题SQL: 多条件查询 + 排序
SELECT * FROM orders
WHERE user_id = 10086 AND status = 'PAID'
ORDER BY create_time DESC
LIMIT 20;

-- 错误索引方案1: 单列索引
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_status ON orders(status);
-- EXPLAIN: type=ref, key=idx_user_id, rows=15600
-- Extra=Using where; Using filesort

-- 错误索引方案2: 联合索引顺序不当
CREATE INDEX idx_status_user ON orders(status, user_id);
-- status区分度低,索引效率差

-- 正确索引方案: 联合索引,区分度高的字段在前,排序字段在后
CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time);
-- EXPLAIN: type=ref, key=idx_user_status_time, rows=35
-- Extra=Using index condition
-- WHERE条件是等值查询,create_time用于ORDER BY
-- 索引天然有序,避免filesort

索引设计的关键经验:等值条件字段在前,范围查询字段在后,排序字段紧跟条件字段。避免索引字段上使用函数或类型转换,否则索引失效:

-- 索引失效案例1: 函数操作
CREATE INDEX idx_create_time ON orders(create_time);
-- 失效: 对索引列使用函数
SELECT * FROM orders WHERE DATE(create_time) = '2026-07-23';
-- 正确: 改为范围查询
SELECT * FROM orders
WHERE create_time >= '2026-07-23 00:00:00'
  AND create_time <  '2026-07-24 00:00:00';

-- 索引失效案例2: 隐式类型转换
-- user_id字段类型为VARCHAR,但查询传入整数
SELECT * FROM orders WHERE user_id = 10086;
-- MySQL会将user_id转为数字比较,导致全表扫描
-- 正确: 传入字符串
SELECT * FROM orders WHERE user_id = '10086';

-- 索引失效案例3: LIKE前缀通配符
SELECT * FROM products WHERE name LIKE '%手机%';
-- 正确: 前缀匹配才能走索引
SELECT * FROM products WHERE name LIKE '手机%';
-- 或使用全文索引
ALTER TABLE products ADD FULLTEXT INDEX ft_name(name);
SELECT * FROM products WHERE MATCH(name) AGAINST('手机');

覆盖索引设计:消除回表提升查询性能

当查询所需的所有字段都包含在索引中时,MySQL直接从索引树返回数据,无需回表读取数据行。这种技术称为覆盖索引:

-- 场景: 订单列表页只查询ID、状态、金额
SELECT id, status, amount FROM orders
WHERE user_id = 10086 ORDER BY create_time DESC LIMIT 20;

-- 不带amount的联合索引需要回表取amount
CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time);
-- Extra: Using index condition(需要回表)

-- 覆盖索引: 将查询字段全部纳入索引
CREATE INDEX idx_user_time_status_amount
ON orders(user_id, create_time, status, amount);
-- Extra: Using index(覆盖索引,不回表)
-- 性能提升: IO从随机读变为顺序读索引,减少50%-80%的IO操作

-- 使用 Invisible Index 灰度测试索引效果
ALTER TABLE orders ALTER INDEX idx_user_time_status_amount INVISIBLE;
-- 确认查询性能后再设为VISIBLE
ALTER TABLE orders ALTER INDEX idx_user_time_status_amount VISIBLE;

覆盖索引会增加索引大小和写入开销,适用于读多写少的高频查询。在数据备份恢复场景中,覆盖索引也能加速恢复过程中的数据校验速度。

SQL改写技巧:子查询优化与分页查询改造

部分SQL写法会导致MySQL选择低效执行计划。通过改写SQL可以引导优化器选择更优路径:

-- 优化1: 子查询改JOIN
-- 低效: 相关子查询,每行执行一次子查询
SELECT * FROM orders o
WHERE o.user_id IN (
    SELECT id FROM users WHERE vip_level >= 5
);
-- 高效: 改为INNER JOIN
SELECT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.vip_level >= 5;

-- 优化2: 分页查询优化(深分页问题)
-- 低效: OFFSET越大,扫描的无效行越多
SELECT * FROM orders ORDER BY id DESC LIMIT 100000, 20;
-- 高效: 使用游标分页(记住上一页最后一条记录的ID)
SELECT * FROM orders WHERE id < 100000
ORDER BY id DESC LIMIT 20;

-- 优化3: 大批量UPDATE分批执行
-- 低效: 单条大事务,长时间锁表
UPDATE orders SET status = 'EXPIRED'
WHERE status = 'PENDING' AND create_time < '2026-01-01';
-- 高效: 分批更新,每批1000条,每批之间SLEEP 0.1秒

-- 优化4: COUNT优化
-- 低效: COUNT(*)扫描全表或索引
SELECT COUNT(*) FROM orders WHERE status = 'PENDING';
-- 高效: 维护汇总表,实时更新计数
CREATE TABLE order_stats (
    status VARCHAR(20) PRIMARY KEY,
    cnt INT NOT NULL DEFAULT 0
);
INSERT INTO order_stats(status, cnt)
VALUES('PENDING', 1)
ON DUPLICATE KEY UPDATE cnt = cnt + 1;
SELECT cnt FROM order_stats WHERE status = 'PENDING';

索引监控与维护:碎片整理与使用率统计

索引上线后需要持续监控其使用情况,清理无用索引,定期维护索引碎片:

-- 查看索引使用情况(基于performance_schema)
SELECT
    object_schema AS db,
    object_name AS table_name,
    index_name,
    count_read,
    sys.format_time(sum_timer_wait) AS total_latency
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE object_schema = 'production'
ORDER BY count_read ASC;

-- 查找从未使用的索引(清理候选)
SELECT object_schema, object_name, index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
  AND count_read = 0
  AND object_schema NOT IN ('mysql', 'performance_schema', 'sys')
ORDER BY object_schema, object_name;

-- 查看索引碎片率
SELECT
    table_name,
    ROUND(data_length/1024/1024, 2) AS data_mb,
    ROUND(index_length/1024/1024, 2) AS index_mb,
    ROUND(data_free/(data_length+index_length)*100, 2) AS frag_pct
FROM information_schema.tables
WHERE table_schema = 'production'
  AND data_free > 0
ORDER BY frag_pct DESC;

-- 碎片率超过30%的表需要OPTIMIZE
-- OPTIMIZE TABLE会锁表,线上使用gh-ost或pt-online-schema-change
OPTIMIZE TABLE orders;

-- MySQL 8.0使用不可见索引安全测试删除索引的影响
ALTER TABLE orders ALTER INDEX idx_old_index INVISIBLE;
-- 观察一段时间,确认无影响后删除
ALTER TABLE orders DROP INDEX idx_old_index;

索引优化是一个持续过程。建议建立定期的慢查询巡检机制,每周分析一次pt-query-digest报告,对TOP 10慢查询逐一优化。同时通过performance_schema监控索引使用率,及时清理无用索引减少写入开销。数据库高可用架构层面,读写分离将分析查询分流到只读副本,进一步降低主库压力。分库分表方案在单表数据量超过千万行时也需要纳入考虑,通过ShardingSphere等中间件实现透明化分片路由。Redis缓存策略配合数据库使用,热点数据走缓存,减轻数据库读压力。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-xing-neng-diao-you-shi-zhan-man-cha-xun-ding-wei-yu/

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

相关推荐