0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

系统讲解MySQL数据库慢查询优化思路

马哥Linux运维 来源:马哥Linux运维 2026-05-30 13:53 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

背景与适用场景

MySQL 是业务系统最常用的数据库,跑着跑着突然接口超时、数据库 CPU 飙升、连接数打满,这些问题排查思路是什么?本文从运维角度出发,讲清楚 MySQL 慢查询的分析方法、索引优化思路、配置参数调优、以及常见性能问题的排查路径。让你遇到数据库性能问题时知道该查什么、怎么查、怎么改、改完怎么验证。

适合阅读本文的场景:

数据库 CPU 或内存突然打满,不知道哪些 SQL 在作妖

接口响应时间变长,怀疑是数据库查询慢

慢查询日志里有一堆 SQL,不知道该优化哪些

新上线的 SQL 有没有性能问题,怎么评估

数据库配置参数不知道怎么调

主从复制延迟突然变大

数据库连接池不够用还是浪费资源

阅读本文需要有一定的 Linux 操作基础,了解基本的 SQL 语句,知道什么是数据库连接和索引。

MySQL 架构先搞清楚

MySQL 线程模型

MySQL 是多线程架构,主要线程:

InnoDB 主线程(负责刷新脏页、合并缓冲池)
InnoDB 读线程(负责预读)
InnoDB 写线程(负责刷脏页)
InnoDB 脏页刷新线程(page cleaner)
MySQL 连接管理线程(负责接收连接)
查询缓存(MySQL 8.0 已移除)

连接模型:客户端连接 -> 连接池 -> 查询解析 -> 优化器 -> 执行器 -> 存储引擎(InnoDB/MyISAM)

# 查看当前 MySQL 版本
mysql --version
mysql -u root -p -e"SELECT VERSION();"

# 查看 MySQL 进程
ps aux | grep mysqld
systemctl status mysql
systemctl status mysqld

# 查看 MySQL 配置
mysql -u root -p -e"SHOW VARIABLES;"| head -20

# 查看最大连接数
mysql -u root -p -e"SHOW VARIABLES LIKE 'max_connections';"

InnoDB 存储引擎核心概念

InnoDB 是 MySQL 默认存储引擎,需要搞清楚几个核心概念:

Buffer Pool:InnoDB 的缓存池,缓存表数据和索引,减小磁盘 IO
redolog:事务日志,保证宕机后数据不丢失
undolog:回滚日志,支持事务回滚
change buffer:二级索引变更缓存,减少随机 IO
自适应哈希索引:InnoDB 自动优化的哈希索引
# 查看 InnoDB 状态
mysql -u root -p -e"SHOW ENGINE INNODB STATUSG"

# 查看 Buffer Pool 大小(通常设置为机器内存的 60-80%)
mysql -u root -p -e"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

# 查看 Buffer Pool 使用情况(MySQL 5.6+)
mysql -u root -p -e"
SELECT
  pool_id,
  pool_size,
  free_buffers,
  database_pages,
  modified_db_pages
FROM information_schema.INNODB_BUFFER_POOL_STATS;
"

# 注意:MySQL 8.0 的 INNODB_BUFFER_POOL_STATS 表结构略有差异
# 某些版本显示的字段名可能不同,实际查询时以 SHOW VARIABLES 为准

# 查看 redo log 大小和状态
mysql -u root -p -e"SHOW VARIABLES LIKE 'innodb_log_file%';"
mysql -u root -p -e"SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';"

慢查询日志配置与开启

开启慢查询日志

# 临时开启(MySQL 重启后失效)
mysql -u root -p -e"SET GLOBAL slow_query_log = 'ON';"
mysql -u root -p -e"SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';"
mysql -u root -p -e"SET GLOBAL long_query_time = 1;"# 超过 1 秒记录

# 永久开启(修改配置文件)
# /etc/mysql/my.cnf 或 /etc/mysql/mysql.conf.d/mysqld.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 # 记录未使用索引的查询
log_slow_admin_statements = 1   # 记录管理操作

# 创建日志目录并授权
mkdir -p /var/log/mysql
chown mysql:mysql /var/log/mysql
touch /var/log/mysql/slow.log

# 重启 MySQL 使配置生效
systemctl restart mysql

# 验证配置
mysql -u root -p -e"SHOW VARIABLES LIKE 'slow_query%';"
mysql -u root -p -e"SHOW VARIABLES LIKE 'long_query_time';"

mysqldumpslow 工具分析慢查询日志

# 分析慢查询日志,输出最慢的 10 条
mysqldumpslow -t 10 /var/log/mysql/slow.log

# 按平均查询时间排序
mysqldumpslow -s at /var/log/mysql/slow.log

# 按总查询时间排序
mysqldumpslow -s t /var/log/mysql/slow.log

# 按锁时间排序
mysqldumpslow -s l /var/log/mysql/slow.log

# 忽略锁时间的排序
mysqldumpslow -s ar /var/log/mysql/slow.log

# 输出示例:
# Reading mysql slow query log from /var/log/mysql/slow.log
# Count: 1 Time=5.23s (5s) Lock=0.00s (0s) Rows=1000000 (1000000) SELECT * FROM orders
# Count: 1 Time=3.21s (3s) Lock=0.00s (0s) Rows=50000 (50000) SELECT * FROM products

pt-query-digest 更强大的分析工具

Percona Toolkit 的 pt-query-digest 是生产环境分析慢查询的首选工具。

# 安装(CentOS)
yum install percona-toolkit -y

# 安装(Ubuntu/Debian)
apt-get install percona-toolkit -y

# 分析慢查询日志
pt-query-digest /var/log/mysql/slow.log

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

# 只分析某个时间段的慢查询
pt-query-digest --since='2026-05-29 0000'--until='2026-05-29 1200'/var/log/mysql/slow.log

# 分析远程 MySQL 的慢查询
pt-query-digest --max-review 100 h=192.168.1.100,u=root,p=password --since=24h

# 输出到文件
pt-query-digest /var/log/mysql/slow.log > /tmp/query_analysis.txt

# 统计查询类型分布
pt-query-digest --filter'$event->{fingerprint} =~ m/^SELECT/i'/var/log/mysql/slow.log

pt-query-digest 输出解读:

# 400ms user=root host=localhost db=mydb
# Query_time: 5.234 Lock_time: 0.001 Rows_sent: 100 Rows_examined: 500000

select * from orderswherestatus ='completed'and created_at >'2026-01-01'

# 解读:
# Query_time: 5.234 秒 - 这条 SQL 执行了 5.234 秒
# Lock_time: 0.001 秒 - 表锁等了 0.001 秒
# Rows_sent: 100 - 返回了 100 行
# Rows_examined: 500000 - 扫描了 50 万行
# 问题:扫描 50 万行只返回 100 行,典型的需要加索引场景

EXPLAIN 执行计划分析

EXPLAIN 基本用法

-- 语法分析(不执行)
EXPLAINSELECT*FROMusersWHEREname='张三';

-- 详细执行计划(MySQL 5.6+)
EXPLAINANALYZESELECT*FROMusersWHEREname='张三';

-- 查看所有字段(MySQL 5.7+)
EXPLAINFORMAT=JSONSELECT*FROMusersWHEREname='张三';

执行计划字段解读

-- 创建测试表
CREATETABLEorders (
 idBIGINTPRIMARYKEYAUTO_INCREMENT,
  order_noVARCHAR(64)NOTNULL,
  user_idBIGINTNOTNULL,
 statusVARCHAR(32)NOTNULL,
  amountDECIMAL(10,2)NOTNULL,
  created_at DATETIMENOTNULL,
  updated_at DATETIME,
 INDEXidx_user_id (user_id),
 INDEXidx_status (status),
 INDEXidx_created_at (created_at),
 UNIQUEINDEXidx_order_no (order_no)
)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;

-- 分析这条 SQL
EXPLAINSELECT*FROMorders
WHEREuser_id =1000
ANDstatus='completed'
ANDcreated_at >'2026-01-01'
ORDERBYcreated_atDESC
LIMIT20;

EXPLAIN 输出字段详解:

id: 1
select_type: SIMPLE
table: orders
partitions: NULL
type: ref
possible_keys: idx_user_id,idx_status,idx_created_at
key: idx_user_id
key_len: 8
ref: const
rows: 1234
filtered: 10.00
Extra: Using index condition; Usingwhere; Using filesort

字段详解:
- id:执行顺序编号,相同数字表示一起执行
- select_type:查询类型,SIMPLE 最简单,SUBQUERY/UNION 等更复杂
- table:涉及的表名
- partitions:匹配的分区(分区表相关)
-type:连接类型,system > const > eq_ref > ref > range > index > ALL
    ALL 是全表扫描,最差;ref 是索引查找,最好
- possible_keys:可能使用的索引
- key:实际使用的索引
- key_len:索引长度,越短越好
- ref:与索引比较的列
- rows:预计扫描的行数,越少越好
- filtered:符合条件行数占总行数的百分比
- Extra:额外信息,Usingwhere表示需要回表检查;Using filesort 表示需要额外排序

type 字段详解(性能从好到差)

system:表只有一行(系统表),最好
const:最多一行匹配,通常是主键或唯一索引查询
eq_ref:连接时用主键或唯一索引,只返回一行
ref:用普通索引查询,返回匹配的多行
ref_or_null:类似 ref,但包含 NULL 查询
index_merge:使用多个索引合并
unique_subquery:子查询返回唯一值
index_subquery:子查询返回非唯一值
range:索引范围查询(BETWEEN、IN、>、<)
index:全索引扫描(只遍历索引,不回表)
ALL:全表扫描(最差,需要优化)

Extra 字段详解

Using filesort:无法用索引排序,需要额外排序操作,常见于 ORDER BY 非索引字段
Using temporary:需要临时表,常见于 DISTINCT、GROUP BY、UNION
Using index:覆盖索引,直接用索引返回数据,不需要回表
Using index condition:索引下推,用索引过滤部分条件
Usingwhere:需要回表检查 WHERE 条件
Using join buffer:连接时使用 join buffer(通常意味着连接字段没索引)
Impossible WHERE:WHERE 条件永远为 FALSE
Select tables optimized away:优化器直接返回结果

常见问题 SQL 分析

-- 1. 全表扫描(type=ALL)
EXPLAINSELECT*FROMordersWHEREamount >100;
-- 问题:amount 字段没有索引
-- 解决:ALTER TABLE orders ADD INDEX idx_amount (amount);

-- 2. Using filesort(需要排序)
EXPLAINSELECT*FROMordersORDERBYcreated_atDESCLIMIT10;
-- 问题:created_at 有索引但查询条件导致索引失效,或 ORDER BY 字段没有索引
-- 解决:SELECT * FROM orders WHERE status='completed' ORDER BY created_at DESC LIMIT 10;

-- 3. Using temporary(需要临时表)
EXPLAINSELECTDISTINCTuser_idFROMordersWHEREstatus='completed';
-- 问题:user_id 没有索引或优化器选择不当
-- 解决:ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);

-- 4. 索引失效(明明有索引但没用上)
EXPLAINSELECT*FROMordersWHEREYEAR(created_at) =2026;
-- 问题:对索引字段使用函数导致索引失效
-- 解决:改写为 SELECT * FROM orders WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';

-- 5. 多表连接顺序不当
EXPLAINSELECT a.*, b.name
FROM orders a
JOINusers b ON a.user_id = b.id
WHERE a.status = 'pending';
-- 注意看 id 字段,数字大的先执行

索引优化

索引设计原则

1. 频繁出现在 WHERE 条件的字段加索引
2. 频繁出现在 ORDER BY、GROUP BY 的字段加索引
3. 区分度高的字段适合加索引(性别这类字段区分度低,不适合)
4. 复合索引遵循最左前缀原则
5. 索引不是越多越好,维护索引有开销
6. 存储空间有限时,优先在高频查询表上建索引

复合索引设计

-- 场景:经常查询某个用户的订单,按状态和时间排序
-- 这样的 SQL:
SELECT*FROMorders
WHEREuser_id =1000ANDstatus='completed'
ORDERBYcreated_atDESC;

-- 复合索引设计(遵循最左前缀)
-- 正确:user_id 在前,status 在中,created_at 在后
ALTERTABLEordersADDINDEXidx_user_status_date (user_id,status, created_at);

-- 验证索引是否被使用
EXPLAINSELECT*FROMorders
WHEREuser_id =1000ANDstatus='completed'
ORDERBYcreated_atDESC;

-- 最左前缀原则验证:
-- 1. user_id = 1000       -- 使用索引
-- 2. user_id = 1000 AND status = 'completed' -- 使用索引
-- 3. user_id = 1000 AND status = 'completed' AND created_at > '2026-01-01' -- 使用索引
-- 4. status = 'completed'    -- 不使用索引(没从最左开始)
-- 5. user_id > 1000       -- 使用索引,但范围查询后不走索引

查看现有索引

-- 查看表的所有索引
SHOWINDEXFROMorders;

-- 格式化输出(MySQL 5.6+)
SHOWINDEXFROMordersG

-- 从 information_schema 查看
SELECT
  TABLE_NAME,
  INDEX_NAME,
  COLUMN_NAME,
  SEQ_IN_INDEX,
  NON_UNIQUE,
  CARDINALITY
FROMinformation_schema.STATISTICS
WHERETABLE_SCHEMA ='your_database'
ANDTABLE_NAME ='orders'
ORDERBYINDEX_NAME, SEQ_IN_INDEX;

索引分析工具

# 使用 EXPLAIN 分析慢查询
# 1. 先从慢查询日志或 pt-query-digest 找到需要优化的 SQL
# 2. 用 EXPLAIN 分析
mysql -u root -p -e"EXPLAIN SELECT ..."database

# 使用 optimizer_trace 分析优化器决策(MySQL 5.6+)
mysql -u root -p -e"SET optimizer_trace='enabled=on';"
mysql -u root -p -e"SELECT ..."database
mysql -u root -p -e"SELECT * FROM information_schema.OPTIMIZER_TRACEG"
mysql -u root -p -e"SET optimizer_trace='enabled=off';"

# 使用 performance_schema 分析(MySQL 5.6+)
-- 开启监控
UPDATE performance_schema.setup_consumers
SET enabled ='YES'
WHERE name IN ('statements_digest');

-- 查看哪些 SQL 最耗时
SELECT
  DIGEST_TEXT,
  COUNT_STAR,
  SUM_TIMER_WAIT/1000000000000 AS sum_seconds,
  AVG_TIMER_WAIT/1000000000000 AS avg_seconds,
  SUM_ROWS_EXAMINED,
  SUM_ROWS_SENT
FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT IS NOT NULL
ORDER BY sum_seconds DESC
LIMIT 10;

常见慢查询优化案例

案例 1:SELECT * 危害

-- 问题 SQL
SELECT*FROMordersWHEREuser_id =1000;

-- EXPLAIN 分析
EXPLAINSELECT*FROMordersWHEREuser_id =1000;
-- type: ref
-- key: idx_user_id
-- rows: 50
-- Extra: Using index condition

-- 问题:虽然走了索引,但 SELECT * 会回表查询所有字段
-- 如果 idx_user_id 是覆盖索引(包含查询的所有字段),则不回表

-- 优化:只查需要的字段
SELECTid, order_no,status, amount, created_at
FROMordersWHEREuser_id =1000;

-- 更好:利用覆盖索引,避免回表
ALTERTABLEordersADDINDEXidx_user_cover (user_id,id, order_no,status, amount, created_at);

EXPLAINSELECTid, order_no,status, amount, created_at
FROMordersWHEREuser_id =1000;
-- Extra: Using index(覆盖索引,不需要回表)

案例 2:分页查询优化

-- 问题 SQL:深分页查询
SELECT*FROMorders
WHEREstatus='completed'
ORDERBYidDESC
LIMIT1000000,20;

-- 问题:MySQL 需要扫描 1000020 行,返回最后 20 行,极慢

-- EXPLAIN 分析
EXPLAINSELECT*FROMorders
WHEREstatus='completed'
ORDERBYidDESC
LIMIT1000000,20;
-- type: ALL(全表扫描)
-- rows: 5000000(扫描 500 万行)
-- Extra: Using filesort

-- 优化 1:利用主键索引
SELECT*FROMorders
WHEREid>1000000ANDstatus='completed'
ORDERBYidDESC
LIMIT20;

-- 优化 2:延迟关联
SELECTo.*FROMorders o
INNERJOIN(
 SELECTidFROMorders
 WHEREstatus='completed'
 ORDERBYidDESC
 LIMIT1000000,20
) tONo.id = t.id;

-- 优化 3:游标分页(最适合大数据量)
-- 上一页最后一条的 id 是 1000020
SELECT*FROMorders
WHEREstatus='completed'ANDid< 1000020
ORDERBYidDESC
LIMIT20;

案例 3:COUNT(*) 优化

-- 问题 SQL
SELECTCOUNT(*)FROMordersWHEREstatus='pending';

-- 问题:全表扫描统计行数,大表极慢

-- EXPLAIN 分析
EXPLAINSELECTCOUNT(*)FROMordersWHEREstatus='pending';
-- type: ALL
-- rows: 5000000

-- 优化 1:利用索引
ALTERTABLEordersADDINDEXidx_status (status);

EXPLAINSELECTCOUNT(*)FROMordersWHEREstatus='pending';
-- type: ref
-- key: idx_status
-- rows: 500

-- 优化 2:使用 EXPLAIN 辅助(MySQL 8.0+ 支持)
EXPLAINSELECTCOUNT(*)FROMordersWHEREstatus='pending';

-- 优化 3:近似值(允许误差时)
SELECTTABLE_ROWSFROMinformation_schema.TABLES
WHERETABLE_SCHEMA ='your_database'ANDTABLE_NAME ='orders';

-- 优化 4:汇总表(实时性要求不高时)
CREATETABLEorders_status_count (
 statusVARCHAR(32) PRIMARYKEY,
  cntBIGINTNOTNULLDEFAULT0
);

-- 定时更新汇总表
UPDATEorders_status_count, (
 SELECTstatus,COUNT(*)ascntFROMordersGROUPBYstatus
) tSETorders_status_count.cnt = t.cnt
WHEREorders_status_count.status = t.status;

案例 4:JOIN 优化

-- 问题 SQL:多表 JOIN
SELECTo.id, o.order_no, u.name, u.email
FROMorders o
JOINusersuONo.user_id = u.id
JOINorder_items oiONo.id = oi.order_id
JOINproducts pONoi.product_id = p.id
WHEREo.status ='completed'
ANDp.category ='electronics'
LIMIT100;

-- EXPLAIN 分析
EXPLAINSELECTo.id, o.order_no, u.name, u.email
FROMorders o
JOINusersuONo.user_id = u.id
JOINorder_items oiONo.id = oi.order_id
JOINproducts pONoi.product_id = p.id
WHEREo.status ='completed'
ANDp.category ='electronics'
LIMIT100;

-- 优化原则:
-- 1. 确保连接字段有索引
-- 2. 优先过滤数据量大的表
-- 3. 小表驱动大表(MySQL 优化器通常自动选择)

-- 先看各表数据量
SELECTCOUNT(*)FROMorders;    -- 500万
SELECTCOUNT(*)FROMusers;    -- 100万
SELECTCOUNT(*)FROMorder_items; -- 2000万
SELECTCOUNT(*)FROMproducts;   -- 10万

-- 优化:让数据量小的表先 JOIN
SELECTo.id, o.order_no, u.name, u.email
FROMproducts p
JOINorder_items oiONp.id = oi.product_id
JOINorders oONoi.order_id = o.id
JOINusersuONo.user_id = u.id
WHEREp.category ='electronics'
ANDo.status ='completed'
LIMIT100;

-- 确保索引完整
ALTERTABLEorder_itemsADDINDEXidx_order_product (order_id, product_id);
ALTERTABLEordersADDINDEXidx_status (status);

案例 5:OR 条件优化

-- 问题 SQL
SELECT*FROMusers
WHEREname='张三'ORemail ='zhangsan@example.com'
 ORphone ='13800138000';

-- 问题:OR 条件可能导致索引失效

-- EXPLAIN 分析
EXPLAINSELECT*FROMusers
WHEREname='张三'ORemail ='zhangsan@example.com'
 ORphone ='13800138000';
-- type: ALL(可能全表扫描)

-- 优化 1:改写为 UNION
SELECT*FROMusersWHEREname='张三'
UNION
SELECT*FROMusersWHEREemail ='zhangsan@example.com'
UNION
SELECT*FROMusersWHEREphone ='13800138000';

-- 优化 2:利用索引(每个 OR 条件都走索引时)
EXPLAINSELECT*FROMusers
WHEREname='张三'
UNION
SELECT*FROMusers
WHEREemail ='zhangsan@example.com'
UNION
SELECT*FROMusers
WHEREphone ='13800138000';

-- 优化 3:使用 IN 代替 OR
SELECT*FROMusers
WHEREnameIN('张三')
 ORemailIN('zhangsan@example.com')
 ORphoneIN('13800138000');
-- 效果同 OR,但语义更清晰

-- 建议:为每个字段单独建索引,UNION 效率更高
ALTERTABLEusersADDINDEXidx_name (name);
ALTERTABLEusersADDINDEXidx_email (email);
ALTERTABLEusersADDINDEXidx_phone (phone);

案例 6:IN + 子查询优化

-- 问题 SQL:IN 里面的子查询
SELECT*FROMorders
WHEREuser_idIN(
 SELECTidFROMusers
 WHEREcreated_at >'2026-01-01'
);

-- EXPLAIN 分析
EXPLAINSELECT*FROMorders
WHEREuser_idIN(
 SELECTidFROMusers
 WHEREcreated_at >'2026-01-01'
);
-- 执行计划可能先执行子查询,再执行外层

-- 优化 1:使用 EXISTS 改写(子查询数据量大时)
SELECT*FROMorders o
WHEREEXISTS(
 SELECT1FROMusersu
 WHEREu.id = o.user_id
  ANDu.created_at >'2026-01-01'
);

-- 优化 2:使用 JOIN 改写
SELECTo.*FROMorders o
INNERJOINusersuONo.user_id = u.id
WHEREu.created_at >'2026-01-01';

-- 优化 3:半连接优化(MySQL 8.0+ 自动优化)
EXPLAINSELECT*FROMorders
WHEREuser_idIN(
 SELECTidFROMusers
 WHEREcreated_at >'2026-01-01'
);
-- MySQL 8.0 可能自动选择 MaterializeLookup 策略

MySQL 配置参数调优

核心参数说明

# 连接到 MySQL
mysql -u root -p

# 查看当前参数值
SHOW VARIABLES LIKE'innodb_buffer_pool_size';
SHOW VARIABLES LIKE'max_connections';
SHOW VARIABLES LIKE'innodb_log_file_size';

常用调参配置(在 my.cnf 中设置):

[mysqld]
# 内存配置
# Buffer Pool 大小,通常设置为机器内存的 60-80%
innodb_buffer_pool_size = 8G

# 连接配置
# 最大连接数,根据业务并发需求调整
max_connections = 2000

# 每个连接可用内存(防止单个查询耗尽内存)
max_connections = 2000
max_allowed_packet = 64M

# 日志配置
# redo log 文件大小,建议 1GB-4GB
innodb_log_file_size = 1G

# redo log 组数量
innodb_log_files_in_group = 3

# 事务提交后刷盘策略(1 最安全,2 性能最好,0 折中)
innodb_flush_log_at_trx_commit = 1

# 临时表和排序缓冲
tmp_table_size = 256M
max_heap_table_size = 256M
sort_buffer_size = 4M
join_buffer_size = 4M

# 注意:MySQL 5.7 和 MySQL 8.0 已经移除了查询缓存(query_cache)
# 5.7 的 query_cache_size 和 query_cache_type 已废弃
# 8.0 完全移除,配置这些参数会报错
# 如果看到配置文件中有这些参数,应该删除

# 慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

# 线程缓存
thread_cache_size = 64

# 表缓存
table_open_cache = 4000

# InnoDB 配置
# 刷新脏页的线程数(建议 CPU 核心数的 1/4)
innodb_page_cleaners = 4

# 开启独立表空间(建议开启,便于管理)
innodb_file_per_table = 1

# 并发线程数(0 表示自动设置,等于 CPU 核心数)
innodb_thread_concurrency = 0

# 开启慢 SQL 记录(记录未使用索引的查询)
log_queries_not_using_indexes = 1

参数调整验证

# 修改参数后验证
mysql -u root -p -e"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

# 查看 Buffer Pool 使用情况
mysql -u root -p -e"
SELECT
  ROUND(POOL_SIZE * PAGE_SIZE / 1024 / 1024, 2) AS pool_size_mb,
  ROUND(FREE_BUFFERS * PAGE_SIZE / 1024 / 1024, 2) AS free_mb,
  ROUND(DATABASE_PAGES * PAGE_SIZE / 1024 / 1024, 2) AS used_mb
FROM information_schema.INNODB_BUFFER_POOL_STATS
WHERE pool_id = 0;
"

# 查看连接数使用情况
mysql -u root -p -e"
SELECT
  MAX_USED_CONNECTIONS,
  MAX_USED_CONNECTIONS / MAX_CONNECTIONS * 100 AS usage_pct,
  THREADS_CONNECTED,
  THREADS_RUNNING
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME IN ('Max_used_connections', 'Max_used_connections', 'Threads_connected', 'Threads_running');
"

# 查看当前连接
mysql -u root -p -e"SHOW PROCESSLIST;"
mysql -u root -p -e"SHOW FULL PROCESSLIST;"

配置变更操作步骤

# 1. 备份原配置文件
cp /etc/mysql/my.cnf /etc/mysql/my.cnf.bak.$(date +%Y%m%d)

# 2. 修改配置
vim /etc/mysql/my.cnf

# 3. 检查配置语法
mysqld --validate-config

# 4. 优雅重启 MySQL(不断开现有连接)
mysql -u root -p -e"SET GLOBAL innodb_buffer_pool_size = 8589934592;"
# 注:某些参数需要重启才能生效

# 5. 重启 MySQL(会短暂断开连接)
systemctl restart mysql

# 6. 验证配置生效
mysql -u root -p -e"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

# 7. 监控错误日志
tail -f /var/log/mysql/error.log

主从复制与延迟监控

查看主从复制状态

-- MySQL 5.7 查看主从状态
SHOWSLAVESTATUSG

-- MySQL 8.0+ 查看主从状态(8.0.22+ 推荐使用 REPLICA 关键字)
SHOWREPLICASTATUSG

-- 关键字段解读(两种版本通用):
-- Slave_IO_Running / Replica_IO_Running: IO 线程是否运行
-- Slave_SQL_Running / Replica_SQL_Running: SQL 线程是否运行
-- Seconds_Behind_Master / Seconds_Behind_Source: 延迟秒数
-- Relay_Log_Pos / Replica_Log_Pos: Relay Log 位置
-- Exec_Master_Log_Pos / Exec_Master_Log_Pos: 执行到主库日志位置
-- Last_Error: 最后一次错误

常见延迟原因与排查

-- 1. 大事务导致延迟
-- 监控:Seconds_Behind_Master 突然变大
-- 排查:
SHOWPROCESSLIST;
-- 找到 SQL 线程状态,查看执行的 SQL

-- 2. 从库压力大导致延迟
-- 监控:CPU 和 IO 使用率
-- 排查:在从库执行
SHOWSTATUSLIKE'Handler_read%';

-- 3. 网络延迟导致
-- 排查:监控主从之间的网络带宽和延迟
-- 解决:使用压缩传输
STOPSLAVE;
SETGLOBALslave_compressed_protocol =1;
STARTSLAVE;

-- 4. 大表 DDL 导致延迟
-- 解决:使用 pt-online-schema-change 或 gh-ost
-- pt-online-schema-change 示例:
pt-online-schema-change
 --alter "ADD INDEX idx_name (name)" 
 --user=root 
 --password=xxx 
  D=mydb,t=orders 
 --execute

-- 5. 错误跳过导致延迟
-- 如果某个 SQL 执行报错被跳过
SHOWSLAVESTATUSG
-- 查看 Last_Errno 和 Last_Error

主从延迟监控 SQL

-- MySQL 5.7 查询当前延迟
SHOWSLAVESTATUSG
-- 查看 Seconds_Behind_Master 字段

-- MySQL 8.0 查询当前延迟
SHOWREPLICASTATUSG

-- MySQL 8.0 performance_schema 查询(更详细)
SELECT
  slave_server_id,
  source_uuid,
  service_state,
  last_heartbeat_timestamp,
  received_heartbeat_set,
  last_error_number,
  last_error_message
FROMperformance_schema.replication_connection_statusG

SELECT
  slave_server_id,
  service_state,
  count_transactions_retries
FROMperformance_schema.replication_applier_statusG

-- 注意:performance_schema.replication_* 表是 MySQL 8.0+ 才有的
-- MySQL 5.7 使用 SHOW SLAVE STATUS 获取同样信息

性能问题排查流程

数据库突然变慢的排查思路

1. 查看当前连接和查询
 SHOW PROCESSLIST;
 SHOW FULL PROCESSLIST;

2. 查看慢查询
 mysqldumpslow -t 10 /var/log/mysql/slow.log

3. 查看锁等待
 SELECT * FROM information_schema.INNODB_LOCK_WAITS;
 SELECT * FROM information_schema.INNODB_TRX;

4. 查看资源使用
 SHOW STATUS LIKE'Threads%';
 SHOW STATUS LIKE'Connections';

5. 查看表锁情况
 SHOW STATUS LIKE'Table_locks%';

实时排查命令

# 1. 查看当前执行的 SQL
mysql -u root -p -e"SHOW PROCESSLISTG"
mysql -u root -p -e"SHOW FULL PROCESSLISTG"

# 2. 查看锁等待
mysql -u root -p -e"
SELECT
  r.trx_id,
  r.trx_mysql_thread_id,
  r.trx_query,
  b.blocking_query,
  b.blocking_id
FROM information_schema.INNODB_TRX r
LEFT JOIN (
  SELECT
    trx_id,
    trx_mysql_thread_id AS blocking_id,
    trx_query AS blocking_query
  FROM information_schema.INNODB_TRX
) b ON b.blocking_id = r.trx_mysql_thread_id
WHERE r.trx_state = 'LOCK WAIT';
"

# 3. 查看连接数
mysql -u root -p -e"
SELECT
  VARIABLE_NAME,
  VARIABLE_VALUE
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME IN ('MAX_USED_CONNECTIONS', 'THREADS_CONNECTED');
"

# 4. 查看 Buffer Pool 命中率
mysql -u root -p -e"
SELECT
  POOL_ID,
  PAGES_DATA,
  PAGES_DIRTY,
  PAGES_FLUSH,
  PAGES_MADE_NOT_YONG,
  PAGES_MADE_YOUNG,
  NUMBER_PAGES_GET,
  HASH_SCANDES,
  HASH_ROWS_GET,
  HASH_ROWS_UPDATED,
  HASH_ROWS_DELETED
FROM information_schema.INNODB_BUFFER_PAGE;
"| less

# 5. 查看临时表使用情况
mysql -u root -p -e"
SELECT
  SUM(CREATED_TMP_TABLES) AS created_tmp_tables,
  SUM(CREATED_TMP_DISK_TABLES) AS created_tmp_disk_tables,
  SUM(CREATED_TMP_FILES) AS created_tmp_files
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME LIKE 'Created_tmp%';
"

常见问题速查

-- 问题 1:连接数打满
-- 原因:连接泄漏、连接池配置不当、慢查询阻塞
-- 解决:
-- 1. 查看连接
SHOWPROCESSLIST;
-- 2. 杀掉阻塞的连接
KILL;
-- 3. 调大 max_connections
SETGLOBALmax_connections =3000;

-- 问题 2:CPU 打满
-- 原因:慢查询、缺少索引、表结构不合理
-- 解决:
-- 1. 找慢 SQL
SELECT*FROMperformance_schema.events_statements_summary_by_digest
ORDERBYSUM_TIMER_WAITDESCLIMIT10;
-- 2. EXPLAIN 分析
EXPLAIN;
-- 3. 加索引
ALTERTABLEordersADDINDEXidx_xxx (xxx);

-- 问题 3:磁盘 IO 打满
-- 原因:频繁刷脏、大事务、大排序
-- 解决:
-- 1. 调大 Buffer Pool
SETGLOBALinnodb_buffer_pool_size =16*1024*1024*1024;
-- 2. 合并小 IO
SETGLOBALinnodb_flush_log_at_trx_commit =2;
-- 3. 优化 SQL,减少扫描行数

-- 问题 4:内存使用过高
-- 原因:Buffer Pool 设置过大、临时表过多
-- 解决:
-- 1. 检查 Buffer Pool 大小
SHOWVARIABLESLIKE'innodb_buffer_pool_size';
-- 2. 检查临时表
SHOWSTATUSLIKE'Created_tmp%';
-- 3. 调整 tmp_table_size
SETGLOBALtmp_table_size =128*1024*1024;

风险提醒与回滚方案

生产环境操作规范

# 1. DDL 操作前必须备份
mysqldump -u root -p --single-transaction mydb orders > orders_backup_$(date +%Y%m%d).sql

# 2. 大表加索引用 pt 工具(在线加索引,不锁表)
pt-online-schema-change 
  --alter"ADD INDEX idx_name (name)"
  --user=root 
  --password=xxx 
  D=mydb,t=orders 
  --execute 
  --print

# 3. 禁止在业务高峰期执行 DDL
# 选择凌晨低峰期,并通知相关人员

# 4. 配置变更前备份原配置
cp /etc/mysql/my.cnf /etc/mysql/my.cnf.bak.$(date +%Y%m%d)

# 5. 重大变更前在测试环境验证
# 测试环境完全模拟生产配置
# 执行同样的 SQL,观察执行计划

# 6. 回滚方案准备
# DDL 回滚:备份原表结构,用备份恢复
# 配置回滚:保留原配置文件
# 索引回滚:DROP INDEX 即可(风险较小)

危险操作清单

# 1. DELETE 和 UPDATE 必须带 WHERE 条件
-- 危险:DELETE FROM orders; -- 删除整表
-- 安全:DELETE FROM orders WHERE id > 1000000 AND created_at < '2025-01-01';

# 2. TRUNCATE 和 DROP TABLE 极度危险
# TRUNCATE TABLE orders;  -- 立即清空表,无法回滚(除非有备份)
# DROP TABLE orders;      -- 删除表结构,无法回滚

# 3. ALTER TABLE 可能长时间锁表
# 大表 ALTER 在 MySQL 5.6 之前会锁表
# 使用 pt-online-schema-change 或 gh-ost

# 4. 禁止在生产环境执行无限制的 JOIN
# SELECT * FROM a JOIN b JOIN c JOIN d ... -- 可能导致内存爆炸

# 5. 禁止在生产环境执行无 LIMIT 的查询
# SELECT * FROM orders WHERE status = 'pending';  -- 可能返回百万行

# 6. 连接数调整需要谨慎
# max_connections 设置过大会导致 MySQL 启动失败(内存不足)
# 预估:max_connections * max_allowed_packet = 最大内存使用

备份策略

# 1. 全量备份
mysqldump -u root -p 
  --single-transaction 
  --routines 
  --triggers 
  --events 
  --master-data=2 
  mydb > mydb_full_backup_$(date +%Y%m%d).sql

# 2. 备份压缩
mysqldump -u root -p mydb | gzip > mydb_backup_$(date +%Y%m%d).sql.gz

# 3. 只备份表结构
mysqldump -u root -p --no-data mydb > mydb_schema.sql

# 4. 只备份数据
mysqldump -u root -p --no-create-info mydb > mydb_data.sql

# 5. xtrabackup 热备(生产环境推荐)
innobackupex --user=root --password=xxx /backup/
innobackupex --user=root --password=xxx --apply-log /backup/2026-05-29_xx-xx-xx/

# 6. 恢复
mysql -u root -p mydb < mydb_full_backup_20260529.sql

# 7. 定时备份脚本示例
cat > /opt/scripts/mysql_backup.sh << 'EOF'
#!/bin/bash
DATE=$(date +%Y%m%d)
BACKUP_DIR="/backup/mysql"
MYSQL_USER="root"
MYSQL_PASS="xxx"
MYSQL_HOST="localhost"

# 创建备份目录
mkdir -p ${BACKUP_DIR}

# 全量备份
mysqldump -h ${MYSQL_HOST} -u ${MYSQL_USER} -p${MYSQL_PASS} 
    --single-transaction 
    --routines 
    --triggers 
    --events 
    mydb | gzip >${BACKUP_DIR}/mydb_${DATE}.sql.gz

# 清理 7 天前的备份
find${BACKUP_DIR}-name"*.sql.gz"-mtime +7 -delete

echo"Backup completed:${DATE}"
EOF

chmod +x /opt/scripts/mysql_backup.sh
# 加入 crontab
# 0 3 * * * /opt/scripts/mysql_backup.sh

总结

MySQL 慢查询优化是运维的必备技能,核心知识点:

慢查询分析:

开启慢查询日志:slow_query_log = 1、long_query_time = 1

用mysqldumpslow和pt-query-digest分析日志

重点关注Rows_examined和Rows_sent的比值

执行计划分析:

EXPLAIN查看 SQL 执行计划

type字段从好到差:const>eq_ref>ref>range>index>ALL

Extra关注Using filesort、Using temporary、Using index

索引优化:

遵循最左前缀原则

覆盖索引避免回表

不要在索引列上使用函数

OR 条件改写为 UNION

配置调优:

innodb_buffer_pool_size:机器内存的 60-80%

max_connections:根据业务并发需求

innodb_log_file_size:1-4GB

排查流程:

SHOW PROCESSLIST查看当前查询

information_schema查看锁等待

performance_schema查看性能统计

遇到数据库性能问题不要慌,按顺序排查:先看连接和慢 SQL,再分析执行计划,最后调整配置和索引。改动前一定备份,改动后一定验证。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    11382

    浏览量

    226560
  • Linux
    +关注

    关注

    88

    文章

    11871

    浏览量

    219966
  • 数据库
    +关注

    关注

    7

    文章

    4095

    浏览量

    68717

原文标题:数据库性能卡顿?运维视角搞定 MySQL 慢查询优化

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    基于数据库查询过程优化设计

    在大型关系数据库管理与开发中,优化设计极大地提高数据库的性能。通过对一大型数据库查询语句执行过程的讨论,提出了对同一表格进行多个选择运算的
    发表于 02-27 16:05 18次下载

    数据库系统概论之如何进行关系查询处理和查询优化

    本文档的主要内容详细介绍的是数据库系统概论之如何进行关系查询处理和查询优化主要内容包括了:1、关系数据库系统
    发表于 11-15 15:12 11次下载
    <b class='flag-5'>数据库系统</b>概论之如何进行关系<b class='flag-5'>查询</b>处理和<b class='flag-5'>查询</b><b class='flag-5'>优化</b>

    MySQL数据库:理解MySQL的性能优化优化查询

    最近一直在为大家更新MySQL相关学习内容,可能有朋友不懂MySQL的重要性。在程序,语言,架构更新换代频繁的今天,MySQL 恐怕是大家使用最多的存储数据库了。由于
    的头像 发表于 07-02 17:18 3869次阅读
    <b class='flag-5'>MySQL</b><b class='flag-5'>数据库</b>:理解<b class='flag-5'>MySQL</b>的性能<b class='flag-5'>优化</b>、<b class='flag-5'>优化</b><b class='flag-5'>查询</b>

    MySQL数据库管理与应用

    MySQL数据库管理与应用 MySQL是一种广泛使用的关系型数据库管理系统,被认为是最流行和最常见的开源
    的头像 发表于 08-28 17:15 2027次阅读

    mysql数据库基础命令

    MySQL是一个流行的关系型数据库管理系统,经常用于存储、管理和操作数据。在本文中,我们将详细介绍MySQL的基础命令,并提供与每个命令相关
    的头像 发表于 12-06 10:56 1626次阅读

    数据库数据恢复—MYSQL数据库ibdata1文件损坏的数据恢复案例

    mysql数据库故障: mysql数据库文件ibdata1、MYI、MYD损坏。 故障表现:1、数据库无法进行
    的头像 发表于 12-09 11:05 1480次阅读

    数据库数据恢复—Mysql数据库表记录丢失的数据恢复流程

    Mysql数据库故障: Mysql数据库表记录丢失。 Mysql数据库故障表现: 1、
    的头像 发表于 12-16 11:05 1448次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—<b class='flag-5'>Mysql</b><b class='flag-5'>数据库</b>表记录丢失的<b class='flag-5'>数据</b>恢复流程

    MySQL数据库的安装

    MySQL是一个开源免费的关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下公司。 MySQL 最流行的
    的头像 发表于 01-14 11:25 1371次阅读
    <b class='flag-5'>MySQL</b><b class='flag-5'>数据库</b>的安装

    MySQL数据库是什么

    MySQL数据库是一种 开源的关系型数据库管理系统(RDBMS) ,由瑞典MySQL AB公司开发,后被Oracle公司收购。它通过结构化
    的头像 发表于 05-23 09:18 1583次阅读

    MySQL查询终极优化指南

    作为一名在生产环境摸爬滚打多年的运维工程师,我见过太多因为查询导致的线上故障。今天分享一套经过实战检验的MySQL查询分析与索引
    的头像 发表于 08-13 15:55 1029次阅读

    数据库查询分析与SQL优化实战技巧

    今天,我将分享我在处理数千次数据库性能问题中积累的实战经验,帮助你系统掌握查询分析与SQL优化的核心技巧。无论你是刚入门的运维新手,还是有
    的头像 发表于 09-08 09:34 1370次阅读

    MySQL数据库查询分析与优化实战

    在讨论MySQL查询之前,需要先明确一个关键前提:什么是查询? 不同业务场景下,
    的头像 发表于 04-02 09:38 330次阅读

    MySQL查询调优指南

    MySQL查询数据库性能问题的最常见原因。当一条SQL语句执行超过1秒时,就可能影响用户体验;超过10秒时,通常会收到用户投诉;而超过30秒的
    的头像 发表于 04-09 10:01 267次阅读

    MySQL数据库查询的排查思路和最佳实践

    数据库查询是导致应用响应缓慢最常见的原因之一。当业务人员反馈“页面加载”、“查询超时”、“系统
    的头像 发表于 04-24 14:40 236次阅读

    系统讲解MySQL查询的完整排查流程

    MySQL 查询是影响业务响应速度的最常见根因。业务高峰期一次看似简单的 SELECT 查询,可能拖整个
    的头像 发表于 05-11 16:50 344次阅读