12_MySQL InnoDB 内存与锁调优 —— Buffer Pool、redo log、隔离级别与锁等待

12_MySQL InnoDB 内存与锁调优 ------ Buffer Pool、redo log、隔离级别与锁等待

系列定位 :《MySQL 数据库全栈运维实战系列》第 12 篇,与《Oracle 数据库全栈运维实战系列(VMware 版)》同构对标。适用版本 :MySQL 8.0(5.7 差异随文标注)。官方文档:MySQL 8.0 Reference Manual、InnoDB Buffer Pool / Redo Log / Locking / InnoDB Multi-Versioning 章节。


一、InnoDB 内存与锁全景(官方:InnoDB In-Memory Structures)

图 1

图 2

组件 职责 类比 Oracle
Buffer Pool 数据页缓存,读加速、写缓冲 Database Buffer Cache
Log Buffer redo 事务提交缓冲 Redo Log Buffer
redo log 崩溃恢复前滚 Redo Log / Online Redo Log
undo log 回滚 + MVCC 版本 Undo Segments / Rollback
Change Buffer 非唯一二级索引写入延迟合并 ---

核心差异 :Oracle 的 Buffer Cache 是块级共享池,MySQL InnoDB Buffer Pool 是按页(16KB)自管理的 LRU 链,内部有年轻/老年代划分防全表扫描污染。


二、环境准备(VMware 虚机)

单台 MySQL 8.0(192.168.10.11),分配 8GB 内存给虚机,专门验证 Buffer Pool 命中率与锁等待。

复制代码
# 造压测流量(解释:让 Buffer Pool 热起来,观察命中率)
sysbench oltp_read_write --mysql-host=127.0.0.1 --tables=8 --table-size=5000000 run

VMware 注意事项(本篇相关):

事项 说明
内存预留/锁留 Buffer Pool 大小接近物理内存时,必须在虚机设置里预留全部内存(Reserve all guest memory),否则 balloon/换出会人为造成命中率骤降
关 swap vm.swappiness=1,Buffer Pool 被换出是性能灾难
磁盘 IOPS redo log 刷盘是顺序写,数据盘要能承受稳定写入;挂 SSD 直通
CPU 锁等待与并发有关,vCPU 数量要与压测并发匹配

三、Buffer Pool 调优(官方:InnoDB Buffer Pool Configuration)

复制代码
# /etc/my.cnf(解释:Buffer Pool 是 InnoDB 性能第一参数)
[mysqld]
innodb_buffer_pool_size = 5G        # 专用 DB 机设物理内存 60~70%(8G 虚机给 5G)
innodb_buffer_pool_instances = 2     # 多实例减少互斥争用(>1G 才建议拆分)
innodb_buffer_pool_dump_at_shutdown = ON   # 关机时 dump 热点页
innodb_buffer_pool_load_at_startup = ON    # 启动时预热,避免冷启动抖动

-- ① 查看 Buffer Pool 配置(解释:8.0 可在线调整大小)
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SET GLOBAL innodb_buffer_pool_size = 6*1024*1024*1024;   -- 在线扩容(8.0 支持)

-- ② 计算命中率(解释:健康应 > 99%)
SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests';   -- 逻辑读
SHOW STATUS LIKE 'Innodb_buffer_pool_reads';            -- 物理读(未落 BP 直接读盘)
-- 命中率 ≈ 1 - reads / read_requests

-- ③ 查看 BP 内部状态(解释:LRU 年轻/老年代、脏页、空闲页)
SHOW ENGINE INNODB STATUS\G
-- 重点看 BUFFER POOL 段落:Free buffers / Database pages / Dirty pages / Hit rate
参数 建议
innodb_buffer_pool_size 专用 DB:物理内存 60~70%;共享机别超 50%
innodb_buffer_pool_instances BP > 1G 时拆 2~8 个,降低单锁争用
innodb_old_blocks_pct 老年代占比(默认 37%),防全表扫描污染
innodb_max_dirty_pages_pct 脏页占比阈值(默认 90),控制刷盘节奏

命中率低怎么办 :先确认是不是虚机内存被换出(vmstat 看 si/so),再考虑扩 BP。盲目加内存却让 OS swap,等于没调。


四、redo log 与刷盘调优(官方:InnoDB Redo Log)

图 3

复制代码
# /etc/my.cnf(解释:redo 大小与刷盘策略直接影响写性能与持久性)
[mysqld]
# 8.0.30+:用总容量参数(自动管理两个 redo 文件)
innodb_redo_log_capacity = 2G
# 5.7 / 8.0.30 之前:innodb_log_file_size + innodb_log_files_in_group
innodb_log_buffer_size = 16M          # 大事务可调到 32M
innodb_flush_log_at_trx_commit = 1    # 1=每次提交刷盘(最安全,默认)
innodb_flush_method = O_DIRECT        # 绕开 OS 缓存,避免双缓存
参数 取值权衡
innodb_flush_log_at_trx_commit=1 每次提交刷盘,宕机不丢数据(金融默认)
=2 提交只写 OS 缓存,每秒刷盘,OS 崩才丢
=0 每秒刷盘,mysqld 崩丢 1 秒(不建议生产)
innodb_flush_method=O_DIRECT 推荐,数据页不走 OS page cache

redo 太小的症状 :脏页还没刷完 redo 就被写满,触发激进刷盘(sharp checkpoint) ,表现为周期性性能毛刺。innodb_redo_log_capacity 太小时 Innodb_log_waits 上升,调大即可。


五、隔离级别与 MVCC(官方:InnoDB Multi-Versioning)

复制代码
-- 查看/设置隔离级别(解释:InnoDB 默认 REPEATABLE READ)
SELECT @@transaction_isolation;          -- 8.0(5.7 用 tx_isolation)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 各隔离级别行为
-- READ COMMITTED:每次快照读都读最新版本(MVCC 快照每次更新)
-- REPEATABLE READ:整个事务一个快照(可重复读,默认)
隔离级别 脏读 不可重复读 幻读 锁差异
READ COMMITTED 否 可能 可能 半一致读 ,UPDATE 只锁匹配行
REPEATABLE READ(默认) 否 否 基本避免 next-key lock 防幻读
SERIALIZABLE 否 否 否 所有普通 SELECT 加共享锁

图 4

MVCC 的意义 :普通 SELECT 是快照读 ,靠 undo 版本链取历史版本,完全不加锁 ,这就是 InnoDB 读写不互相阻塞的原因。只有 UPDATE/DELETE/SELECT...FOR UPDATE/LOCK IN SHARE MODE 这类当前读 才加锁。选 RC 还是 RR:高并发写多、且能接受"同一事务两次读可能不同"的业务,常用 RC(半一致读锁更少、死锁更少);金融账务类守 RR。


六、锁机制与锁等待排查(官方:InnoDB Locking)

6.1 锁类型

图 5

锁 作用范围 说明
记录锁 Record Lock 单行索引记录 当前读加在索引记录上
间隙锁 Gap Lock 索引记录之间的间隙 防插入,RR 下存在
临键锁 Next-Key Lock 记录 + 前面间隙 RR 下范围查询默认,防幻读
意向锁 IS/IX 表级 表锁与行锁的协同标记
自增锁 AUTO-INC 自增列 插入时序列化

6.2 排查锁等待(8.0 新视图)

复制代码
-- ① 正在等待的锁(解释:8.0 用 performance_schema.data_locks / data_lock_waits)
SELECT * FROM performance_schema.data_lock_waits\G

-- ② 谁持锁、谁等锁(解释:data_locks 看锁对象,结合 innodb_trx 看事务)
SELECT t.trx_id, t.trx_state, t.trx_started, t.trx_query,
       l.lock_type, l.lock_mode, l.lock_table, l.lock_index
FROM performance_schema.data_locks l
JOIN information_schema.innodb_trx t ON l.lock_trx_id=t.trx_id\G

-- ③ sys 一键看谁堵谁(解释:最常用)
SELECT * FROM sys.innodb_lock_waits\G
-- 输出:waiting_pid / blocking_pid / waiting_query / blocking_query / sql_kill_blocking_query

-- ④ 杀掉阻塞事务(解释:sys 直接给 KILL 语句)
-- 复制 sql_kill_blocking_query 列的值执行,如 KILL 12345;

6.3 锁等待参数与死锁

图 6

复制代码
-- 锁等待超时(解释:默认 50 秒,太长会卡业务;OLTP 调小)
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';   -- 50
SET GLOBAL innodb_lock_wait_timeout = 5;

-- 死锁检测(解释:默认 ON,发现循环等待立即回滚牺牲者)
SHOW VARIABLES LIKE 'innodb_deadlock_detect';       -- ON

-- 查看最近一次死锁详情(解释:Status 里 LATEST DETECTED DEADLOCK 段)
SHOW ENGINE INNODB STATUS\G
调优点 建议
innodb_lock_wait_timeout OLTP 5~10 秒,快速失败别死等
死锁检测 高并发小事务下可考虑,极高并发时反成瓶颈(8.0 可用并行控制)
长事务 锁持最久,innodb_trx 里 trx_started 过久的要查
索引缺失 UPDATE 无索引 → 锁全表/大量间隙,必加索引

锁等待的头号根因 :UPDATE/DELETE 的 WHERE 条件没走索引,导致锁扫描放大(RR 下连间隙一起锁),一个小更新堵一片。这和 11 篇索引设计直接关联。


七、长事务与 undo 监控

复制代码
-- ① 找长事务(解释:运行超过 60 秒的事务,锁持最久、undo 膨胀)
SELECT trx_id, trx_started,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS run_sec,
       trx_rows_modified, trx_query
FROM information_schema.innodb_trx
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60
ORDER BY run_sec DESC;

-- ② undo 与历史长度(解释:history list length 持续增长 = 有长事务回滚段)
SHOW ENGINE INNODB STATUS\G
-- TRANSACTIONS 段:History list length,持续涨说明有事务没提交/回滚
异常信号 含义 处理
History list length 暴涨 长事务撑住 undo,purge 跟不上 找长事务 KILL
锁等待超时频发 锁冲突激烈 加索引/缩短事务/调隔离级别
Buffer Pool 命中率 < 95% 内存不足或被换出 扩 BP / 关 swap

八、踩坑记录(实测汇总)

# 坑 现象 解决
1 Buffer Pool 设太大但虚机没预留内存 OS swap,命中率骤降 预留全部客户机内存 + vm.swappiness=1
2 redo log 太小 周期性性能毛刺、Innodb_log_waits 涨 调大 innodb_redo_log_capacity
3 innodb_flush_log_at_trx_commit=2 图快 OS 重启丢数据 金融坚持 =1,配合 O_DIRECT
4 UPDATE 没走索引 锁放大、大面积锁等待 WHERE 条件加索引(11 篇)
5 隔离级别用 RR 又不理解间隙锁 莫名其妙的插入阻塞 理解 next-key lock 或改 RC
6 锁等待超时 50 秒不改 业务卡 50 秒才报错 调到 5~10 秒快速失败
7 长事务不处理 history list length 暴涨、undo 膨胀 监控 trx_started,超时 KILL
8 5.7 配置照搬到 8.0.30+ innodb_log_file_size 报错/失效 8.0.30+ 改用 innodb_redo_log_capacity
9 冷启动 Buffer Pool 不预热 开机后慢查询爆发 开 dump/load_at_startup
10 快照读误以为会加锁 业务方不敢在高峰 SELECT 快照读 MVCC 不加锁,随便读

九、本文要点速记

  1. Buffer Pool 是第一参数:专用库 60~70% 内存,命中率 > 99%;命中率低先查 swap。
  2. redo log 先日志后数据 :innodb_redo_log_capacity(8.0.30+)别太小,否则周期性刷盘毛刺;flush_log_at_trx_commit=1 保持久。
  3. MVCC 快照读不加锁:普通 SELECT 靠 undo 读历史版本,读写不互斥;只有当前读才加锁。
  4. RR 用 next-key lock 防幻读,理解间隙锁;高并发写多可考虑 RC(锁更少)。
  5. 锁等待头号根因是 UPDATE 没走索引 :用 data_locks/data_lock_waits 或 sys.innodb_lock_waits 定位,KILL 阻塞事务,查长事务。

十、参考资料(官方正式文档)

主题 官方链接
MySQL 8.0 Reference Manual https://dev.mysql.com/doc/refman/8.0/en/
InnoDB Buffer Pool Configuration https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool.html
InnoDB Redo Log https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html
InnoDB Multi-Versioning https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.html
InnoDB Locking https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
Performance Schema Lock Tables https://dev.mysql.com/doc/refman/8.0/en/performance-schema-data-locks-table.html
sys Schema innodb_lock_waits https://dev.mysql.com/doc/refman/8.0/en/sys-innodb-lock-waits.html

本文章属于《MySQL 数据库全栈运维实战系列》性能调优篇。欢迎收藏、交流。

相关推荐
笃行3501 小时前
AI数据库的库内四层结构,KingbaseES 产品拆解
数据库
一只爱撸猫的程序猿1 小时前
我看着一个 AI 引擎在 14 天里,从"记忆"走进了"搜索"
人工智能·后端·架构
java_logo2 小时前
Docker 部署 Dify:轻松搭建 LLM 应用开发平台
运维·docker·容器
我是大猴子2 小时前
redis
数据库·redis·缓存
方便面不加香菜3 小时前
Docker Container -- 综合实战案例详解
运维·docker·容器
海洋之恒4 小时前
GitHub Actions 自动化:5 个真实运维提效案例
运维·自动化·github
苏宸啊5 小时前
MySQL(一)
数据库·mysql
2603_969694855 小时前
JDBC 基础学习笔记:API 核心原理解析与 CRUD 代码实操
数据库·笔记·mysql·学习方法·database·sql注入
这个DBA有点耶5 小时前
库内语义构建 vs 外挂向量库:业务语义缺失,才是RAG“胡说八道”的根源
数据库·mysql·aigc