MySQL慢查询

一、什么是 MySQL 慢查询?

首先要明确一个核心问题:什么样的查询算是 "慢查询"?

MySQL 官方定义为:执行时间超过long_query_time参数设定阈值的 SQL 语句(默认值为 10 秒)。但在实际业务中,这个阈值需要根据场景调整 ------ 比如电商秒杀场景,超过 500ms 的查询就可能影响用户体验,而后台统计报表查询允许 10 秒甚至更久的执行时间。

除此之外,还有一个容易被忽略的 "隐性慢查询":虽然单条语句执行时间未超过阈值,但由于调用频率极高(如每秒执行上千次),累计耗时会严重占用数据库资源,这类查询同样需要重点优化。

二、如何开启慢查询日志?

要处理慢查询,首先得 "抓住" 它 ------ 这就需要开启 MySQL 的慢查询日志(slow query log) 。慢查询日志会记录所有执行时间超过long_query_time的 SQL 语句,包括执行时长、执行时间、锁等待时间等关键信息。

2.1 查看当前慢查询配置

先通过 SQL 命令查看当前数据库的慢查询相关配置,确认是否已开启:

java 复制代码
-- 开启慢查询日志
set global slow_query_log=ON;
-- 设置慢查询阈值为1秒(根据业务调整,建议从1秒开始,逐步缩小范围)
set global long_query_time=1;
-- 开启"未使用索引的查询"记录(帮助发现隐形问题)
set global log_queries_not_using_indexes=ON;
-- 注意:设置后需重新连接数据库才能生效

2.2 临时开启慢查询日志(重启失效)

如果只是临时排查问题,不需要重启 MySQL,可以通过set global命令动态开启:

复制代码
-- 开启慢查询日志
set global slow_query_log = ON;
-- 设置慢查询阈值为1秒(根据业务调整,建议从1秒开始,逐步缩小范围)
set global long_query_time = 1;
-- 开启"未使用索引的查询"记录(帮助发现隐性问题)
set global log_queries_not_using_indexes = ON;
-- 注意:设置后需重新连接数据库才能生效

2.3 永久开启慢查询日志(推荐)

临时配置会在 MySQL 重启后失效,生产环境建议通过修改配置文件my.cnf(Linux)或my.ini(Windows)永久生效:

复制代码
[mysqld]
# 开启慢查询日志
slow_query_log = 1
# 慢查询日志存储路径(需确保MySQL有写入权限)
slow_query_log_file = /var/lib/mysql/mysql-slow.log
# 慢查询时间阈值(单位:秒,支持小数,如0.5表示500ms)
long_query_time = 0.5
# 记录未使用索引的查询(即使执行时间很短)
log_queries_not_using_indexes = 1
# 记录慢查询的同时,排除管理类语句(如OPTIMIZE TABLE)
log_slow_admin_statements = 0

修改后重启 MySQL 服务使配置生效:

复制代码
# Linux重启命令(CentOS/RHEL)
systemctl restart mysqld
# 验证是否生效
show variables like 'slow_query_log';

三、如何分析慢查询日志?

慢查询日志开启后,会不断积累符合条件的 SQL 语句。但直接打开日志文件查看(尤其是大文件)会非常繁琐,推荐使用 MySQL 自带的工具mysqldumpslow或第三方工具pt-query-digest进行分析。

3.1 用 mysqldumpslow 快速分析(自带工具)

mysqldumpslow是 MySQL 默认安装的工具,支持按 "执行次数、执行时间、锁等待时间" 等维度排序,适合快速定位 TOP N 慢查询。

常用命令示例:

复制代码
# 1. 查看帮助文档(了解所有参数)
mysqldumpslow --help

# 2. 统计执行次数最多的10条慢查询
mysqldumpslow -s c -t 10 /var/lib/mysql/mysql-slow.log

# 3. 统计总执行时间最长的10条慢查询
mysqldumpslow -s t -t 10 /var/lib/mysql/mysql-slow.log

# 4. 统计锁等待时间最长的10条慢查询
mysqldumpslow -s l -t 10 /var/lib/mysql/mysql-slow.log

# 5. 过滤出包含"order by"的慢查询,并按执行时间排序
mysqldumpslow -s t -t 10 -g 'order by' /var/lib/mysql/mysql-slow.log

参数说明:

  • -s:排序方式(c = 执行次数,t = 总时间,l = 锁等待时间,r = 返回行数)

  • -t:显示前 N 条记录

  • -g:按关键字过滤(支持正则表达式)

3.2 用 pt-query-digest 深度分析(推荐)

mysqldumpslow功能较简单,无法分析 SQL 的执行频率、平均耗时、占比等细节。生产环境更推荐使用 Percona Toolkit 中的pt-query-digest工具,它能生成更详细的分析报告。

3.2.1 安装 pt-query-digest(Linux)
复制代码
# CentOS/RHEL系统
yum install percona-toolkit -y
# Ubuntu/Debian系统
apt-get install percona-toolkit -y
# 验证安装
pt-query-digest --version
3.2.2 生成深度分析报告
复制代码
# 1. 分析慢查询日志,输出到屏幕
pt-query-digest /var/lib/mysql/mysql-slow.log

# 2. 分析慢查询日志,输出到文件(方便后续查看)
pt-query-digest /var/lib/mysql/mysql-slow.log > slow_query_analysis.txt

# 3. 分析最近1小时的慢查询(避免全量日志过大)
pt-query-digest --since=1h /var/lib/mysql/mysql-slow.log

3.2.3 报告核心信息解读

pt-query-digest的报告分为 3 个关键部分:

  1. 总体统计:日志时间范围、总查询数、慢查询数、总执行时间等;

  2. 慢查询排名:按 "总执行时间占比" 排序,展示每条 SQL 的执行次数、平均耗时、锁等待时间等;

  3. SQL 详情:每条慢查询的完整语句、执行计划(explain 结果)、涉及的表和索引等。

例如,报告中可能会出现这样的条目:

复制代码
# Query 1: 0.00 QPS, 0.02x concurrency, ID 0xABCDEF123456 at byte 12345
# Scores: V/M = 10.0
# Time range: 2024-05-01 10:00:00 to 10:30:00
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count          5      10       1       1       1       1       0       1
# Exec time     80      20       2       2       2       2       0       2
# Lock time      0       0       0       0       0       0       0       0
# Rows sent      1      10       1       1       1       1       0       1
# Rows examine  99   10000    1000    1000    1000    1000       0    1000
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms
#  10ms
# 100ms
#    1s  ##########
#  10s+
# String:
# Hosts        192.168.1.100 (10)
# Users        app_user (10)
# Databases    test_db (10)
# Tables       order_info (10)
# Indexes      NULL
# SQL hash     0x123456789ABCDEF
# SQL:
select * from order_info where user_id = 123 and create_time > '2024-05-01';

从上述报告可看出:

  • 这条 SQL 执行了 10 次,总执行时间占比 80%(核心慢查询);
  • 平均执行时间 2 秒,每次扫描 10000 行数据,但只返回 1 行(说明未使用索引,存在全表扫描);
  • 涉及表order_info,过滤条件是user_idcreate_time

四、慢查询优化实战:从 SQL 到索引

分析出慢查询后,优化的核心思路是减少 "数据扫描量" 和 "执行步骤" ,常见手段包括 "优化 SQL 语句" 和 "优化索引",两者通常需要结合进行。

4.1 先看执行计划:explain 命令

优化前必须先通过explain命令查看 SQL 的执行计划,了解 MySQL 是如何处理这条语句的(是否全表扫描、是否使用索引、连接方式等)。

例如,对上述慢查询执行explain

复制代码
explain select * from order_info where user_id = 123 and create_time > '2024-05-01';

执行结果可能如下(关键列解读):

id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE order_info ALL NULL NULL NULL NULL 10000 Using where

关键列含义:

  • type:连接类型,ALL表示全表扫描 (性能最差),理想值是range(范围查询)或ref(非唯一索引查找);

  • possible_keys:可能使用的索引(NULL 表示无可用索引);

  • key:实际使用的索引(NULL 表示未使用索引);

  • rows:MySQL 预估需要扫描的行数(数值越大,性能越差);

  • Extra:额外信息,Using where表示需要在内存中过滤数据(无索引时常见)。

4.2 优化手段 1:添加合适的索引

上述案例中,SQL 的过滤条件是user_id = 常量create_time > 常量,符合 "最左前缀原则 ",适合创建联合索引(user_id, create_time)

创建索引并验证:
复制代码
# 创建联合索引
create index idx_order_user_create on order_info(user_id, create_time);

# 再次查看执行计划
explain select * from order_info where user_id = 123 and create_time > '2024-05-01';

优化后的执行计划可能如下:

id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE order_info range idx_order_user_create idx_order_user_create 10 NULL 10 Using where

可以看到:

  • typeALL变为range(范围查询,性能大幅提升);

  • key显示使用了新创建的联合索引;

  • rows从 10000 变为 10(扫描行数减少 99.9%)。

此时再执行这条 SQL,执行时间通常会从 2 秒降至 10ms 以内。

索引优化注意事项:
  1. 避免过度索引:索引会加速查询,但会减慢插入 / 更新 / 删除操作(需维护索引结构),一张表的索引建议不超过 5 个;
  2. 优先联合索引:当 SQL 有多个过滤条件时,联合索引比单字段索引更高效(需符合最左前缀原则);
  3. 避免 "选择性差" 的索引:如性别(只有男 / 女)、状态(只有 0/1)等字段,索引过滤效果差,反而会增加开销。

4.3 优化手段 2:重构 SQL 语句

有些时候,即使有索引,不合理的 SQL 写法也会导致慢查询,常见问题及优化方案如下:

问题 1:使用select *获取所有字段

select *会导致 MySQL 读取所有字段的数据,尤其是包含TEXT/BLOB等大字段时,会增加 IO 开销。

优化方案:只查询需要的字段(按需取值)

复制代码
-- 优化前
select * from order_info where user_id = 123;
-- 优化后(只查询订单ID、金额、创建时间)
select order_id, amount, create_time from order_info where user_id = 123;
问题 2:使用or连接多个条件(无索引时)

or会导致 MySQL 放弃索引,进行全表扫描(除非所有or条件的字段都有索引)。

优化方案 :用union all替代or(需确保字段有索引)。

复制代码
-- 优化前(无索引时全表扫描)
select * from order_info where user_id = 123 or order_id = 456;
-- 优化后(若user_id和order_id有索引,会走索引查询)
select * from order_info where user_id = 123
union all
select * from order_info where order_id = 456;
问题 3:like%开头(如%abc

like '%abc'会导致索引失效,进行全表扫描;而abc%可以正常使用索引。

优化方案:尽量避免前缀模糊查询,若业务必须,可考虑使用 Elasticsearch 等搜索引擎。

复制代码
-- 优化前(索引失效)
select * from user where username like '%zhang';
-- 优化后(可使用索引)
select * from user where username like 'zhang%';
问题 4:在索引字段上做函数操作

对索引字段使用函数(如date(create_time))会导致索引失效,MySQL 无法直接使用索引进行过滤。

优化方案:将函数操作转移到常量端。

复制代码
-- 优化前(索引失效)
select * from order_info where date(create_time) = '2024-05-01';
-- 优化后(可使用索引)
select * from order_info where create_time between '2024-05-01 00:00:00' and '2024-05-01 23:59:59';

4.4 优化手段 3:其他高级方案

如果上述优化后性能仍不满足需求,可考虑以下高级方案:

1. 分表分库

当单表数据量超过 1000 万行时,即使有索引,查询性能也会明显下降。此时可通过 "分表" 将大表拆分为小表:

  • 水平分表:按时间(如每月一张订单表)、按用户 ID 哈希(如 user_id%10 拆为 10 张表);

  • 垂直分表 :将大字段(如remarkcontent)拆分到单独的表,减少主表数据量。

2. 读写分离

大部分业务场景中,"读" 操作远多于 "写" 操作(如电商商品详情页,查询量是更新量的 100 倍)。此时可通过 "读写分离" 将读请求分流到从库,主库只处理写请求:

  • 主库:插入、更新、删除(写操作);

  • 从库:查询(读操作);

  • 同步方式:MySQL 主从复制(异步 / 半同步)。

3. 缓存热点数据

对于高频访问且更新不频繁的数据(如商品分类、热门商品列表),可将其缓存到 Redis、Memcached 等缓存中间件中,直接从缓存返回结果,避免访问数据库。

相关推荐
郭庆汝3 小时前
模型部署:(三)安卓端部署Yolov8-v8.2.99目标检测项目全流程记录
android·yolo·目标检测·yolov8
Anthony_2313 小时前
MySQL的常用命令
运维·数据库·mysql·adb·docker
fatiaozhang95273 小时前
中国移动云电脑一体机-创维LB2004_瑞芯微RK3566_2G+32G_开启ADB ROOT安卓固件-方法3
android·xml·adb·电脑·电视盒子·刷机固件
柯南二号3 小时前
【Android】设置让输入框只能输入数字
android
CV资深专家3 小时前
Android 编译系统lunch配置总结
android
撩得Android一次心动3 小时前
Android 项目:画图白板APP开发(五)——橡皮擦(全面)
android·绘图·自定义视图
2501_915106323 小时前
App Store 软件上架全流程详解,iOS 应用发布步骤、uni-app 打包上传与审核要点完整指南
android·ios·小程序·https·uni-app·iphone·webview
大菠萝爱上小西瓜5 小时前
分享一篇关于雷电模拟器基于安卓9的安装环境及抓包的详细教程
android
用户2018792831675 小时前
浅析:Synchronized的锁升级机制
android