MySQL的缓存
在现代 MySQL(8.0+)中,根本没有所谓的“连表缓存结果”,底层全都是单表的数据页缓存(Buffer Pool)。只要执行 JOIN,MySQL 每次都要在内存里把这几张表的数据重新“手拉手”匹配一遍。
那么,当数据发生改变(比如执行了 UPDATE、DELETE 或 INSERT)时,MySQL 的单表缓存(Buffer Pool)是如何精准判断失效并进行更新的呢?
这背后依赖的是一套极其严密的“脏页(Dirty Page)管理”与“内存淘汰机制”。
# 一、 缓存失效的真相:并不是“直接删除”,而是标记为“脏页”
在 MySQL 的 Buffer Pool 内存中,数据是以 Page(数据页,默认 16KB) 为最小单位存储的。一个数据页里可能存放了你 users 表里的几十条用户数据。
当某行数据发生改变时,MySQL 绝对不会立刻去把这个内存页删掉,它的处理流程如下:
# 1. 内存中就地修改(瞬间完成)
当发起 UPDATE users SET name = '新名字' WHERE id = 1; 时:
- MySQL 会先看
id=1所在的数据页是否在 Buffer Pool 内存中。 - 如果在,MySQL 会直接在内存中把这个页里的名字改掉。
- 此时,内存里的数据是“最新的”,而磁盘上的数据还是“老数据”。这个内存与磁盘不一致的数据页,就被称为“脏页(Dirty Page)”。
# 2. 通过 Flush 链表进行追踪
MySQL 内部有一个专门的链表叫 Flush List(刷脏链表)。一旦某个数据页变成了脏页,它的指针就会被加入到这个链表里。
- 对于接下来的连表查询(JOIN):由于数据在内存里已经是最新状态了,后续的 JOIN 查询直接读取这个“脏页”里的新名字,拿到的就是正确的结果。
- 缓存没有失效,它只是变新了。
# 3. 异步刷盘(真正落盘)
MySQL 后台有专门的 Master 线程,会根据系统的空闲程度、Redo Log 的落盘进度,不慌不忙地把 Flush 链表里的“脏页”异步写入到磁盘中,让内存和磁盘达成一致。
# 二、 那么,内存什么时候会真正“失效 / 擦除”呢?
既然修改只是让缓存变新,那内存里的数据页什么时候会被彻底清理(失效)呢?这取决于内存够不够用。
Buffer Pool 的大小是有限的(由参数 innodb_buffer_pool_size 决定)。当有很多新的查询进来,内存满了装不下时,MySQL 就必须淘汰旧的缓存。它使用的是一种改进版的 LRU(Least Recently Used,最近最少使用)算法。
- 冷热分代:MySQL 把存放缓存页的 LRU 链表分成了两部分:5/8 的热数据区(New Sublist)**和**3/8 的冷数据区(Old Sublist)。
- 末位淘汰(真正的失效):当新查询需要加载新的磁盘页,而内存满了,MySQL 就会盯住冷数据区最末尾的数据页。
- 如果这个页是干净的(没被修改过):直接从内存中无情抹去(失效),腾出空间。
- 如果这个页是脏页(被修改过但还没写回磁盘):MySQL 会被迫触发同步刷盘,把数据写回磁盘,然后再把这个内存页抹去。
# 三、 总结:MySQL 的单表缓存判断逻辑
回到你的问题:数据改变了,缓存如何判断失效?
- 在业务读写时:MySQL 不需要判断失效。因为数据改变是直接在缓存(内存)里改的,缓存永远保持最新。连表查询(JOIN)每次去读单表缓存,读到的都是最新修改后的数据。
- 在内存管理时:MySQL 通过 LRU 算法的冷热分代来判断哪些单表缓存太久没人访问了。只有没人访问的旧数据页,才会被彻底踢出内存(真正意义上的缓存失效)。
这套设计非常精妙,它用“只改内存 + 异步写盘”的机制,把昂贵的磁盘 I/O 变成了极快的内存操作,从而撑起了动辄几万的并发读写。