专业书籍精读 · Database Internals · 第 5 章

事务处理与恢复

Database Internals · Ch 5 · Alex Petrov · 2019

EN →

这一章讲什么?

你在手机上点「转账」,屏幕转了半秒,弹出「成功」。就在这半秒里,机房停电了。

前四章讲的是数据库怎么把数据摆得整齐、找得飞快。这一章讲另一件事:改到一半断电了怎么办;两个人同时改同一条记录怎么办。没有这一章,前面那棵漂亮的目录树只是个「单人使用、且永不断电」才成立的玩具。

先说个怪事

你大概以为:硬盘上那些数据文件是数据库的「正本」,旁边那个日志文件是备份。其实反过来。日志才是正本、才是真相;数据文件只是日志的一份「誊清稿」——丢了照着日志重誊一遍就是。数据库的可靠性,整个建立在这层颠倒的关系上。

旧世界为什么难

一,改一件事要动好几个地方。转账要一边减、一边加,这两下必须要么都算数、要么都不算数——只做一半,钱就凭空消失了。

二,改动发生在内存里,而内存断电就没。数据库不可能每改一个字就跑一趟硬盘,它把常用的那几页搬到内存「桌面」上改、攒一批再写回去。中间一停电,桌面上没写回去的全丢。

三,往硬盘上东一处西一处地写,慢得离谱。你改的记录散落在第 3 页、第 700 页、第 9 万页;硬盘最怕来回翻找,最喜欢「一直往后写」。

核心机制:一本流水账,加两种锁

先记流水账,再动正本。要改什么,先在一本只往后添、从不回头改的流水账上写一行「我要把第 700 页那个数从 100 改成 80」;这一行落盘了,才允许去动正本。流水账一路往后写,正合硬盘胃口。于是提交一笔转账,你只需等这一行账落盘——正本什么时候誊清,慢慢来。

崩溃之后,先重演,再撤销。最反直觉的一步:断电重启,数据库不是先清理烂摊子,而是拿起流水账把整段历史原样重演一遍——连那些明知最后要撤销的动作也照做。等世界还原到断电前一刻,它才回头把那些「话说了一半、没来得及说『我做完了』」的事务倒着撤掉。先铺平地板再挪家具,比一边铺一边挪简单得多。

两种完全不同的「锁」。一种管人:不让你看到别人做了一半的事,跟着整笔事务从头持到尾。另一种管手:内存里那页纸正被改写,别的线程这会儿读到的会是半成品——它只护住那几微秒,改完立刻放。中文都叫锁,其实是两码事,混淆它们是新手最常摔的跟头。

带来了什么 / 代价是什么

有了这几招,数据库才敢说那句「成功」。它的意思不是「你的钱已誊到正本上」,而是「这件事已写进流水账、流水账已真的落盘;剩下的交给我,断电也丢不了」。代价也藏在这句话里:只要链条上有一环(硬盘、固件、文件系统)谎称「写好了」,上面所有精巧设计一起落空。

一句话记住

数据库的真相不在数据文件里,在那本只往后添的流水账里:改任何东西之前先记账,提交只等这一行账落盘;断电重启后,先照账把历史整个重演一遍,再把没说完的话撤掉。至于并发,记住有两种锁——一种护事务之间的规矩,一种只护内存里那页纸的几微秒。

想进到具体机制、记号和示意图? → 切到精读版