当前位置: 首页 > news >正文

慢sql处理流程和常见案例

 思维导图:

 

  在 MySQL 数据库管理中,慢查询是影响系统性能的常见痛点。随着 MySQL 8 版本的普及,其新增特性(如 CTE、隐藏索引、JSON 格式执行计划等)为慢查询优化提供了更强大的工具。本文结合 MySQL 8 的特性,通过代码示例详解慢查询的定位、分析与优化全流程,从而提高数据库的性能。

一、开启慢查询日志:精准捕获性能瓶颈

1. 配置文件永久开启(推荐生产环境)

修改 MySQL 8 的配置文件my.cnf(Linux)或my.ini(Windows),添加以下核心配置:修改后需重启 MySQL 服务使配置生效。

[mysqld]
slow_query_log = 1                # 启用慢查询日志(8.0+默认关闭)
slow_query_log_file = /var/log/mysql/mysql-slow.log  # 日志文件路径
long_query_time = 1.0             # 慢查询阈值(超过1秒记录,建议根据业务调整)
log_queries_not_using_indexes = 1 # 记录未使用索引的查询(关键优化线索)
log_slow_admin_statements = 1     # 记录慢管理语句(如ALTER TABLE,8.0新增)

配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息/var/lib/mysql/localhost-slow.log。如图所示:

 

2. 动态配置(临时调试,无需重启)

通过 SQL 命令实时开启慢日志(需SUPER权限):

SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- 阈值设为1秒
SET GLOBAL log_queries_not_using_indexes = ON;

3. 日志分析:核心指标与工具

关键指标解读:

  • Query_time:查询执行时间(精确到微秒),直接反映慢查询严重程度。
  • Rows_examined:扫描的行数,若远大于Rows_sent(返回行数),说明存在大量无效扫描。
  • Full_scan:标记是否全表扫描(Yes表示未使用索引)。

分析工具:
原生工具:使用mysqldumpslow过滤日志(语法与 5.5 兼容):
 

# 按耗时排序,获取最慢的5条查询 
mysqldumpslow -s t -t 5 /var/log/mysql/mysql-slow.log # 按扫描行数排序,定位扫描行数最多的查询 
mysqldumpslow -s r -t 5 /var/log/mysql/mysql-slow.log

可视化工具:推荐使用pt-query-digest(Percona Toolkit 组件),生成包含执行频率、平均耗时、索引使用情况的详细报告:

pt-query-digest /var/log/mysql/mysql-slow.log > slow_query_analysis.txt

示例日志片段

# Time: 2025-05-05T15:00:00+08:00
# User@Host: app_user[app_user] @ 192.168.1.100 []
# Query_time: 2.865912  Lock_time: 0.000045  Rows_sent: 20  Rows_examined: 500000
# EXPLAIN for: SELECT * FROM orders WHERE status = 'processing' LIMIT 20;

 问题:扫描 50 万行仅返回 20 行,status字段未使用索引,触发全表扫描。

二、用 EXPLAIN ANALYZE 深度分析执行计划(MySQL 8 增强特性)

MySQL 8 引入EXPLAIN ANALYZE命令,可获取更精确的执行统计信息(需开启optimizer_switch中的derived_merge=off以避免优化器合并子查询)。核心关注字段:

1. type(连接类型,效率优先级)

类型说明优化目标
system单表且仅有一行数据(特殊const理想状态,无需优化
const主键 / 唯一索引精准匹配优先通过主键 / 唯一索引查询
range索引范围查询(如BETWEEN/IN合理使用索引,无需强制优化
ALL全表扫描(必须优化!)添加索引或改写查询条件

2. key与key_len

  • key=NULL:未使用索引,需检查WHERE条件是否触发索引失效。
  • key_len:索引使用的字节长度,反映是否完全使用联合索引(如(user_id, order_date)的key_len=8表示仅使用user_id列)。

3. Extra(关键优化信号)

  • Using filesort:文件排序(需通过索引覆盖ORDER BY字段)。
  • Using temporary:临时表(分组 / 排序时产生,尽量通过索引避免)。
  • Using index:覆盖索引(理想状态,无需回表)。


案例:

全表扫描优化(MySQL 8 专属 JSON 格式输出)原查询(未加索引): 

EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'user@example.com';

JSON 执行计划片段(简化后):

{"query_block": {"table": {"table_name": "users","access_type": "ALL",  -- 全表扫描"rows_examined_per_scan": 100000  -- 扫描10万行}}
}

优化后(添加索引并使用覆盖索引)

ALTER TABLE users ADD INDEX idx_email_cover (email, name, phone);  -- 覆盖索引
EXPLAIN ANALYZE SELECT name, phone FROM users WHERE email = 'user@example.com';

执行计划

{"query_block": {"table": {"table_name": "users","access_type": "ref",  -- 索引引用"rows_examined_per_scan": 1,"using_index": true  -- 覆盖索引,无需回表}}
}

效果:扫描行数从 10 万降至 1,查询时间从 2.5 秒缩短至 0.01 秒。

三、索引优化:MySQL 8 的新特性与经典规则

1. 避开索引失效的 5 大 “陷阱”

陷阱 1:Like'%前缀'导致索引失效

错误写法(全表扫描):

SELECT * FROM products WHERE name LIKE '%笔记本';  -- 以%开头,索引失效

优化(前缀匹配,索引有效):

SELECT * FROM products WHERE name LIKE '笔记本%';  -- 匹配“笔记本”开头的字符串

陷阱 2:OR条件混合无索引列

错误写法(索引失效):

SELECT * FROM orders WHERE user_id = 100 OR address LIKE '%上海';  -- user_id有索引,address无索引

优化方案

  • address添加索引(适合高频查询):
ALTER TABLE orders ADD INDEX idx_address (address(20));  -- 前缀索引(MySQL 8支持)

  • 改用UNION ALL(减少临时表开销):
SELECT * FROM orders WHERE user_id = 100
UNION ALL
SELECT * FROM orders WHERE address LIKE '%上海';

陷阱 3:多列索引未遵循 “最左匹配”

索引定义ALTER TABLE sales ADD INDEX idx_date_prod (order_date, product_id);
有效查询(使用索引):

-- 仅用第一列(范围查询)
SELECT * FROM sales WHERE order_date > '2025-01-01';
-- 用前两列(精准匹配)
SELECT * FROM sales WHERE order_date = '2025-01-01' AND product_id = 101;

无效查询(跳过第一列,索引失效):

SELECT * FROM sales WHERE product_id = 101;  -- 仅用第二列,不使用索引

2. MySQL 8 专属优化技巧

(1)隐藏索引(测试索引有效性)

通过INVISIBLE关键字临时隐藏索引,测试其对查询的影响:

ALTER TABLE users ALTER INDEX idx_email INVISIBLE;  -- 隐藏索引
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';  -- 观察是否走全表扫描
ALTER TABLE users ALTER INDEX idx_email VISIBLE;  -- 恢复索引

(2)降序索引(优化倒序排序)

MySQL 8 支持降序索引,避免DESC排序时的文件排序:

ALTER TABLE orders ADD INDEX idx_order_date_desc (order_date DESC);
-- 优化后无需文件排序
SELECT * FROM orders ORDER BY order_date DESC LIMIT 10;

四、数据库结构优化:从设计层减少查询压力

1. 拆分大表:分离高频与低频字段

场景user_info表包含 30 个字段,其中resume(简历)和certificate(证书)字段极少使用,导致全表扫描缓慢。

优化步骤

  1. 创建高频字段表user_basic(存放常用字段):
CREATE TABLE user_basic (id BIGINT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50) NOT NULL,email VARCHAR(100) UNIQUE,created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

 

    2.创建低频字段表user_extend通过外键关联):

CREATE TABLE user_extend (id BIGINT PRIMARY KEY,resume TEXT,certificate JSON,  -- MySQL 8支持原生JSON类型FOREIGN KEY (id) REFERENCES user_basic(id)
);

效果:高频查询(如登录、用户列表)仅访问user_basic,扫描行数减少 60%。

2. 中间表优化复杂关联查询

场景:频繁统计 “用户近 30 天订单金额”,需关联usersordersorder_items三张表,执行时间超过 3 秒。


优化方案:创建统计中间表user_order_stats,每日定时同步数据:

CREATE TABLE user_order_stats (user_id BIGINT PRIMARY KEY,total_amount DECIMAL(10, 2),order_count INT,last_update DATE,INDEX idx_last_update (last_update)  -- 按时间查询索引
);-- 替代复杂关联查询(原需3表JOIN)
SELECT total_amount FROM user_order_stats WHERE user_id = 123 AND last_update = CURDATE();

效果:查询时间从 3 秒降至 0.1 秒,消除多表 JOIN 开销。

五、实战技巧:处理高频慢查询场景

1. 深度分页优化(LIMIT offset, size性能问题)

问题LIMIT 100000, 20需扫描 100020 行,丢弃前 100000 行,效率极低。

MySQL 8 优化方案

  • 方案 1:利用覆盖索引减少回表
-- 先获取主键(减少扫描字段)
SELECT id FROM orders ORDER BY create_time LIMIT 100000, 20;
-- 再通过主键批量查询(IN操作比子查询高效)
SELECT * FROM orders WHERE id IN (100001, 100002, ..., 100020);

  • 方案 2:记录上次分页 ID(滚动分页)
-- 基于上次最大ID分页,避免偏移量累积
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 20;

 

2. 分解关联查询:应用层组装结果(适合高并发)

原复杂查询(3 表 JOIN,执行时间 2 秒):

SELECT u.username, o.order_id, p.product_name
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN products p ON o.product_id = p.id
WHERE u.country = 'China' AND o.status = 'paid';

优化步骤

  1. 查询用户 ID 列表:
SELECT id, username FROM users WHERE country = 'China';  -- 单表查询,0.05秒

     2. 查询订单 ID 与产品 ID:

SELECT order_id, product_id FROM orders WHERE user_id IN (1,2,3) AND status = 'paid';  -- 0.1秒

    3.查询产品名称:

SELECT product_id, product_name FROM products WHERE product_id IN (101, 102);  -- 0.03秒

总耗时降至 0.18 秒,相比原查询提升 10 倍以上。

六、MySQL 8 新增特性助力优化

1. CTE(公共表表达式)简化复杂子查询

-- 原嵌套子查询(可读性差,易触发全表扫描)
SELECT * FROM orders WHERE user_id IN (SELECT id FROM users WHERE score > 80);-- 改用CTE(清晰且便于优化器分析)
WITH high_score_users AS (SELECT id FROM users WHERE score > 80
)
SELECT * FROM orders WHERE user_id IN (SELECT id FROM high_score_users);

2. 索引统计信息自动更新(8.0.17+)

通过AUTO_RECALCULATE参数自动更新索引统计信息,避免因统计信息陈旧导致的执行计划偏差:

ALTER TABLE users ALTER INDEX idx_email AUTO_RECALCULATE DEFAULT ON;

七、最佳实践:建立慢查询优化闭环

  1. 定期审计流程

    • 每日通过SHOW GLOBAL STATUS LIKE 'Slow_queries'监控慢查询数量。
    • 每周用pt-query-digest生成报告,重点优化Rows_examined > 5万Query_time > 1的语句。
  2. 索引设计三原则

    • 为高频查询的WHERE/ORDER BY/GROUP BY字段创建索引。
    • 联合索引优先包含过滤性强的字段(如user_idstatus更适合作为第一列)。
    • 使用EXPLAIN ANALYZE JSON验证索引有效性,避免过度索引(超过 5 个索引的表需评估)。
  3. 监控与预警

    • 通过 Prometheus+Grafana 监控com_selectsort_merge_passes(排序合并次数)等指标。
    • 当慢查询数量突然增加 50% 时,触发短信 / 邮件预警,快速定位问题。

总结

MySQL 8 的慢查询优化需要结合版本特性与经典优化理论,从 “日志分析→执行计划诊断→索引优化→结构调整” 四个层面逐层深入。通过EXPLAIN ANALYZE的精准分析、覆盖索引的合理设计、以及大表拆分等架构优化,多数慢查询问题可迎刃而解。记住:优化的核心是让数据库 “少干活”—— 减少扫描行数、避免临时表与文件排序、利用索引覆盖查询。持续迭代优化策略并结合监控体系,才能确保 MySQL 在高并发场景下稳定高效运行。

相关文章:

  • 20250505下载VLC for Android
  • git上常用的12个月份对应的英语单词以及月份英语缩写形式
  • 矩阵快速幂 快速求解递推公式
  • 二重指针和二维数组
  • 力扣119题解
  • 机场围界报警系统的研究与应用
  • 深入理解 CSS Flex 布局:代码实例解析
  • WMS仓库管理系统:Java+Vue,含源码及文档,集成仓储全流程管控,实现库存精准、作业高效、数据透明
  • 苹果公司正在与亚马逊支持的初创公司Anthropic展开合作
  • 【数据结构】第八章:排序
  • 网络编程套接字(一)
  • C语言数据在内存中的存储详解
  • 标题:试验台铁地板:革新之路
  • Untiy基础学习(六)MonoBehaviour基类的简单介绍
  • QT聊天项目DAY08
  • 下载core5compat 模块时,被禁止,显示 - servese replied: Forbbidden. -->换镜像源
  • 文旅行业淡旺季明显,如何做好人力资源规划?​
  • cgi技术初识
  • Python实现自动驾驶中的车道检测算法:从理论到实践
  • Debezium MySqlValueConverters详解
  • 两个灵魂,一支画笔,意大利艺术伴侣的上海灵感之旅
  • 云南禄丰一尾矿干堆场坍塌致5人被埋
  • 特朗普宣布对进口电影征收100%关税
  • 新加坡国会选举投票抽样结果公布,执政党已获超半数议席
  • 南京大屠杀幸存者刘贵祥去世,享年95岁
  • 2025年五一档电影票房破4亿,《水饺皇后》领跑