MySQL缓存机制全解:从Buffer Pool到应用层缓存策略
2026/9/16 2:09:17 网站建设 项目流程

数据库变慢的时候,大多数人的第一反应是什么?加索引,或者加机器。但很多时候,你还没到需要升级硬件那一步。MySQL本身的缓存策略就没配置到位,大量的热数据根本没被有效地留在内存里,磁盘IO成了瓶颈,SQL再优化也快不起来。我做了这么多年数据库相关的开发和运维,几乎每次性能排查走到最后,都会落到缓存策略上。今天这篇,就把MySQL缓存这条线从头到尾捋一遍——从它内部的Buffer Pool,到已经进了历史课本的Query Cache,再到业务系统真正常用的应用层缓存方案,都聊透。

这篇东西适合谁看?一类是刚接手项目、发现数据库CPU和磁盘IO经常报警的后端开发,另一类是准备面试、经常被问到MySQL缓存机制和优化思路的求职者。读完你至少能搞清楚MySQL有哪些层级的缓存、每个缓存该调到多大、缓存命中率怎么计算、以及为什么现在的互联网项目基本都在应用层再做一道缓存。除了原理,我还会把实际运维中踩过的坑和排查手段一并写出来。

1. MySQL缓存体系:先看清“房子”里有哪些抽屉

1.1 从内存视角重新认识MySQL

我们平时聊MySQL优化,总是习惯性先看慢查询日志、再看SQL执行计划,这当然没错。但如果对MySQL的内存工作方式没概念,很多优化做起来就是隔靴搔痒。MySQL其实是一个典型的“吃内存”的数据库,它把数据放在磁盘上,但读写都尽量在内存里完成。你可以把MySQL的内存想象成厨房里的操作台——菜都放在冰箱(磁盘)里,但做菜的时候你不会一趟趟跑冰箱,而是把常用的食材和工具提前摆到操作台上。缓存策略,本质上就是决定怎么摆这个操作台、摆多少东西上去。

MySQL的缓存分散在好几个层级。最底层的是操作系统的文件缓存,只要你不强制开启innodb_flush_method=O_DIRECT绕过它,系统会自动把热点的数据文件页缓存起来。再往上,是MySQL自身各存储引擎和Server层的缓存。InnoDB引擎有Buffer Pool、Log Buffer,MyISAM引擎有Key Buffer,Server层曾经还有Query Cache,还有表结构缓存、线程缓存、排序缓冲、连接缓冲等等一堆东西。

这么多缓存在实际排障时,优先级是完全不同的。我自己看一个MySQL实例,99%的精力都花在InnoDB Buffer Pool上,偶尔看下Log Buffer和表缓存。其他的缓存要么影响面太小,要么在8.0版本已经彻底删掉了。理解这层关系很重要——缓存优化不是把每个参数都调一遍,而是盯住最核心的瓶颈点。

1.2 哪些缓存值得你花时间去调

我在这直接把两类缓存摊开讲清楚:一类是值得死磕的,一类是基本别碰的

值得花心思的,首推InnoDB Buffer Pool。它负责缓存数据页、索引页、插入缓冲、锁信息等,是InnoDB读写数据的主要中转站。你可以把它看作是数据库的“主存工作区”,几乎所有热数据的读写都经过它。如果这个池子太小,热数据根本放不下,每次查询都得走磁盘,那性能必然稀碎。相关的配置参数也很多,包括大小、实例数、预热方式、刷脏策略等等。

其次是Log Buffer,也就是重做日志缓冲。它负责暂存事务提交时产生的redo log,避免每次提交都立刻写磁盘。这个缓冲有默认值就够用,但因为它是内存到磁盘之间的一个短暂缓冲区,配置太大反而没意义,太小的话在高并发大事务下会增加磁盘写入频率。

Query Cache就不一样了。它在MySQL 5.7及更早版本里是个“看着很美”的功能——把SELECT语句和结果集直接缓存在内存里,完全相同的SQL再来查就直接返回结果。但从实际运维来看,它的失效机制太粗暴了:只要涉及的表发生任何数据变更,相关缓存全部失效,在高并发写入场景下,维护缓存和清理失效的开销甚至比查询本身的代价还大。所以MySQL 8.0直接把这个功能移除了。这个历史包袱如果你能避开,就别再往里跳了。

注意:如果你维护的是老项目,用的还是MySQL 5.7,建议确认下query_cache_type参数状态。很多年久失修的服务器这个参数还是ON,白白浪费内存不说,还会拖慢写入性能。我自己处理过好几台这样的机器,关了Query Cache之后,写入性能反而涨了一截。

2. InnoDB Buffer Pool调优:缓存策略的地基

2.1 Buffer Pool到底缓存了什么

InnoDB的Buffer Pool不是简单的一个大数组,它内部有多种链表和数据结构的组合。它缓存的最核心内容就是数据页索引页。InnoDB存储数据时并不是一条一条记录往磁盘上写,而是按照16KB一个页的粒度来管理的。当你查询一行数据时,InnoDB会把包含这行数据的整个页从磁盘读入Buffer Pool,后续再查这个页里的其他数据,就直接命中内存了。

除了页数据,Buffer Pool里还放着插入缓冲(Insert Buffer/Change Buffer)、自适应哈希索引、锁信息等元数据。Change Buffer尤其有意思——当你要修改的二级索引页不在Buffer Pool里时,InnoDB不会立刻去磁盘读这个页,而是把修改记录在Change Buffer里,等以后这个页被读入时再合并。这种延迟合并的策略极大地减少了随机IO,但也意味着如果空闲时刷脏不及时,Change Buffer太大会拖慢实例恢复速度。

Buffer Pool内部通过LRU(最近最少使用)链表来管理页的淘汰。不过InnoDB的LRU不是教科书式的那种简单链表,而是把链表分成了Young区和Old区。新读入的页先放到Old区头部,只有再次被访问才会提升到Young区。这种设计是为了防止全表扫描或者大查询把真正热的数据全部挤出去——很多人在调优时容易忽略这个细节,但它对缓存的稳定性非常关键,理解了它你也就明白了为什么偶尔跑一个大的报表查询,不会把线上热数据全冲掉。

2.2 容量怎么定:从2GB到128GB的配置思路

Buffer Pool的容量设置是我每次帮人调参时第一个问的。很多服务器实际内存很大,但innodb_buffer_pool_size还停在默认的128MB或者随便设的1GB、2GB,这基本等于把法拉利当拖拉机开。

怎么定这个值?核心原则是:给操作系统和其他进程留足余量的前提下,尽可能把热数据装进内存。常用的经验值是物理内存的50%到70%。比如一台16GB内存的数据库专用服务器,Buffer Pool设到8GB到10GB是比较合理的;32GB内存可以设到20GB左右;再往上走,单实例到128GB内存的机器,我会建议先确认清楚数据量和实际工作负载,再决定是单实例占满还是分多实例部署。

光看经验值还不够,更可靠的做法是结合命中率微调。计算命中率的指标来自SHOW GLOBAL STATUS

SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';
  • Innodb_buffer_pool_read_requests:从Buffer Pool中读取页的请求次数
  • Innodb_buffer_pool_reads:从磁盘读取页的次数

命中率可以用这个公式算:(read_requests - reads) / read_requests * 100%。我一般要求线上实例的Buffer Pool命中率长期维持在99%以上,如果低于95%,基本说明Buffer Pool太小或者数据访问过于分散,需要扩容或者优化数据访问模式。注意,一次性采样会受启动时间影响,所以我会连续采样几天的值再判断趋势。

推荐落到配置文件里的关键参数组合如下:

[mysqld] # 核心缓冲池大小,通常是物理内存的50%-70% innodb_buffer_pool_size = 8G # 缓冲池实例数,常用配置为8,最大可到64 innodb_buffer_pool_instances = 8 # 关闭/开启时是否保存/加载缓冲页,减少重启后的预热时间 innodb_buffer_pool_dump_at_shutdown = ON innodb_buffer_pool_load_at_startup = ON # 刷脏控制 innodb_io_capacity = 600 innodb_io_capacity_max = 2000 innodb_max_dirty_pages_pct = 75

这里的innodb_buffer_pool_instances很多人不理解,其实它是把Buffer Pool这个大池子按内存地址划分成多个小池子,每个池子有独立的锁。当并发非常高、访问也不同集中时,多实例能减少锁竞争。经验法则是:Buffer Pool大小超过1GB时,实例数按1GB一个实例来切比较合适,比如8GB设8个实例。超过64个实例也没必要,反而浪费内存管理开销。

容量扩大会有个很现实的问题,就是MySQL启动时准备内存会变慢,同时重启后需要重新缓存热数据。8.0里加载Buffer Pool字典信息比以前快了不少,但仍然需要时间。所以千万别在业务高峰期随手改这个参数重启,否则接下来一段时间磁盘IO会明显上升,等缓存“加热”完成才恢复。

2.3 容易被忽略的预热、多实例与刷脏细节

预热这个点,我在生产环境里吃过亏。有一次接手一个项目,服务器配置不低,内存也加了,Buffer Pool也调大了,但每天凌晨定时任务一跑完,数据库就开始卡顿。排查下来发现倒不是定时任务的SQL有多烂,而是服务器只要重启过,Buffer Pool就是空的,所有热数据都要重新从磁盘加载一遍。这个“冷启动”阶段持续几十分钟到一两个小时,期间任何请求都可能在磁盘上卡一下。

解决办法就是用innodb_buffer_pool_dump_at_shutdowninnodb_buffer_pool_load_at_startup这对参数。关闭MySQL时,它会把Buffer Pool中页的引用信息记录到磁盘文件里,启动时再根据这些信息把这些页加载回内存。注意它加载的只是页的“名单”,真正把数据读入内存还是需要磁盘IO的,所以这个过程不会瞬间完成,但已经能够让最热的数据优先回归,比全部冷启动好得多。

刷脏是另一个值得盯的点。Buffer Pool里的页更新后就成了脏页,InnoDB需要在后台把这些脏页写回磁盘。如果脏页积累太多,前台需要查询Buffer Pool时发现没有空闲页,就得强制触发刷脏,造成瞬间的IO毛刺。innodb_io_capacityinnodb_max_dirty_pages_pct就是控制这个节奏的关键。

我一般把innodb_io_capacity设为磁盘能力的80%左右。比如线上SSD能支撑大概800 IOPS,就设600;如果是高性能NVMe,可以设1500到2000。innodb_max_dirty_pages_pct保持默认的75就好,也不建议调太高,否则一旦触发强制刷脏,延迟会很难看。要实时看脏页情况和刷脏线程状态,可以用这个命令:

SHOW ENGINE INNODB STATUS\G

然后把注意力放到BUFFER POOL AND MEMORY段,看Modified db pages这一项。如果这个数字会话期间长期居高不下,说明刷脏能力跟不上写入速度了,要么调大innodb_io_capacity,要么检查有没有大事务或者大量行更新在执行。

3. 从查询缓存到“不依赖MySQL缓存”:缓存思路的转变

3.1 为什么MySQL 8.0直接删掉了Query Cache

Query Cache在早期MySQL版本中曾经被当成宝贝功能:同样的SELECT语句,第一次执行后把结果集存起来,第二次来查时直接返回缓存结果,连SQL解析和执行计划都省了。听着特别美,官方也确实默认开启过。但实际在高并发生产环境里,这个功能几乎是帮倒忙的。

问题出在缓存的失效粒度上。Query Cache是整表级失效的——只要某张表发生任何一次INSERTUPDATEDELETE操作,跟这张表相关的所有查询缓存都会被清空。在写入频繁的业务里,缓存基本上就是刚建好就被清掉,维护这个缓存本身还需要全局锁保护,反而会造成所有查询都要去抢这把锁,性能直接崩掉。这就是为什么很多生产系统在关闭Query Cache后,查询和写入性能反而同时提升。

所以MySQL 8.0把Query Cache整个功能模块从代码里删干净了,不是默认关闭,是彻底不存在。如果你是8.0用户,根本不用考虑这一层缓存,把精力放在Buffer Pool和执行计划优化上就行。如果是老版本升级或者还在维护老架构,我建议从MySQL 5.7开始就直接把query_cache_type=OFF,并把query_cache_size=0,别再给它分配内存。

3.2 新思路:让MySQL尽量不“重复劳动”

既然MySQL内部不那么依赖查询缓存了,那提升查询性能靠什么?其实归根结底是两条路:一是让数据尽量留在内存中,也就是前面说的Buffer Pool调优;二是让单条SQL的CPU和IO成本降下来,典型手段是指定合理索引、维护准确的统计信息、优化执行计划。

这里重点说一下统计信息。MySQL的优化器决定走哪个索引、用哪种连接顺序,依赖的是表上的统计信息。如果统计信息严重过期,优化器可能挑了一个极差的执行计划,哪怕数据都在Buffer Pool里,也会因为扫描行数太多而变慢。常见的处理方法是保持innodb_stats_auto_recalc自动重算开启,对于频繁变更的大表,在大批量写入后手动执行ANALYZE TABLE。缓存策略到了这个层面,已经不只是“内存够不够大”的问题,而是“MySQL选路是否高效”的问题。

另外一个容易被忽略的点是索引页的物理顺序。Buffer Pool解决了“读到内存”的速度,但如果索引页本身离散度太高,比如因为频繁随机插入导致页分裂,那么即使从内存读取,CPU的处理开销也会增加。这种情况下,定期用OPTIMIZE TABLE重建表的聚簇索引和数据页排列,能有效提高缓存的使用效率,让同样大小的Buffer Pool装下更多有效数据。

3.3 如果还在用5.7,如何兼容老查询缓存

现实情况是,很多公司遗留项目还在用MySQL 5.7,甚至5.6。这些项目往往有大量只读报表查询,也不怎么频繁写入,Query Cache在这种场景下确实能带来些收益。但我的建议依然是:别依赖它,趁早关掉。

原因很直白:它带来的收益天花板很低,但引入的坑太多了。首先是命中率不稳定,一旦有写入就清零,运维很难给业务方一个明确的性能承诺。其次是内存浪费,query_cache_size设得越大,内存碎片和管理开销也越大,8.0时代这套机制已经被证明是低效的。如果确实有“相同SQL并发很高”的场景,正确做法是在应用层加缓存,把结果放到Redis这类专门的缓存组件里,而不是让MySQL自己去扛。

如果你一时改不了代码,只能先在5.7上顶着,那也建议这样配置:query_cache_type = DEMAND,并在需要缓存的SQL里显式加SQL_CACHE提示,这样可以限制只有你想缓存的查询才进缓存,减少无谓的失效开销。这算是老版本里的一个折衷操作,但治标不治本。

提示:老版本升级到MySQL 8.0是个大工程,不只是改个配置那么简单。要提前把带有SQL_CACHESQL_NO_CACHE提示的SQL全部清理掉,否则升级后语法直接报错。还要测试字符集排序规则、认证插件等兼容性,建议先在测试环境完整跑一遍业务链路再规划升级窗口。

4. 真正的缓存策略在应用层:MySQL与业务缓存的协同

4.1 Cache Aside是默认选择:什么时候读缓存、什么时候写库

聊到缓存策略,MySQL内部的Buffer Pool只是第一层。真正高并发的互联网项目,几乎没有哪个是单纯靠数据库缓存扛住读流量的。因为数据库的内存再大也是有限的,热点数据再多也架不住几十万QPS的打法。所以业务系统里最常见的缓存架构,是在应用层和数据库之间再架一层缓存——通常是Redis。

这层缓存怎么跟MySQL配合?最经典的方案是Mode "Cache Aside"(旁路缓存),中文一般叫“缓存旁路”。它的逻辑很简单:

  • 读请求:先查缓存,命中就直接返回;没命中,查MySQL,把结果写回缓存,再返回。
  • 写请求:先更新MySQL,然后删除对应缓存。

写操作选择“删缓存”而不是“更新缓存”,这一点很多新手想不明白。原因在于:更新缓存需要把新数据完整地构建出来,可能涉及多个字段的运算和关联查询,成本比删除一个key高得多。而且多个并发写操作如果顺序错乱,还可能把旧值写进缓存。删除缓存就没这么多问题——下一次读请求发现缓存没有,自然会把最新数据重新加载进来。

代码示例大概长这样:

// 读操作 public String getUserOrderInfo(String userId) { String key = "user:order:" + userId; String cached = redis.get(key); if (cached != null) { return cached; } // 缓存未命中,查询数据库 String value = userOrderMapper.queryByUserId(userId); // 回填缓存,设置合理的过期时间 redis.setex(key, 300, value); return value; } // 写操作 public void updateOrder(Order order) { // 先更新数据库 orderMapper.update(order); // 再删除缓存,下次读取时重建 redis.del("user:order:" + order.getUserId()); }

这里面有两个细节很关键。第一,缓存过期时间一定要加,而且要根据业务耐受度来定,一般5到30分钟比较常见。没有过期时间的缓存一旦写错,就是长期的脏数据。第二,缓存中的value序列化格式要考虑好,JSON可读性好但是体积大,二进制序列化省空间但排障不方便,中小项目里JSON够用,吞吐量极高的场景再考虑压缩或二进制。

4.2 从双删到binlog订阅:缓存一致性的演进之路

Cache Aside看起来简洁,但有一个并发黑洞:读请求在缓存失效后、回填缓存前,写请求刚好更新了数据库并删除了缓存,随后读请求把旧数据回填到了缓存,导致缓存里长期存着旧值。这种概率不高,但一旦发生就很麻烦。

业界对付这个问题,最朴素的方案是“延迟双删”。也就是写完数据库后先删一次缓存,过几百毫秒再删一次,把可能回填的旧值二次清掉。这个方案实现简单,能解决大部分并发场景,但延迟时间不好确定,删太早没意义,删太晚又影响性能。我在实战中一般设500毫秒到1秒,结合业务对一致性的容忍度来定。它不完美,但性价比很高。

再往上走,就是通过订阅MySQL的binlog来同步更新缓存。现在比较常听到的中间件是Canal,它把自己伪装成一个MySQL从库,拉取主库的binlog,解析出数据变更事件,然后通知业务系统更新缓存或者同步到其他存储。这套方案的优点是解耦,业务代码里不需要手动处理删缓存的逻辑,数据变更以事件流的方式驱动缓存更新,适合对一致性要求更高的场景。

binlog订阅方案也有代价:要多维护一套中间件,还要处理消息延迟和重复消费幂等。对于大多数中小项目,延迟双删已经够用了。但如果项目已经足够大,团队也养得起中间件,binlog订阅的方向会更干净,也是大型架构演进的一个必经节点。

4.3 穿透、击穿、雪崩:缓存失效的三种“事故现场”与对策

应用层缓存做得再怎么花哨,都绕不开三个经典问题,几乎每次聊缓存面试都会问到,线上出问题也大多是这三兄弟在作怪。

缓存穿透是指查询一个根本不存在的数据,缓存里没有,数据库里也没有,每次请求都会打到数据库。如果有人恶意构造一堆不存在的ID,数据库压力瞬间就会上去。解决手段最常用的是布隆过滤器,把所有可能存在的主键都提前加载到布隆过滤器里,请求来了先判断ID是否可能存在于数据库,如果过滤器说没有,直接返回空。另一个简单有效的办法是缓存空值,把不存在的查询也缓存一个短暂的key,比如60秒,可以挡住大量重复无效查询。

缓存击穿是指某个热点key突然过期,此时大量并发请求同时打到数据库。比如某个爆款商品的秒杀详情页,缓存里本来有,过期的那一瞬间,几千个请求全部涌向MySQL。防御方案有两种:互斥锁和逻辑过期。互斥锁的做法是当缓存没有时,先获取一把分布式锁,只让一个线程去查库并重建缓存,其他线程等待锁释放后重新读缓存。逻辑过期是另一种思路,value里存一个逻辑过期时间,读的时候发现逻辑过期,不直接删key,而是先返回旧数据,同时异步去刷新缓存。后者对系统可用性更友好,适合读多写少的场景。

缓存雪崩则是大量key在同一时间段集体过期,导致请求全部落到数据库,或者缓存节点直接宕机,数据库被打垮。应对方式首先是把过期时间离散化——在固定过期时间的基础上加一个随机偏移量,比如300秒加0到60秒随机值,避免同一时刻大批key到期。其次,如果是Redis集群节点挂了,要从架构上做高可用保障,比如哨兵或Cluster模式,并在应用层做好熔断降级,数据库端也建议加连接池和限流保护,不要裸奔。

这三个问题的本质,是不能把缓存层当成永远不会出问题的黑盒。缓存设计里要预留“缓存挂掉怎么办”的预案,这才是架构上的成熟。

5. 实战中的度量与避坑:监控缓存健康度

5.1 命中率这个指标该怎么看

很多人一看缓存命中率99%就觉得万事大吉,但我必须泼一盆冷水:命中率高不代表缓存策略是健康的,关键要看命中率和业务请求量的关系。

比如一个冷门接口,一天只有1000次请求,其中990次命中缓存,命中率是99%,这个数据没有任何参考价值。更要注意的是命中率单一指标的陷阱:如果命中率在95%以上,但用户响应时间依然很慢,那瓶颈多半不在缓存上,而在应用层序列化、网络延迟,或者数据库这边有慢SQL在拖后腿。

我习惯把缓存多个指标放在一起看:请求总量、命中率、回源量(落到数据库的请求数)、平均响应时间、以及Redis的used_memoryevicted_keysevicted_keys尤其重要,它代表内存不够时被LRU淘汰的key数量。如果这个值快速增长,说明Redis可用内存不足,热数据被持续淘汰,此时命中率再高也只是表面繁荣,很快会崩。正确的做法是根据业务增长曲线提前扩容内存,或者把大key拆分成多个小key分散存储。

数据库这边的监控同样要做好。SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%'要建个定时任务定期记录,方便出问题的时候回溯趋势。很多企业用的监控平台如Prometheus+Grafana,也有现成的MySQL Exporter可以采集这些指标,提前配好,比你半夜被电话叫起来查一条条SQL强得多。

5.2 大事务、慢SQL与缓存刷新的联动排查

缓存问题往往不是孤立出现的。我处理过一个比较典型的故障,现象是某个接口平时很快,偶尔突然卡顿几秒。第一反应是缓存命中率出问题了,但查下来Redis一切正常,命中率也稳定。后来看数据库的慢查询日志,发现有几条大事务更新了同一张表,导致该表上的行锁排队,而接口里的查询SQL因为走不到合适的索引,必须全表扫描,这几个因素叠加,就把响应时间拉上去了。

这个案例给我的教训是,缓存只是数据库的“门卫”,它挡不住所有问题。当应用层缓存命中的时候,请求可以很快返回;一旦碰上未命中,需要回源数据库,如果数据库本身结构或者执行计划有问题,回源的那一下就会特别痛。所以在做缓存策略的同时,必须同步做好数据库侧的索引优化、慢SQL治理和事务拆分。

如果你遇到类似“偶发变慢”的问题,可以按这个顺序排查:

  1. 确认缓存命中率和Redis侧是否有慢命令或大key。
  2. 打开MySQL慢查询日志,找到对应时间段的慢SQL。
  3. EXPLAIN看慢SQL的执行计划,确认是否走错索引或者扫描行数异常。
  4. 检查数据库当时的锁等待情况,SHOW STATUS LIKE 'innodb_row_lock%'可以快速判断。
  5. 如果是大事务,重点查应用代码里是否存在单次请求内做了太多更新操作,必要时拆成多个小事务。

5.3 一套自己常用的缓存检查命令与脚本

最后把我平时排查缓存问题时必用的一套命令和SQL放出来,基本是”上手即用“的。这里不完全覆盖所有场景,但对于快速定位80%的问题是足够的。

查看MySQL Buffer Pool整体情况:

SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_free'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_total'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';

算命中率的公式我已经在前面写过,用的时候拿两次采样的差值来计算,避免从服务器启动以来的累计值误导判断。查看当前Buffer Pool配置:

SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; SHOW VARIABLES LIKE 'innodb_buffer_pool_instances'; SHOW VARIABLES LIKE 'innodb_io_capacity'; SHOW VARIABLES LIKE 'innodb_max_dirty_pages_pct';

查看内存分配和重做日志(redo log)相关情况:

SHOW ENGINE INNODB STATUS\G

在这个输出的BUFFER POOL AND MEMORY部分,你会发现Free buffersDatabase pagesModified db pages等指标。Modified db pages太高就是前面提到的刷脏压力大的信号。

Redis侧,需要留意几个INFO项:

redis-cli info memory redis-cli info stats

重点看used_memorymaxmemoryevicted_keyskeyspace_hitskeyspace_misses。Redis的命中率计算是keyspace_hits / (keyspace_hits + keyspace_misses),这个值连同比和环比一起看,如果短时间内明显下滑,要么是过期时间太激进,要么是访问模式变化,要么就是内存满了在疯狂淘汰。

再说个我自己保持的习惯:每周跑一次脚本,把这些关键指标存到一张统计表里,每周对比一下趋势。缓存调优不是一锤子买卖,它是随着业务数据量和访问模式变化而动态调整的过程。建立历史基线后,哪天指标异常,你能第一时间知道是“突然变化”还是“早就该处理了”。

我个人的一个体会是:缓存策略从来不是孤立的配置游戏,而是数据库、应用、业务三个层面协同出来的结果。MySQL内部的Buffer Pool要把热数据留在内存里,这是地基;应用层要根据业务特征设计合适的缓存结构和失效策略,这是主体;而监控和预案,则是保障整套系统稳稳跑下去的护栏。三者缺一不可。

最后再分享一个小技巧:每当你给某个缓存key设置过期时间之前,先想一下“这个时间用户能接受吗?数据库能扛住吗?过期后有多少并发会同时打过来?”把这三件事想清楚了,80%的缓存事故你都已经提前避开了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询