数据库的物理存储会分成许多个页(Page),记录都存储在页中,数据每一次IO,操作的最小单位是一个页。数据库运行时,会在内存中维护一个缓冲区(Buffer Pool)。缓冲区缓存部分的页(因为内存有限,所有不能把所有数据都缓存进来)。每次对数据的操作,都需要把磁盘中的页读入缓冲区,然后再进行操作;如果缓冲区中有对应的页,就不需要读磁盘。如果数据库要读入一个页,而此时需要缓冲区已经被用完了,这个时候就要替换出一个页面,腾出一块内存给新读入的页面。如果被换出的页被改过(在内存中每个页会有个Dirty标记,标识当前页面是否被修改过),就要把内存页写入到磁盘中。
数据库为了实现事务,需要并发控制和恢复机制。
恢复机制,可以用log实现,多采用steal(未提交的数据可以写入磁盘),no-force(提交的数据可以不写入磁盘)策略。
并发控制,现在的多数数据库用MVCC(MVCC+2PL或MVCC+SSI),由于steal、no-force策略,这个部分完全就相当于在内存中做,直接更新的是内存页中的元组。只有发生页换出,或是checkpoint(定期的刷脏页)时,才会写磁盘。
所以,不管提交之前还是提交之后,数据在内存中,或是被写入磁盘,都是有可能的。
补充说明下恢复机制。以上是针对存储元组的页面和缓冲区来说的,log还是要在接交时写入磁盘的,以保证已提交的事务是持久化的。
以MySQL为例,MySQL使用REDO和UNDO来实现恢复。其中,REDO 为了重做对数据更改保存的信息,用于恢复;UNDO是与事务相关的, 为了撤销对数据更改保存的信息,用于回滚。每一条REDO日志记录都有一个LSN(Log Sequence NO) 日志号,一个递增的64位整数,一个LSN表示一个redo log结构。
在三个阶段涉及到日志的操作,分别是写入数据时,事务提交时,数据库恢复时。
一)写入数据,以update为例, Insert与之类似
1.计算更新后tuple到原tuple的delta信息,把这个delta复制到rollback segment中的undo
2.写redo log,记录对rollback segment的更改
3.把buffer pool中的对应tuple更新成新值,把新值的rollback pointer指向第1部生成的undo
4.写redo log,记录对数据page的更改
5.将page改成dirty
二)事务提交
force log, flush当前事务的redo log到磁盘
三)数据库恢复
1.启动开始时检测是否发生崩溃
2.定位到最近的一个checkpoint
3.定位在这个checkpoint flush到磁盘的数据,检查checksum。如果不正确,说明这个页在上次写入是不完整的,从doublewrite buffer里把正确的page读出来,更新到buffer中的page
4.分析redo log,标识出未提交事务
5.顺序执行redo
6.rollback未提交的事务