1.背景
数据库配置:8C32G(阿里云polarDB)
系统配置:4C8G12个实例
使用场景:调度任务数据表存在大量更新删除操作
2.问题
2月5号晚上数据库的内存达到96%出现告警,DBA拉齐开发做扩容。
联系阿里云给出原因如下: 整体的buffer pool占用如下:
Buffer Pool Size: 1703936 页(每页大小通常为 16KB,因此总大小约为 26.6GB)。
Free Buffers: 16181 页(约 252MB),表示当前空闲的缓冲区页面数。
Database Pages: 1685791 页(约 26.3GB),表示当前被使用的数据库页面数。
Old Database Pages: 622128 页(约 9.7GB),表示旧页(Old Sublist)的数量。
Modified DB Pages: 56066 页(约 876MB),表示已被修改但尚未写入磁盘的页面数。
分析:
Buffer Pool 使用率:Database Pages / Buffer Pool Size = 1685791 / 1703936 ≈ 99%,表明 Buffer Pool 几乎被完全占用,空闲缓冲区非常少(仅 16181 页)。
Old Pages 占比:Old Database Pages / Database Pages = 622128 / 1685791 ≈ 36.9%,表明旧页占据了较大比例。
Modified Pages 占比:Modified DB Pages / Database Pages = 56066 / 1685791 ≈ 3.3%,表明脏页比例较低。
3.原因
当前 Buffer Pool 的空闲空间freeList较少,而旧页占据了较大的比例。
性能瓶颈:当新数据需要加载到 Buffer Pool 时,可能需要频繁淘汰旧页,增加 I/O 开销。
资源浪费:旧页占据较多空间,但可能并未被频繁访问,导致缓存效率降低。
解决方案: 为了减少旧页空间并增大空闲页空间,可以从以下几个方面进行调整:
-
调整 innodb_old_blocks_pct 参数 作用:该参数控制旧页子列表(Old Sublist)占 Buffer Pool 的比例,默认值为 37%。 建议调整:将 innodb_old_blocks_pct 降低到 20%-30%,以减少旧页占用的空间,从而释放更多空间给新页和空闲页。 SET GLOBAL innodb_old_blocks_pct = 25; 注意事项:调整后需观察性能变化,确保不会因旧页过少而导致频繁的页淘汰。
-
增加 Buffer Pool 大小 作用:如果服务器内存充足,可以增加 Buffer Pool 的大小,从而提供更多空闲空间。 建议调整:通过修改 innodb_buffer_pool_size 参数来增加 Buffer Pool 的容量。例如,将其从 26.6GB 增加到 32GB。 SET GLOBAL innodb_buffer_pool_size = 34359738368; -- 32GB 注意事项:增加 Buffer Pool 大小时需确保服务器有足够的物理内存,避免因内存不足导致系统性能下降。
-
监控和优化查询 作用:某些低效查询可能导致大量数据加载到 Buffer Pool 中,进而占用过多空间。 建议调整: 使用慢查询日志或性能监控工具(如 EXPLAIN、Performance Schema)分析查询性能。 优化索引设计,减少不必要的全表扫描。 调整查询逻辑,避免一次性加载过多数据。 (以上解决方案来自DeepSeek)
最终决策:扩容buffer pool:8C64G
4.理解InnoDB
4.1.内存缓冲池Buffer pool
Buffer Pool 是数据库管理系统的核心组件,用于缓存磁盘数据页以减少 I/O 操作
内存划分结构
内存管理流程
1. 初始状态:页加载
-
从 Free List 分配空闲页
当需要加载磁盘数据页到 Buffer Pool 时:-
检查 Free List 是否有空闲页。
-
若有空闲页,直接取出分配给新页,并插入 LRU List 的 Old 区(冷数据区)。
-
若 Free List 为空,触发 页面替换 机制(见下文)。
-
2. 页面访问与热度管理(LRU List)
-
访问页时的状态变化
-
当页首次被访问时,可能停留在 LRU Old 区。
-
若短时间内被多次访问(例如,通过
innodb_old_blocks_time阈值控制),则将其移动到 LRU Young 区(热数据区)。 -
每次访问会调整页在 LRU List 中的位置,避免冷数据占据内存。
-
-
LRU 链表的冷热分区
-
Young 区:存放高频访问的热数据页,替换优先级最低。
-
Old 区:存放低频访问的冷数据页,替换时优先淘汰。
-
3. 页面替换(Free List 耗尽时)
当 Free List 无空闲页,需通过 LRU List 淘汰冷页以腾出空间:
步骤 1:选择淘汰页
-
从 LRU Old 区尾部 选择最近最少使用的页(冷页)作为候选。
-
若页的
pin count > 0(被事务占用),跳过并选择下一个页。
步骤 2:处理脏页
-
若淘汰页是脏页(修改未刷盘):
-
将其从 LRU List 移除,并加入 Flush List(若尚未加入)。
-
触发 异步刷盘(Async Flush):后台线程将脏页写入磁盘。
-
刷盘完成后,该页变为干净页,移出 Flush List,并释放到 Free List 中。
-
步骤 3:复用空闲页
- 空闲页(来自 Free List)被分配给新加载的磁盘页,插入 LRU Old 区。
4. 脏页刷盘(Flush List 管理)
-
Flush List 的作用
记录所有待刷盘的脏页,按修改时间(LSN)排序,确保崩溃恢复时数据一致性。 -
刷盘触发条件
-
Checkpoint 机制:定期(如每秒)或日志空间不足时触发批量刷盘。
-
LRU 淘汰脏页:淘汰脏页时强制刷盘(同步或异步,取决于策略)。
-
用户主动刷盘:如执行
FLUSH TABLES命令。
-
(以上流程来自DeepSeek)
4.2.线程模型
InnoDB是多线程的模型,整个引擎后台有多个不同的后台线程负责处理不同的任务,后台线程梳理如下:
Master Thread
-
核心后台线程:
- 负责脏页刷新
- checkPoint刷新
- Undo Log 回收
- Redo Log刷新
- 合并 Change Buffer(非主键索引会使用changeBuffer,该区域的作用是对普通索引的插入和更新起到加速的作用,将更新或者插入非主键索引的操作在先写入change Buffer缓冲区,后续查询可以直接操作该缓冲区的数据。该作用后期由Page Cleaner Thread接管,但Master Thread 仍然可能会在某些情况下参与插入缓冲的管理,例如在系统启动初期或者负载较低时,Master Thread 可能会直接处理一些插入缓冲的合并操作。)
-
工作周期:每 1 秒或 10 秒执行特定任务(如刷盘) InnoDB会判断缓冲池中脏页的比例(buf_get_modified_ratio_pct)如果超过70%则刷新100个脏页到磁盘,如果脏页比例小于70%,则只需要刷新10%的脏页到磁盘。
IO Thread
InnoDB中大量使用AIO(Async IO)来处理IO请求,这样可以加大提高数据库的性能。IO Thread的工作主要负责这些IO请求的回调处理。IO线程又分为多种线程,如下:
-
读线程(Read Thread):处理数据页的异步读请求。
-
写线程(Write Thread):处理脏页的异步写回。
-
插入缓冲线程(Insert Buffer Thread):处理插入缓冲的写入
-
日志线程(Log Thread):处理Redo Log的异步写回Redo Log File
Purge Thread
- 职责:清理已提交事务不再需要的 Undo Log。
Page Cleaner Thread
Page Cleaner Thread是在InnoDB1.2.X版本引入的。
- 职责:
- 脏页刷新:将之前版本中脏页的刷新操作都放到单独的线程完成,减轻Master Thread的工作,提高InnoDB的性能。
- 合并插入缓冲:处理插入缓冲区中的数据merge到非聚集索引中