ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

InnoDB缓冲池实战调优:从误判内存泄漏到精准运维

InnoDB缓冲池实战调优:从误判内存泄漏到精准运维 1. 从一次诡异的“内存泄漏”说起分清操作系统缓冲池与数据库缓冲池先讲个我上周遇到的事。群里有人发截图说Windows 11任务管理器显示“非分页缓冲池”占用高达4GB怀疑数据库把内存泄漏了疯狂重启MySQL问题依然存在都准备重装系统了。这个场景太典型了。很多人把“缓冲池”三个字直接等同于数据库的Buffer Pool一看到系统监控里出现“非分页缓冲池”“分页缓冲池”就慌了。实际上这是两套完全不同的东西。非分页缓冲池Nonpaged Pool和分页缓冲池Paged Pool是Windows操作系统内核的内存区域用来存放内核对象、设备驱动、文件系统过滤驱动比如杀毒软件、备份Agent等不能或者可以被换出到磁盘的数据。数据库的缓冲池是用户态进程自己的内存压根不归这里管。真正导致非分页缓冲池暴涨的通常是网卡驱动、杀毒软件冲突、某个存储驱动的BUG和MySQL、PostgreSQL关系不大。我当时的排查建议是打开任务管理器→性能→内存看“提交”和“已缓存”两个值再用PoolMonWindows驱动工具包自带或者RAMMap看具体是哪个tag占的。如果是NDIS、CM、FMfn这些tag基本就是网络驱动和文件系统过滤驱动的问题优先更新网卡固件、卸载不兼容的杀毒软件而不是去调数据库参数。不过这个案例也折射出一个深层次问题很多人对数据库缓冲池的内部运作机制并不真正了解遇到内存类问题才会两眼一抹黑。这个系列前面两篇讲了缓存替换策略和淘汰算法这篇我打算换个角度专门聊聊我在实际项目里最常用到的几个缓冲池运维场景如何判断InnoDB缓冲池是否需要调大、怎么预热、脏页刷盘节奏怎么控制、以及那些容易被误诊为“缓冲池问题”的并发和死锁故障。这一篇比较适合正在做数据库调优的DBA、后端开发以及那些想弄明白“为什么我配了10G缓冲池线上还是不快”的人。看的时候建议打开自己的数据库对照着查比单纯看文字有效得多。2. 实战第一步正确评估当前缓冲池状态2.1 别只看命中率这个指标会骗人我见过太多人拿着show global status like Innodb_buffer_pool_read_requests和Innodb_buffer_pool_reads算命中率算出99.99%就欢天喜地。但是我要泼一盆冷水缓冲池命中率在高并发读场景下极具迷惑性。原因在于Innodb_buffer_pool_read_requests统计的是逻辑读次数也就是InnoDB层发起的页面读取请求页面可能来自缓冲池也可能来自磁盘。而Innodb_buffer_pool_reads只统计从磁盘实际读取的页面次数。如果业务是典型的“热点数据高度集中”模式——比如只有几百条商品SKU被疯狂查询——就算缓冲池只有1GB命中率也能跑到99%以上。但实际上你可能把90%的冷数据都漏在了外面一旦热点发生偏移比如大促换了活动商品性能马上崩。我更推荐的做法是结合几个维度一起看每秒磁盘读次数Innodb_buffer_pool_reads除以采样间隔长期高于几十次/秒说明有大量页面在磁盘和缓存之间来回搬家。每秒逻辑读次数Innodb_buffer_pool_read_requests除以采样间隔这个值反映业务真实读压力。脏页比例Innodb_buffer_pool_pages_dirty除以Innodb_buffer_pool_pages_total超过75%就要小心刷盘跟不上。缓冲池命中率趋势看至少一周的曲线变化而不是只看某个时间点的值。如果命中率高但磁盘IO仍然很忙通常是以下几种情况在作怪存在全表扫描一次性把大量页面拉进缓冲池把热点挤出去临时表走磁盘频繁使用文件排序自适应哈希索引失效导致频繁索引下探日志写入redo log和双写缓冲占用了IO资源。2.2 用performance_schema深度定位页面访问想要真正定位哪些表在频繁读取页面MySQL 8.0的performance_schema是个好东西。schema_tables这张表可以查到每个表的IO统计但直接看原生表比较吃力我习惯把常用查询存成视图方便日常巡检。-- 查看每个表的缓冲池逻辑读与物理读情况 SELECT OBJECT_SCHEMA, OBJECT_NAME, SUM(COUNT_READ) AS total_logical_reads, SUM(SUM_NUMBER_OF_BYTES_READ) AS total_bytes_read, SUM(COUNT_FETCH) AS total_page_fetches FROM performance_schema.table_io_waits_summary_by_table WHERE OBJECT_SCHEMA NOT IN (mysql, performance_schema, information_schema, sys) GROUP BY OBJECT_SCHEMA, OBJECT_NAME ORDER BY total_page_fetches DESC LIMIT 20;注意COUNT_READ是逻辑读累计值COUNT_FETCH是物理读从磁盘拿页面累计值。如果某张表COUNT_FETCH和COUNT_READ的比值偏高意味着这张表的数据经常“不在缓存里”要么是缓冲池装不下要么是缓冲池被其他表挤占了。另一个容易被忽略的信息源是sys库的innodb_buffer_stats_by_schema视图它会按schema统计缓冲池中页面的分布占比。假设你的业务库占90%日志库占8%系统库占2%那说明缓冲池大小和业务访问模型基本匹配。如果某个归档库、日志库占了40%以上的缓冲池空间问题大概率出在“缓存污染”——低频数据把高频数据挤出了缓冲池。针对缓存污染我常用的两个手段开启innodb_buffer_pool_size自动扩展MySQL 8.0.30支持动态调整按业务高峰期动态加内存低峰期回收。适当调低innodb_old_blocks_time这个参数控制数据页进入old sublist后需要多久才能“转正”到young sublist。默认值1000毫秒如果大量大表扫描导致缓存污染可以提高到2000毫秒甚至5000毫秒让全表扫描进来的页面快速被淘汰掉。2.3 InnoDB缓冲池的内存占用到底怎么算很多人设置innodb_buffer_pool_size8G然后发现MySQL进程实际占用内存超过10G有点慌。这是正常的因为InnoDB缓冲池并不是一整块裸内存它由多个部分组成数据页page默认16KB这是主体索引页同样在缓冲池里和数据页共用空间自适应哈希索引AHI占用一部分额外内存锁信息lock info每个页面会有对应的锁结构变更缓冲区change buffer占用缓冲池的一部分默认最多25%数据字典缓存表结构、字段元数据各种控制块control block每个页面在缓冲池里有一个对应的控制结构约数百字节。所以实际内存占用公式大概是buffer pool大小 5%~10%的额外开销 连接线程栈 排序缓冲 连接缓冲。如果开了performance_schema还要额外加10%左右的开销。我见过一个配置不当的案例服务器物理内存16GMySQL配置了12G的缓冲池还开了performance_schema结果OOM killer直接把mysqld进程杀了。所以我的建议是缓冲池保守设置为物理内存的50%~60%并预留足够给OS page cache因为逻辑备份、临时表、文件排序同样会吃page cache。3. 缓冲池的大小与预热从“足够”到“精准”3.1 到底应该设多大先跑一周再决定很多优化文章喜欢给一个固定比例比如“缓冲池设为内存的70%”。我认为这种经验值只能作为起点真正合理的大小要看你业务数据的热点集有多大。判断方法是连续一周采集Innodb_buffer_pool_pages_free和Innodb_buffer_pool_pages_total的比例也就是空闲页占比。-- 查看缓冲池空闲页比例 SELECT innodb_buffer_pool_size AS buffer_pool_size, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME Innodb_buffer_pool_pages_free) AS free_pages, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME Innodb_buffer_pool_pages_total) AS total_pages, ROUND((SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME Innodb_buffer_pool_pages_free) / (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME Innodb_buffer_pool_pages_total) * 100, 2) AS free_ratio;如果free_ratio长期高于20%说明缓冲池还有冗余不需要扩容如果长期低于5%且磁盘IO繁忙说明缓冲池不够用了。这里要特别注意空闲页比例低并不绝对等于不够用——有些高并发系统刻意让缓冲池保持高利用率因为热数据确实很多。你需要结合磁盘读延迟和TPS来看。如果空闲页低但磁盘IO压力不大可以观察如果空闲页低同时Innodb_data_reads飙升那就必须扩容了。在MySQL 8.0中可以用SET GLOBAL innodb_buffer_pool_size xxx动态调整不需要重启。但要记住调整时会触发整个buffer pool的resize操作会阻塞当前请求。官方实际上是把旧的buffer pool实例逐步替换成新的实例过程会对DML有短暂影响。我一般选择业务低峰期调整或者直接改配置文件后计划内重启。3.2 使用innodb_buffer_pool_dump_now快速预热这是MySQL自带的一个“热启动”能力原理是记录当前缓冲池中的页面编号和LRU列表位置在数据库重启后自动加载这些页面。-- 手动触发dump SET GLOBAL innodb_buffer_pool_dump_now ON; -- 查看dump进度 SHOW STATUS LIKE Innodb_buffer_pool_dump_status; -- 重启后加载进度 SHOW STATUS LIKE Innodb_buffer_pool_load_status;innodb_buffer_pool_dump_at_shutdown和innodb_buffer_pool_load_at_startup默认都是OFF生产环境我建议开启。但有一个坑必须提醒不要把innodb_buffer_pool_dump_pct设到100。它控制dump时记录多少比例的页面默认25%实际上记录25%的LRU头部页面已经能覆盖绝大多数热数据。如果设100%dump文件会巨大加载时间会很长等于启动变慢。还有一个经验大促前如果重启过数据库可以手工SET GLOBAL innodb_buffer_pool_load_abort OFF;等待加载完成后再接流量返回或者在应用层做流量预热——启动后先放少量请求让热点页面逐步load进内存而不是一股脑放全量流量进去。3.3 预热不是万能药配合LRU策略才是完整方案预热只能解决“重启后快速恢复缓存”的问题不能解决“热点数据本身不集中”的结构性问题。如果你的业务有大量低频大表扫描这些扫描进来的页面会把真正高频的热数据挤掉。这时候应该考虑设置innodb_old_blocks_time。我推荐一个比较通用的起始值innodb_old_blocks_time1000是MySQL默认值但如果你的场景是“定期跑报表、批量任务和在线业务共存”可以调到2000~5000防止报表扫描污染主业务缓存。如果调整之后依然严重就要考虑在SQL层面做控制了大表的报表查询走只读备库不要和在线交易共用同一个实例用SELECT ... INTO OUTFILE代替大量SELECT *回表分批处理大范围查询避免一次性扫描几百万行。4. 脏页刷盘与IO抖动比命中率更影响体验的环节4.1 脏页比例过高为什么会让性能“抽风”脏页是缓冲池中被修改但尚未写入磁盘的数据页。InnoDB为了保证持久性会在适当时候把脏页刷到磁盘这个过程叫flush。如果脏页太多或者刷盘过于集中磁盘IO会突然飙到接近百分之百导致当时正在执行的查询全部变慢——这就是典型的IO抖动。Innodb_buffer_pool_pages_dirty状态值可以实时查看脏页数量。还有一个重要指标是Innodb_buffer_pool_wait_free这个值表示“server因为找不到干净页而等待刷盘”的次数。如果这个值在监控里不断增长说明刷盘速度跟不上脏页产生速度。影响因素有这几个innodb_max_dirty_pages_pct默认值是75%MySQL 8.0中改成了动态调整表示脏页占比达到这个阈值时会加大刷盘力度。调低可以让刷盘更频繁、更平均代价是磁盘IO总开销变大。innodb_io_capacity表示InnoDB期望的磁盘IOPS上限。如果设太低比如默认200脏页刷盘会被限速脏页越积越多。如果设太高可能把磁盘IO全吃光。机械盘建议200~400SATA SSD建议1000~2000NVMe SSD可以到5000以上。innodb_flush_neighbors如果设置为ON刷盘时会顺带把相邻页面一起刷出去适合机械盘因为顺序IO比随机IO快得多但对SSD来说纯属浪费建议关闭。我之前处理过一个客户案例他们的监控看板每10分钟采样一次Innodb_buffer_pool_pages_dirty发现每天都在凌晨2点左右飙到80%以上紧接着IO延迟从2ms涨到200ms持续半个多小时。最后定位到是定时任务在凌晨两点触发了一个超大批量UPDATE几百万元数据连续修改产生了大量脏页。当时做了这三个改动innodb_io_capacity从200调到2000服务器是SSDinnodb_max_dirty_pages_pct从75%调到50%让刷盘提前开始、分散压力定时任务改成小批量循环每批5000行sleep 0.1秒再继续避免一次性制造海量脏页。改动之后凌晨的IO抖动基本消失任务总时长增加了17%但主业务完全不受影响。4.2 redo log容量脏页刷不过来的隐形瓶颈很多人会忽视redo log的大小。实际上redo log空间决定了数据库能容纳多少未刷盘的数据变更。如果redo log太小InnoDB还没来得及把脏页刷到磁盘redo log就被写满了这时候会强制同步刷脏页表现为“一卡一卡”的周期性停顿。MySQL 8.0.30之前redo log文件数量和大小的控制参数是innodb_log_file_size和innodb_log_files_in_group8.0.30之后改成了innodb_redo_log_capacity默认100MB动态调整。我建议至少设置1GB~4GB取决于写入压力。判断当前redo log是否够用可以查-- 查看redo log总量和已使用量MySQL 8.0.30 SELECT innodb_redo_log_capacity AS total_capacity, innodb_redo_log_current_use AS current_use, ROUND(innodb_redo_log_current_use / innodb_redo_log_capacity * 100, 2) AS use_ratio;如果current_use长期高于75%说明redo log容量偏小需要扩容。扩容操作在8.0里是动态的SET GLOBAL innodb_redo_log_capacity 4G;不需要重启。4.3 使用双写缓冲doublewrite会降低性能但别随便关这个点特别容易踩坑。InnoDB的doublewrite机制是为了解决“部分页面写入”torn page问题——如果数据库在写页面到磁盘的途中断电16KB的页面可能只写了一半重启后无法恢复。doublewrite会把页面先写入一个连续的doublewrite buffer区域再写入实际位置保证极端情况下的可恢复性。innodb_doublewrite默认ON。很多性能优化文章建议关闭它以节省磁盘IO但这是极其危险的做法。尤其在云主机、SSD、RAID卡带电池保护的环境下看起来关闭后性能有5%~10%的提升但一旦发生断电或者主机crash数据库可能直接损坏无法启动。我的经验是生产环境永远不要关闭doublewrite。如果确实在意性能可以考虑把doublewrite文件放在更快的存储上比如独立的NVMe盘或者购买支持原子写Atomic Write的企业级SSD后才可以考虑关闭。注意使用ZFS文件系统时由于ZFS本身在块级别有校验和机制关闭doublewrite是相对合理的。但如果你不是存储专家别玩这个。5. 连接池与并发锁那些被误诊为“缓冲池不足”的故障5.1 MySQL连接池缓冲池再大也扛不住连接风暴接下来从缓冲池往外走一步聊聊另一个高频名词——连接池。在热词里出现“MySQL数据库连接池”这跟缓冲池是两件事但故障表现很相似CPU不高、内存不高但业务就是慢查询全部堆积。根本原因通常是应用层连接池配置不合理。连接池并不是越大越好每个连接都会占用线程栈默认2MB、排序缓冲、临时表内存同时每个会话的查询都会消耗缓冲池的页面访问配额。如果并发从100涨到500即使缓冲池足够大锁等待和上下文切换也会拖垮性能。这是我常用的连接池配置原则连接池上限 服务器核心数 × 2 磁盘IO并发数有效磁盘IO并发通常很小大概2~4初始连接数不要设太高让连接池按需缓慢增长maxWait获取连接的最大等待时间不要设成无限否则线程会在获取连接时全部阻塞最终拖垮应用服务器空闲连接回收时间要短于数据库wait_timeout否则会积累大量无用的半开连接。举个例子一台32核的数据库服务器应用并发需要300个连接合理配置是核心数×264再加上少量预留取80~100。如果你配到500InnoDB内部的row lock waits和meta data locks冲突概率会成倍增加。5.2 死锁和锁等待缓冲区越大锁冲突暴露越快死锁和锁等待也是热词里的高频话题。有一个容易被忽视的规律当缓冲池足够大、磁盘IO不再是瓶颈时锁冲突反而会成为新的瓶颈。因为所有查询都在很快地读取数据并尝试获取锁锁的竞争频率就变高了。排查死锁的标准姿势是打开死锁日志SHOW ENGINE INNODB STATUS;关注LATEST DETECTED DEADLOCK部分它会显示正在执行的SQL、持有的锁、等待的锁以及被回滚的事务。常见的死锁模式是两个事务以不同顺序更新同一组记录比如先更新A再更新B另一个先更新B再更新A间隙锁与插入意向锁冲突RR隔离级别下最典型唯一键冲突导致事务回滚时锁的释放顺序不当。解决手法通常是这几个确保所有事务以相同顺序访问资源先A后B尽量减小事务体让持有锁的时间更短如果只是排查可以用innodb_lock_wait_timeout控制等待时长默认50秒太长了线上我一般设5秒宁可让超时报错也不让线程无限挂起定期用performance_schema.data_lock_waits视图查看锁等待关系比死锁日志更直观。另外一定要配好监控告警。我见过凌晨三点被电话叫起来处理死锁当时数据库里积压了几千个等待锁的会话所有应用线程卡死缓冲池命中率跌到60%——并不是缓冲池问题而是横跨多个业务模块的各个事务互相等锁导致的连锁阻塞。因为每个事务在等待锁时会占着事务开启时的读视图导致undo log无法purge版本链越来越长查询越跑越慢恶性循环。5.3 大事务是缓冲池的“慢性毒药”大事务的影响很难被直观发现但它会同时拉低命中率、放大锁竞争、拖慢purge。一个UPDATE影响100万行的超大事务在做回滚或者提交前它会一直持有对这些行的锁其他事务的更新全部排到它后面。更隐蔽的是大事务产生的undo log不会在事务提交后立刻被purge而是要等待所有读视图ReadView过期后才清理。如果有个长连接一直不提交查询那么它持有的读视图会让undo log无限增长最终把整个undo tablespace撑大缓冲池还要为这些undo页腾出空间。判断是否有大事务的方式-- 查看当前事务的执行时间MySQL 8.0 SELECT trx_id, trx_state, trx_started, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration_seconds, trx_mysql_thread_id FROM information_schema.innodb_trx WHERE trx_state RUNNING ORDER BY duration_seconds DESC LIMIT 10;超过60秒的事务就要警惕了生产环境的大事务我一般要求控制在几秒内完成。如果需要对大批量数据操作用分批循环替代单条大事务例如每次UPDATE 1000行循环执行。6. 工具链与生态那些被缓冲池掩盖的日常操作问题6.1 数据库工具的选择管理、同步、迁移热词里出现了一堆工具名dbx数据库工具、数据库同步软件、excel导入数据库、Navicat连接不了等等。这些看似和缓冲池没关系但实际运维中工具选择不当直接影响你能否正确判断缓冲池状态。我平时用的工具有这么几类命令行优先mysql客户端 sys库的视图这是最准确的图形化工具Navicat、DBeaver、DataGrip都行。Navicat胜在界面顺手DBeaver开源免费且支持几乎所有数据库类型性能监控Prometheus mysqld_exporter Grafana是我最推荐的组合可以持续收集Innodb_buffer_pool_*系列指标画出趋势图。至于“Navicat连接不了数据库”大多数情况下不是数据库本身的问题而是这几类原因用户权限不对账号只授权了localhostMySQL 8.0默认认证插件是caching_sha2_password旧版Navicat不支持11.x以下bind_address没配置允许远程连接防火墙云安全组没有放行3306端口。其中第2条特别坑。如果你的MySQL是8.0以上、Navicat是老版本连接时直接报Authentication plugin caching_sha2_password cannot be loaded。解决办法是改用户的认证插件ALTER USER your_user% IDENTIFIED WITH mysql_native_password BY your_password;注意这会有安全性降级MySQL 8.0官方推荐的是caching_sha2_password。如果Navicat版本够新直接升级客户端就好。6.2 数据库同步与数据迁移的缓冲池效应热词里经常有人搜“数据库同步工具”“数据库同步软件”。做同步时一个常见误区是认为同步就是把数据导过去和缓冲池没有关系。实际上同步过程中对源库的SELECT压力非常大如果同步工具写得很粗糙不加limit、不走索引很容易触发全表扫描进而污染源库的缓冲池把热数据挤掉。我踩过一个大坑用某同步工具从生产MySQL实时同步数据到另一个MySQL做统计该工具每5秒执行一次全量轮询单表几百万行每次扫描都会把源库的缓冲池LRU列表刷一遍导致真正的在线交易查询全部missIO飙高业务响应时间从10ms涨到500ms。后续方案是同步改基于binlog的增量同步比如canal、Maxwell、Debezium而不是轮询全表如果只能用轮询查询加上WHERE id last_max_id配合索引源库设置innodb_old_blocks_time5000让这类扫描页面进不了young区域。6.3 其他数据库的缓冲池顺便看懂PostgreSQL与SQLite热词里出现PostgreSQL、SQLite。补充一下缓冲池概念在它们那边的对应物方便你横向理解。PostgreSQL没有严格意义的“缓冲池”概念它的Shared Buffers是共享内存里的页缓存默认只有128MB。相比MySQLPostgreSQL更依赖操作系统page cache所以优化手段不太一样。调整shared_buffers到物理内存的15%~25%比较合适同时要确保effective_cache_size反映的OS缓存容量要够大否则优化器会错误地认为索引读取很贵而选择错误的执行计划。SQLite是个极致的单文件数据库它实际上把整个数据库文件当作一个内存映射来处理。如果你用SQLite做高并发应用一定会碰上“数据库被锁定”的问题——这不是缓冲池不够而是它根本不支持高并发写入。SQLite适合做本地缓存、嵌入式存储不适合做服务端主库。工具层面用DB Browser for SQLite就够用。6.4 TDengine等时序数据库的写入路径热词里出现了TDengine、taos_stmt_prepare、C绑定写入。TDengine的模型和MySQL很不一样它以“超级表-子表”的方式组织时序数据写入走的是追加模型天然对缓存友好。对于TDengine我用C绑定写入时特别注意taos_stmt_prepare配合参数绑定能显著减少SQL解析开销。这与MySQL的prepared statement思路类似都是为了降低每次写入的解析和规划成本。如果你也在做TDengine的C写入一个重要心得是尽量使用批量写入一次写入多行而不是一条一条插入。批量写入可以在网络层和存储层都获得显著收益。我见过有人用循环单条insert写100万条数据耗时十几分钟改成批量2000条之后几十秒就完成了。7. 常见问题与排查技巧实录7.1 问题速查表我把这些年遇到的高频问题整理成一个表格方便你对照排查现象可能原因排查命令解决思路内存持续上涨且不回落缓冲池中脏页刷不出去或pfs内存开销过大SHOW ENGINE INNODB STATUS看脏页与log调整io_capacity、清理performance_schema检查大事务磁盘IO周期性飙高脏页集中刷盘或redo log太小触发同步刷SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_wait_free调低max_dirty_pages_pct、调大redo log容量重启后前几分钟特别慢缓冲池冷启动热点页面没加载查Innodb_buffer_pool_load_status开启dump_at_shutdown/load_at_startup命中率很高但查询慢锁等待、临时表、大事务performance_schema锁等待视图检查大事务、慢SQL查询全部卡死连接池耗尽或死锁风暴SHOW PROCESSLIST调连接池上限、死锁SQL优化错误日志出现“out of memory”内存分配超额系统日志调innodb_buffer_pool_size预留OS page cache单表数据量极大但热点很少LRU被冷数据污染sys.innodb_buffer_stats_by_table调高innodb_old_blocks_time业务隔离7.2 踩过的三个坑第一个坑千万不要盲目相信“经验值”。网上很多人说缓冲池设物理内存的70%我照着配过一台16G的服务器结果系统OOM。后来发现服务器上还跑着监控Agent、日志采集、ELK的Beat进程这些加起来吃掉了3G内存。配置缓冲池前一定要先了解这台机器上所有常驻进程的内存占用。第二个坑innodb_buffer_pool_instances不是越大越好。在MySQL 5.7时代缓冲池拆分成多个实例可以减少并发访问时的锁竞争默认是CPU核心数。但拆分的本质是每个实例有独立的LRU链表和mutex实例过多会导致内存碎片化并且某些场景下跨实例的并发访问反而增加开销。在MySQL 8.0中如果缓冲池小于8G建议保持默认1个实例大于8G后再按每实例不低于1G来设置。第三个坑使用动态调整缓冲池大小时务必在业务低峰期执行。我曾经在下午三点执行SET GLOBAL innodb_buffer_pool_size16G结果所有查询阻塞了将近10秒。虽然官方文档说8.0支持动态调整但它的实现方式是逐步把旧缓冲池中的页面迁移到新缓冲池期间有锁和内存拷贝。如果对停机敏感就别在高峰期玩这个。7.3 一套我自己在用的巡检SQL最后分享一套我日常巡检缓冲池的SQL片段。这套SQL会输出一组关键指标我习惯把它存成Shell脚本每天早上10点跑一次输出到日志里。#!/bin/bash mysql -uroot -p****** -e SELECT NOW() AS sample_time, innodb_buffer_pool_size / 1024 / 1024 AS buffer_pool_mb, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_pages_total) AS total_pages, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_pages_free) AS free_pages, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_pages_dirty) AS dirty_pages, ROUND((SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_pages_free) * 1.0 / (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_pages_total) * 100, 2) AS free_pct, ROUND((SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_pages_dirty) * 1.0 / (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_pages_total) * 100, 2) AS dirty_pct, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_wait_free) AS wait_free_count, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_reads) AS physical_reads, (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAMEInnodb_buffer_pool_read_requests) AS logical_reads; 输出里如果free_pct长期低于10%dirty_pct长期高于60%wait_free_count在增长就要警惕了。8. 最后分享一点个人体会写到这里数据库缓冲池该聊的实操层面内容基本都聊到了。从这么多年处理各种生产事故来看我觉得最核心的一句话是缓冲池优化不能脱离业务访问模式去谈。命中率、脏页比例、redo log容量、LRU策略、连接并发这些指标从来不是孤立存在的。你只有把系统的全貌搞清楚从SQL到执行计划再到缓冲池的页面分布最后到磁盘IO的节奏才算真正在调优而不是在盲目调参。我在实际项目中后期几乎不会再去调大缓冲池了反而会花更多时间审视业务SQL、索引设计和事务边界。很多时候你以为缺内存实际缺的是一把好用的索引你以为要扩缓冲池实际要砍掉的是那个每天凌晨三点全表扫描的定时任务。最后再分享一个小技巧给缓冲池做一个每月一次的压力测试。找一台测试机灌入和线上差不多大的数据量然后模拟在线流量和批量任务的混合负载用dstat和pt-query-digest观察指标看看在脏页率60%的时候系统是否还能稳定扛住。这种提前演练比等到线上出问题再抢救要省心得多。数据库调优这件事没有终点你只能不停地把系统往前推一点再多推一点。
返回列表