
我花了很长时间把MySQL缓存这块整个梳理了一遍从最底层的Buffer Pool原理到实际调优参数再到和Redis搭配时绕不开的缓存一致性坑全部过了一遍。说实话网上聊MySQL缓存的文章不少但大多东一块西一块有人只讲一个查询缓存有人把Buffer Pool参数丢给你就完了真正把“缓存到底是怎么运作的”讲透的并不多。这篇内容我就按自己的理解用最直接的方式把整个MySQL缓存体系拆开揉碎顺便把我实际踩过的坑也一并记录下来希望对正在研究MySQL性能的同学有帮助。1. 先想清楚一个问题MySQL缓存到底在缓存什么聊缓存之前我们得先建立一个基本认知MySQL的缓存不是一个东西而是一套分层体系。很多人在讨论“MySQL缓存”时各说各话就是因为每个人关注的那一层不一样。有人说的缓存是查询缓存也就是那套把SQL语句和结果集直接存起来的东西这个在MySQL 8.0里已经被移除了。有人说的缓存是InnoDB Buffer Pool这才是存储引擎层面真正核心的数据页缓存。还有人说的缓存是操作系统的Page Cache这个阶段连MySQL自己都管不着但实际效果却很猛。更广义一点还有人把Redis放在MySQL前面充当的缓存层也算进来不过那属于应用架构层面的缓存了后面我会专门聊。先拉一张对照表把几个容易混淆的概念理清楚缓存名称所在层级缓存对象是否可控是否默认开启Query CacheMySQL Server层SQL语句与结果集可控MySQL 5.7及以下默认关闭8.0已移除InnoDB Buffer PoolInnoDB存储引擎层数据页、索引页、undo页等可控核心调优对象默认开启MyISAM Key CacheMyISAM存储引擎层索引键值可控默认开启Redo Log BufferInnoDB存储引擎层重做日志数据可控默认开启操作系统Page Cache操作系统内核层磁盘文件页基本不可控默认开启注意看这张表。绝大多数情况下我们说的“MySQL缓存优化”真正要调的是InnoDB Buffer Pool这一层而不是其他。为什么因为现在主流生产环境几乎全部使用InnoDB引擎MyISAM已经很少见了Query Cache在8.0里直接被砍掉了Redo Log Buffer只是临时缓冲日志数据体量不大。而操作系统的Page Cache虽然一直在默默帮忙但它不是MySQL能精细控制的。这个认知很重要。如果一开始就把方向搞错了比如花大力气去调一个8.0里已经不存在的东西那就完全白费功夫了。2. 把MySQL缓存家族逐个讲清楚2.1 查询缓存一个已经走进历史的方案先聊一个很多人听过但已经过时的东西——查询缓存。在MySQL 5.1到5.7这个阶段查询缓存是可以被开启的它的原理很简单粗暴把SELECT语句的文本做哈希如果命中就直接返回结果集根本不去走解析、优化、执行那一套流程。听起来很美好但实际用起来问题很大。第一个问题是缓存失效极其频繁只要涉及的表有任何一条数据被更新该表对应的所有查询缓存全部失效脏数据会被全部清掉。对于写入频繁的业务来说查询缓存几乎等于一直在做无用功而且清理缓存本身还有锁开销导致性能反而下降。第二个问题是查询缓存只能精确匹配SQL语句中间多一个空格、大小写不一致、参数值不同都无法命中。所以在MySQL 5.7里查询缓存默认就是关闭的到了8.0版本干脆直接把这块代码删掉了。我自己的建议是如果你还在用5.7也千万别开查询缓存留着它纯粹是给自己找麻烦。你可以通过下面的命令看看它的当前状态SHOW VARIABLES LIKE query_cache_type;如果结果是OFF那就对了不要去动它。2.2 InnoDB Buffer PoolMySQL缓存真正的核心InnoDB Buffer Pool中文一般叫“缓冲池”这是InnoDB在内存中划出的一片区域用来缓存数据页和索引页。MySQL每一次读写数据不是直接去磁盘操作而是先在Buffer Pool里找找到了就直接用内存中的数据找不到才去磁盘读读上来之后再放到Buffer Pool里备用。理解Buffer Pool可以先想象一个场景你开了一家餐厅食材都放在很远的仓库里每天去仓库取货很费时间。所以你在后厨建了一个冷藏库把常用的食材提前放进去做菜的时候直接从冷藏库拿。Buffer Pool就是那个冷藏库磁盘就是那个远方的仓库。这个机制解决了MySQL最大的性能瓶颈——磁盘IO。内存的访问速度是纳秒级磁盘的访问速度是毫秒级中间差了几个数量级。数据如果能大量命中Buffer Pool数据库的整体性能就会有质的提升。那Buffer Pool到底有多大默认情况下MySQL 5.7及之前的版本是128MB8.0版本默认值有所调整但不管怎样这个值对绝大多数生产环境来说都太小了。实际调优中一般建议把Buffer Pool设置为物理内存的50%到70%如果这台机器是数据库专用服务器比例还可以更高。2.3 其他容易被忽略的缓存组件InnoDB引擎还有几个小型的缓存区域虽然平时不太起眼但关键时刻也有影响。Redo Log Buffer这个用来暂存事务产生的重做日志在事务提交时把日志写入磁盘的redo log文件。它的大小由innodb_log_buffer_size控制默认是16MB。如果业务中有大量并发写入事务这个值太小会导致频繁写磁盘适当调到32MB或64MB会有改善。MyISAM Key Cache这个只在MyISAM存储引擎下才生效缓存的是MyISAM表的索引块。因为现在MyISAM用的人很少了这个参数重要性也大不如前。如果你还在用MyISAM表可以通过key_buffer_size来调节但我的建议是赶紧迁移到InnoDB别在旧引擎上耗费精力了。还有自适应哈希索引Adaptive Hash IndexAHI。InnoDB会监控索引被查询的频率如果发现某个索引值被反复访问它会在内存中自动维护一个哈希索引来加速等值查询的定位。这个机制完全自动不需要人工配置但它确实消耗Buffer Pool的内存。在极端情况下如果等值查询占比不高反而可能因为维护AHI带来额外开销不过绝大多数场景下不用管它。2.4 别忘了还有操作系统层很多人在分析MySQL性能时忽略了一个事实MySQL读数据到Buffer Pool数据其实还要经过操作系统的Page Cache这一层。也就是说一次数据读取的完整路径是磁盘 - Page Cache - Buffer Pool - 应用。如果你的Buffer Pool没有命中但Page Cache命中了这次的IO依然是内存级别的速度并没有真正落到磁盘。这也是为什么数据库服务器内存配置足够大时即使MySQL的Buffer Pool命中率不是特别高整体表现也还行。操作系统偷偷帮了一手。不过Page Cache不受MySQL控制MySQL无法主动管理哪些页面留在里面。所以在做缓存分析时可以把Page Cache当成一个锦上添花的因素但不能依赖它真正要优化的还是Buffer Pool本身。3. Buffer Pool内部到底是怎么运转的Buffer Pool不只是简单地在内存里塞了一堆缓存页它内部有一套完整的管理机制。理解这套机制你才能理解为什么有些参数这么调为什么有些时候命中率很高但性能依然不好。3.1 数据页是缓存的最小单位先明确一个概念InnoDB和磁盘交互的最小单位叫作“页”默认大小是16KB。Buffer Pool管理数据的最小单位也是页整个缓冲池被划分成一个个16KB的内存块。你可以通过下面的命令查看页大小SHOW VARIABLES LIKE innodb_page_size;通常结果就是16384对应16KB。这意味着即使你只需要一行数据InnoDB也会把这一行所在的整个数据页从磁盘读入Buffer Pool。如果这行数据只有几十字节那么读一页16KB的数据显然是浪费的。但这是磁盘IO的物理特性决定的顺序读取整页远比随机读取单行要高效所以InnoDB选择以页为单位管理。3.2 三张核心链表Free List、LRU List、Flush ListBuffer Pool内部有三条重要的链表理解它们之间的关系是理解MySQL缓存的钥匙。Free List是空闲页链表里面挂的是可以写入新数据的空页。当有磁盘页需要读入Buffer Pool时InnoDB会先看Free List里有没有空闲页有就直接用没有的话就需要淘汰一些旧的缓存页腾出空间。LRU List是最近最少使用链表里面挂的是已经缓存了数据内容的页按照最近被访问的时间从新到旧排列。新读入的页会放在链表头部被访问的页会往头部移动而链表尾部的页就是最久没被使用的优先被淘汰。Flush List是脏页链表里面挂的是“数据已经被修改过、但还没来得及写回磁盘”的页。脏页意味着内存里的数据和磁盘上的数据不一致InnoDB必须找机会把脏页刷回磁盘否则一旦宕机内存数据全部丢失事务就没法保证了。这三条链表对应了Buffer Pool里一个页的完整生命周期从Free List拿空页 - 从磁盘读入数据并挂到LRU List - 被修改后挂到Flush List - 刷盘后变干净 - 被淘汰后回到Free List。3.3 经典的LRU算法被MySQL改了哪里标准的LRULeast Recently Used算法大家应该都熟悉最近最少使用的数据先被淘汰。但InnoDB并没有直接用标准LRU而是做了一个“分代”优化把LRU List分成两个区域young区域和old区域默认old区域占37%。为什么要这样设计想象一个场景一张几千万行的大表你执行了一个全表扫描的SQLInnoDB会把整张表的数据页全部读入Buffer Pool。如果使用标准LRU这些新读入的数据会全部占据链表头部把之前真正频繁访问的热点数据全部挤到尾部并淘汰掉。然后全表扫描结束了这些页再也没有人用留着一堆“一次性”数据而原本的热点数据却被挤走了下次访问又得重新读磁盘性能瞬间崩盘。MySQL的分代LRU处理方式是这样的新读入的页先放在old区域头部只有再次被访问到才会被提升到young区域。这样一次性扫描的数据页因为之后不会再被访问会一直在old区域里慢慢老化直到被淘汰。热点数据则始终在young区域存活。一句话总结分代LRU是为了保护热数据不被冷数据冲掉。3.4 脏页刷盘和Checkpoint机制说完了页是怎么在内存里存放的再说说数据怎么落盘。脏页不能一直留在内存里必须定期写回磁盘这个动作在MySQL里叫“刷盘”。刷盘的时机由很多因素决定比如脏页占比达到阈值、Buffer Pool空间不足、系统空闲等。刷盘的核心机制是Checkpoint检查点。简单理解Checkpoint就是记录“哪些日志对应的脏页已经成功写回磁盘”的标记。有了Checkpoint崩溃恢复时就不需要把全部redo log重放一遍只需要从最近的Checkpoint开始重放就行恢复速度会大幅提升。这里有一个非常关键的参数要注意innodb_max_dirty_pages_pct它控制脏页占Buffer Pool的最大百分比。默认值是75%MySQL 8.0里默认是90%也就是说Buffer Pool里脏页最多可以占到九成。这个值设置得越小脏页会越早被刷盘可以减少宕机时的数据丢失风险但刷盘频率高了磁盘IO压力也会变大。实际生产环境里如果是普通机械硬盘建议把值设低一些比如50%到60%如果是SSD可以保持默认值。因为SSD的随机写性能很好刷盘成本低即使脏页占比高一点也不至于引起性能抖动。4. 实操调优让Buffer Pool真正为你所用4.1 如何确定Buffer Pool的大小这一节是大家最关心的。Buffer Pool设置多大合适答案取决于你的数据量和物理内存大小。先看数据量。如果你把Buffer Pool设置得比整个数据库的总数据量还大那就意味着全部数据都能常驻内存查询基本不会产生磁盘IO这当然是最好的情况。但大多数业务的数据总量是不断增长的不可能无限放大内存。所以现实的目标是把最常访问的数据尽量放在内存里。一个经验法则是在数据库专用服务器上把物理内存的60%到70%分配给Buffer Pool。比如机器是64GB内存Buffer Pool可以设置成40GB到45GB。剩下的内存留给操作系统Page Cache、InnoDB其他结构、连接线程栈以及Redis等其他组件。修改方式是在配置文件的[mysqld]段下设置[mysqld] innodb_buffer_pool_size 40GMySQL 8.0还支持在运行中动态调整Buffer Pool大小这在扩容场景下很方便SET GLOBAL innodb_buffer_pool_size 48G;不过需要注意动态调大是可以的调小的话风险较高尽量不要在生产环境执行缩小操作。4.2 多个Buffer Pool实例是怎么回事在MySQL 5.7及之前Buffer Pool虽然很大但它作为一块大内存区域同一时刻只能被一个线程独占访问高并发下会产生锁竞争。为了解决这个问题MySQL支持把Buffer Pool拆分成多个独立的实例每个实例有自己的Free List、LRU List等结构互不干扰可以并发访问。相关参数是innodb_buffer_pool_instances。默认情况下如果Buffer Pool大小小于1GB实例数为1大于1GB后默认实例数会按规则调整。一般建议每个实例不小于1GB比如40GB的Buffer Pool可以设置成4个实例[mysqld] innodb_buffer_pool_instances 4注意这个参数只能在启动时设置运行中不能修改。改了之后要重启MySQL才能生效。4.3 如何查看缓存命中率调优之前先要能衡量效果。MySQL提供了两个比较关键的指标读命中率和磁盘读次数。查看当前Buffer Pool的命中情况可以执行SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_read_requests; SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_reads;第一个值表示从Buffer Pool中读取的逻辑读请求次数第二个值表示需要从磁盘读取的物理读次数。命中率公式是命中率 (read_requests - reads) / read_requests * 100%如果你看到的命中率低于95%说明Buffer Pool可能太小或者SQL没有走索引导致大量扫描。还有一对指标是写相关的SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_pages_dirty; SHOW GLOBAL STATUS LIKE Innodb_buffer_pool_pages_total;两者对比能看出脏页占比帮助判断刷盘压力。4.4 数据预热重启后如何快速恢复缓存状态MySQL重启后Buffer Pool是空的这时候如果业务流量直接打进来前期的SQL全部会穿透到磁盘磁盘IO飙升数据库响应时间会明显变长。为了缓解这个问题MySQL提供了Buffer Pool预热机制。思路是在正常运行时定期把Buffer Pool中缓存页对应的表空间ID和页号保存下来存到磁盘文件里。重启后MySQL自动读取这个文件把这些页重新加载回内存从而快速恢复缓存状态。相关参数是[mysqld] innodb_buffer_pool_dump_at_shutdown ON innodb_buffer_pool_load_at_startup ON第一个参数表示关闭MySQL时把缓存页的元数据导出到文件第二个参数表示启动时自动加载之前导出的数据。默认情况下这两个参数在MySQL 8.0里都是开启的老版本可能是关闭状态建议确认一下。另外还有一个参数可以控制导出的比例innodb_buffer_pool_dump_pct 25这个值表示关闭时只导出最近最常用的25%的页信息而不是全量导出。如果Buffer Pool很大全量导出文件会非常大启动加载时间也会很长25%是一个比较平衡的默认值。冷启动后这25%的热点数据已经能扛住大部分查询了剩下的数据再按需从磁盘加载即可。4.5 刷盘频率的微调刷盘策略对性能的影响很大尤其是机械硬盘时代刷盘太勤快磁盘IO扛不住刷盘太懒散宕机风险和数据丢失窗口变大。我们可以调整两个参数来平衡[mysqld] innodb_io_capacity 1000 innodb_io_capacity_max 2000这两个参数告诉InnoDB你的磁盘系统大概能承受多大的IOPS。普通机械硬盘建议设置为200左右SATA固态可以设置到1000左右NVMe SSD可以设置到2000以上。InnoDB会根据这个数值决定后台刷脏页的力度。如果设置得过高后台刷盘太激进可能会抢占正常查询的IO资源设置得过低脏页堆积到达阈值后可能出现刷盘风暴一样影响性能。还有延迟刷盘的阈值参数innodb_adaptive_flushing ON这是自适应刷盘机制默认开启。它会根据redo log生成的速度动态调整刷盘频率避免“前期不刷、后期猛刷”这种断层现象。除非有特殊原因否则建议保持开启。5. 缓存一致性从MySQL到应用层的大问题聊完了MySQL自身缓存必须再往上层看一层。在真实业务系统里光靠MySQL自己的Buffer Pool远远不够很多团队会在应用和数据库之间再加一层缓存最常用的就是Redis。这时候就出现了一个非常经典的问题缓存和数据库的数据一致性。5.1 MySQL自身的一致性是怎么保证的先理清MySQL内部的一致性机制。Buffer Pool里的脏页和磁盘上的数据不一致这事MySQL是怎么容错的靠的是redo log。事务提交时InnoDB会把本次修改产生的redo log写入磁盘即使这时候脏页还没刷盘数据库宕机了重启之后可以通过redo log重放把数据恢复出来。这就是“先写日志后刷数据”的WALWrite-Ahead Logging机制。也就是说MySQL内部的一致性靠的是日志先行不是靠实时刷盘。理解了这一点再去看各种刷盘参数的取舍就不会糊涂了。5.2 应用层缓存写Redis还是写MySQL先后顺序怎么定应用层的缓存一致性问题比MySQL内部要复杂得多因为涉及两个独立的存储系统。业界讨论最多的两个方案是先更新数据库再删除缓存以及先删缓存再更新数据库。先删缓存再更新数据库这个方案很直观但有一个明显的坑如果删完缓存后数据库还没更新完另一个线程来读数据发现缓存没有就去数据库读了旧数据然后把旧数据回填到缓存。后续再有查询就一直读到旧数据了缓存和数据库就永远不一致了。所以更推荐的方案是先更新数据库再删除缓存。这个方案的逻辑是数据库里的数据是最新的缓存里的旧数据即使被读到也只是一小段时间的窗口问题等删除缓存后下一次读取就会把最新数据加载进来。但先更新数据库再删除缓存也有失败的可能比如数据库更新成功但删除缓存失败缓存里就一直是旧数据。解决办法是引入可靠的消息队列重试机制删除失败就投递一条消息由消费者重试删除。还有更严格的方案叫“双删”就是更新数据库后删除缓存然后延迟一段时间再删除一次缓存。这个延迟删除是为了解决并发场景下“先读到旧数据回填缓存”的窗口问题。实际应用中如果业务对一致性要求不是极端严格更新数据库后删一次缓存再配合消息重试已经可以满足绝大多数场景了。下面用一张表对比两个主流策略策略优点缺点适用场景先删缓存再更新DB实现简单并发下容易出现缓存回填旧数据几乎不推荐先更新DB再删缓存一致性概率高删除失败需重试有短暂脏读窗口大部分业务推荐先更新DB再删缓存延迟双删进一步缩小小窗口实现复杂多一次删除开销一致性要求较高时5.3 缓存过期时间不是摆设除了主动删除缓存我们还必须给缓存设置过期时间这是兜底方案。即使前面所有的一致性策略都失效了缓存最终会过期数据会强制刷新。没有过期时间的缓存一旦出现一致性问题就是永久性的脏数据这是绝对不能接受的。设置过期时间时要考虑业务的容忍度。比如商品详情页缓存5分钟过期意味着极端情况下用户可能看到5分钟前的价格如果价格变动影响不大这个时间可以接受。对于库存、余额这类数据过期时间要设置得很短甚至不缓存直接用数据库实时查。5.4 Redis和MySQL是分工关系不是替代关系很多人在设计缓存方案时容易陷入误区觉得Redis能替代MySQL或者MySQL有Buffer Pool就不需要Redis。其实两者服务的层次完全不同。以电商首页为例用户的请求先打到RedisRedis里存的是已经组装好的商品摘要信息、热点排行榜、用户会话等。如果Redis没有再去MySQL查询MySQL的Buffer Pool会负责把底层数据页缓存在内存里减少磁盘IO。Redis挡住了大部分高频读请求MySQL负责数据最终落地的可靠性。两者配合才是完整的性能优化方案。6. 实战中遇到的那些坑和排查心得6.1 查询缓存引发的“优化反效果”前几年接手过一个老项目用的是MySQL 5.7数据库压力大某位同事就把查询缓存给开了。结果上线后不仅没有变快数据库的CPU使用率反而上升了。原因就是写入频繁查询缓存不断被清空、不断重建再加上清理操作需要加锁争抢了原本用于其他查询的资源。排查方式很简单可以用以下命令观察Query Cache的命中率SHOW STATUS LIKE Qcache%;如果Qcache_hits很低而Qcache_inserts和Qcache_lowmem_prunes很高说明查询缓存在空转。最终处理方式就是关掉它[mysqld] query_cache_type 0从那以后我把所有MySQL 5.7项目的查询缓存都设置为关闭状态再也没有在这上面吃过亏。6.2 Buffer Pool命中率很高但SQL依然慢有次排查一个慢SQL问题发现Buffer Pool命中率高达99%理论上性能应该是极好的但某些查询就是慢得离谱。后来详细分析执行计划发现是一条大表关联查询走了全表扫描需要读取上千万行的数据。虽然大部分数据页都在Buffer Pool里但SQL需要对海量数据做排序和临时表操作耗时被拉长了。这条经验很关键命中率只是健康指标之一不等于SQL没有优化空间。命中率高只说明缓存很有效地减少了磁盘IO但慢SQL的根因可能出在索引设计、SQL写法、锁等待上。Buffer Pool命中率再高也救不了一条设计糟糕的SQL。排查慢SQL还是要先从执行计划和索引入手不要被“命中率没毛病”的假象欺骗。6.3 MySQL重启后数据库性能骤降过几分钟才恢复这是典型的冷启动问题。MySQL实例重启后Buffer Pool空空如也大量请求直接打到磁盘磁盘IO瞬间被打满接口响应时间飙升。等跑了一段时间缓存慢慢热起来后性能才恢复正常。处理办法就是我前面提到的开启Buffer Pool预热[mysqld] innodb_buffer_pool_dump_at_shutdown ON innodb_buffer_pool_load_at_startup ON注意最好在低峰期重启数据库不要选择业务高峰时段做这件事。否则即使有预热文件加载过程本身也需要占用IO和CPU资源会让高峰期雪上加霜。6.4 常见问题速查表现象可能原因排查方向解决方式Buffer Pool命中率长期低于90%缓存太小或SQL扫描范围过大检查命中率指标、慢查询日志调大Buffer Pool优化SQL和索引CPU飙升但IO不高逻辑读过多或锁竞争激烈查看执行计划、Innodb_row_lock相关状态优化SQL、减小扫描行数、检查锁等待数据库重启后性能骤降冷启动缓存为空确认预热参数是否开启开启dump和load参数脏页比例持续偏高刷盘能力不足或IO能力配置错误查看dirty pages占比、磁盘IO调大innodb_io_capacity评估磁盘性能缓存与数据库数据不一致应用层缓存更新策略错误检查缓存更新代码是否有重试机制改为先更新DB再删缓存增加重试队列开启查询缓存后性能变差写多读少缓存频繁失效查看Qcache状态关闭Query Cache6.5 关于低频热点和大结果集的特别提醒还有两个容易忽略的细节。第一个是不是所有数据都适合进Redis缓存。比如说一个几万人的单位有一张几百万行的员工操作日志表这个数据只会在出问题时被查询平时基本不访问。把它全部缓存到Redis等于白白占用内存而且缓存无法体现最新数据变化得不偿失。低频冷数据老老实实放在MySQL里靠Buffer Pool按需加载就行。第二个是大结果集不要塞进缓存。有些接口返回的数据量很大比如导出报表几十万行。如果把这些结果往缓存里塞会大量占用内存而且序列化和反序列化的代价很高。这种场景适合直接走数据库流式查询或者落到临时文件。根据我个人经验缓存设计的第一步永远是“识别热点”。把20%的热点数据放进缓存就能解决80%的访问压力。盲目扩大缓存或者盲目提升Buffer Pool大小不如先把你系统里的慢查询日志翻一遍看看哪些数据真的在被高频访问。7. 最后再分享一个压箱底的经验每次排查MySQL缓存相关的问题我都会先问三个问题我的数据真的走到磁盘了吗如果没走到磁盘那卡在哪里如果走到了磁盘为什么Buffer Pool留不住这些数据这三个问题问完之后大部分缓存问题的方向就清晰了。第一问考察的是Page Cache和Buffer Pool的命中情况第二问考察的是SQL和索引的合理性第三问考察的是缓存淘汰策略和大表扫描的影响。我记得有次处理一个客户系统的问题他们一直在加大Buffer Pool但始终收效甚微。我登录上去一看发现他们的行为有些“可爱”——很多页面查询都用了SELECT*还加了ORDER BY对无索引字段排序。数据页虽然被加载进缓存了但每条查询都要扫描大量无关字段和排序分组Buffer Pool命中率并不低实际却一直在“空转”。后来把SQL改写、索引补齐之后数据库压力直接掉了一大截Buffer Pool压根不用动。所以还是要强调那句话缓存是性能优化的手段不是目的。把SQL写好、把索引建对再配合合理的缓存策略才是真正的提质增效之道。希望这篇文章能帮你把MySQL缓存这层窗户纸彻底捅破。