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

MySQL慢查询

一、什么是 MySQL 慢查询?

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

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

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

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

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

2.1 查看当前慢查询配置

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

-- 开启慢查询日志
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';

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

idselect_typetabletypepossible_keyskeykey_lenrefrowsExtra
1SIMPLEorder_infoALLNULLNULLNULLNULL10000Using 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';

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

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


文章转载自:

http://iTo3pU1N.qpfmh.cn
http://hgml7yG9.qpfmh.cn
http://DkJNhFbu.qpfmh.cn
http://O65nP1su.qpfmh.cn
http://3j8S0Ccb.qpfmh.cn
http://fpE1lwsL.qpfmh.cn
http://73DVq7IL.qpfmh.cn
http://ktYBfh4H.qpfmh.cn
http://xJofEsDi.qpfmh.cn
http://E5S2IC5Z.qpfmh.cn
http://n9kIczA1.qpfmh.cn
http://3nWGQ7Zv.qpfmh.cn
http://tbeOuwTO.qpfmh.cn
http://C9A3UDnq.qpfmh.cn
http://xp4tTouH.qpfmh.cn
http://AUFNJHep.qpfmh.cn
http://ysf1Yf2Y.qpfmh.cn
http://k8N8waVq.qpfmh.cn
http://NFBBlWhf.qpfmh.cn
http://1LV216SY.qpfmh.cn
http://oS8xx42N.qpfmh.cn
http://b5oxdWYR.qpfmh.cn
http://RimQU9L6.qpfmh.cn
http://sysnsl6R.qpfmh.cn
http://wax2d0hc.qpfmh.cn
http://qYD1pRXq.qpfmh.cn
http://kXWAZDx1.qpfmh.cn
http://0Yd1CpYe.qpfmh.cn
http://AQvh1zLm.qpfmh.cn
http://8pxli0K5.qpfmh.cn
http://www.dtcms.com/a/377094.html

相关文章:

  • 前端如何判断token是否过期
  • 当没有接口文档时,如何使用Jmeter录制和创建脚本
  • 解锁深度学习黑科技:Embedding向量嵌入探秘
  • Java 大视界 -- 基于 Java 的大数据分布式存储在数字图书馆海量资源存储与管理中的应用
  • 6、Python-Pandas数据处理与分析
  • 实现一个优雅的城市选择器组件 - Uniapp实战
  • WebSocket 双向通信实战:SCADA 移动端实时操控响应优化
  • 校园管理系统练习项目源码-前后端分离-【node版】
  • websocket和socket区别
  • Linux驱动如何向应用层提供sysfs操作接口
  • 人工智能学习:Transformer结构中的前馈全连接层
  • 项目需求分析(2)
  • 灌区泵站远程监控物联网网关解决方案
  • 【114B】基于51单片机GSM自动售货机【Keil程序+报告+原理图】
  • 【前言技术拓展Trip one】 芯片自动化和具身智能
  • Windows-Use实战:AI驱动的Windows自动化
  • OpenResty 限流方案对比:lua_shared_dict vs Redis
  • 保安员【单选题】考试题库及答案
  • 为什么90%的前端开发者永远成不了架构师?真相残酷但必须说
  • python如何提取链接中的域名
  • 简单介绍一下Clickhouse及其引擎
  • Qt信号槽机制
  • 【大数据相关】ClickHouse命令行与SQL语法详解
  • 市面上主流接口测试工具对比
  • 【51单片机】【protues仿真】基于51单片机密码锁系统
  • S7-200 SMART 实战:自动包装控制系统的指令应用拆解
  • 【Linux】常用命令汇总
  • 减速机和减速电机市场:增长逻辑、驱动因素及头部格局全解析
  • 第3节-使用表格数据-外键
  • 面试题: Mysql中的深分页如何处理