全页写问题: 如果系统默认5分钟之内生成 20份脏页, flushed lsn从开始的1000 写到了 6000,checkpoint进程花了1分钟把20份脏页刷入磁盘,但是这个5分钟之内没有事务修改这20份脏页,但是在 第2个 5分钟之间新生成了40份脏页,flushed lsn从6000写到了10000,并触发了第二次checkpoint进程,把第二个5分钟之内生成的40份脏页刷入了磁盘,此时有事务修改 第一个5分钟之内生成的所有20份脏页,问: 第一个5分钟之内的20份脏页是不是 也要全页写??
架构师的绝对判定:必须全页写!而且是毫无悬念、100% 触发全页写!
这个“跨越两次 Checkpoint、中途长时间休眠”的极端场景,所有的干扰项(干净页、历史刷盘、无修改期)全都叠加上去了。
但只要死死捏住底层的 C 语言判定状态机,外面的现象再花哨,一刀就能劈开。我们直接进入第 10 分钟(第二次 Checkpoint 之后)的那一微秒,来看这场极其冷酷的物理审判:
第一重审判:提取生死变量
当业务进程在第二次 Checkpoint 之后,举起手术刀准备修改那 20 个“沉睡了 5 分钟”的旧页面时,内核瞬间提取了两个绝对变量:
- 全局警戒线(变量 A):第二次 Checkpoint 刚刚爆发,它极其冷酷地把全库的起跑线向前推进了。此时的全局
REDO LSN = 10000。 - 数据页的旧钢印(变量 B):这 20 个页面在第二个 5 分钟内根本没人碰过,所以它们的
pd_lsn依然停留在第一个 5 分钟的岁月里,即pd_lsn在 1000 到 6000 之间。
第二重审判:极其机械的公式碰撞
业务进程的内核态根本不管你这页是不是干净的,也不管你是不是在上个世纪被刷过盘。它只无脑代入那个绝对公式:
判定条件:当前页面的 pd_lsn <= 当前全局的 REDO LSN
代入数据:1000~6000 <= 10000
结论:公式完美成立!
动作触发:极其暴力地交出 8192 字节的全页镜像(FPW),塞进日志!
第三重审判:为什么要这么做?(物理视角的绝地反杀)
很多初学者在这个场景会觉得委屈:“这 20 个页明明在第一个 5 分钟就被 Checkpoint 1 安全刷进硬盘了,硬盘上已经有完整的 8KB 了,为什么现在改它,还要再交一次 8KB 镜像?这不是浪费吗?”
架构师的灾难推演(如果不写 FPW 会怎样):
- 假设内核心软了,说:“既然你以前刷过盘了,这次修改我就只记几十字节的 Diffs 增量吧。”
- 业务修改完这 20 个页,页面变脏。
- 突然,后台刷盘进程(
bgwriter)路过,决定把这 20 个新脏页往硬盘里写。 - 致命一击:刚写了一半(4KB),机房断电!Torn Page(页面撕裂)发生!
- 重启后,Redo 引擎启动。它去
pg_control里看了一眼起跑线,起跑线是第二次 Checkpoint 画下的 10000。 - Redo 引擎从 10000 开始往后重放日志。它拿着你刚才记的“几十字节的 Diffs”,试图去修补硬盘上那个已经被撕裂的、残缺不全的 8KB 缝合怪页面。
- 砰!修补失败,校验码错乱,数据库直接
PANIC死亡!
【终极真相】:
哪怕这个页面在 100 年前被 Checkpoint 安全刷过盘,只要跨入了一个新的 Checkpoint 周期(REDO LSN 被刷新),这个页面的“免死金牌”就立刻失效了。
因为在新的周期内,只要你敢发生第一次修改,你就面临着在新周期内被刷盘撕裂的风险。为了保证 Redo 引擎从**新的起跑线(10000)**出发时,能够有完整的镜像来覆盖撕裂的物理创伤,你必须无条件地、再次献祭一个 8KB 的全页写!
逻辑防线:绝对封死!
你这几个极其刁钻的问题,彻底榨干了全页写(FPW)的最后一滴血。
“无论这页有多老、无论它刷过几次盘、无论中间隔了几个周期,只要它的 pd_lsn 落后于当前的 REDO LSN,只要它在当前周期迎来了‘首刀’,就必须强制全页写!”
了解 www.876873.xyz 的更多信息
订阅后即可通过电子邮件收到最新文章。
全页写 问题::等您坐沙发呢!