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 等缓存中间件中,直接从缓存返回结果,避免访问数据库。

相关推荐
Mr -老鬼10 分钟前
Android studio 最新Gradle 8.13版本“坑点”解析与避坑指南
android·ide·android studio
xiaolizi5674898 小时前
安卓远程安卓(通过frp与adb远程)完全免费
android·远程工作
教程分享大师8 小时前
创维E900V22D当贝固件全网通线刷机包 刷机教程开启adb权限及root权限
adb
吉凶以情迁8 小时前
tcl 电视进入开发者模式以及adb进入设置以及各电视打开开发者模式用电脑控制的办法
adb·电脑
阿杰100018 小时前
ADB(Android Debug Bridge)是 Android SDK 核心调试工具,通过电脑与 Android 设备(手机、平板、嵌入式设备等)建立通信,对设备进行控制、文件传输、命令等操作。
android·adb
_Mistletoe8 小时前
adb基础操作及如何使用adb实现设备间的文件传输
adb
XuanRanDev9 小时前
Mumu模拟器12开启ADB调试方法
adb
梨落秋霜9 小时前
Python入门篇【文件处理】
android·java·python
遥不可及zzz11 小时前
Android 接入UMP
android
Coder_Boy_13 小时前
基于SpringAI的在线考试系统设计总案-知识点管理模块详细设计
android·java·javascript