Write-Ahead Transaction Log
引用:http://msdn.microsoft.com/en-us/library/ms186259(SQL.90).aspx
最近在学习sql server事务日志的东西,知道现在大部分数据库都支持先写log再写数据的策略,也就是保证数据不先于日志写入磁盘。我们将这样的日志叫做Write-Ahead Transaction Log。Write-Ahead Transaction Log的完整解释可以在MSDN上找打,写的非常详细,也很透彻,不难懂,我把这部分内容摘录下来。
Write-Ahead Transaction Log的解释
SQL Server 2005 uses a write-ahead log (WAL), which guarantees that no data modifications are written to disk before the associated log record is written to disk. This maintains the ACID properties for a transaction. For more information about transactions and ACID properties, see Transactions (Database Engine).
解释:SQL Server2005使用write-ahead log,这种日志能够保证数据不会先于日志被写入磁盘。这种机制能够保证事务的ACID特性。后面解释write-ahead transaction log如何保证ACID。
To understand how the write-ahead log works, it is important for you to know how modified data is written to disk. SQL Server maintains a buffer cache into which it reads data pages when data must be retrieved. Data modifications are not made directly to disk, but are made to the copy of the page in the buffer cache. The modification is not written to disk until a checkpoint occurs in the database, or the modification must be written to disk so the buffer can be used to hold a new page. Writing a modified data page from the buffer cache to disk is called flushing the page. A page modified in the cache, but not yet written to disk, is called a dirty page.
sql server是从buffer cache中读取数据内容的,如果buffer cache中没有需要的数据,那么就得先从磁盘中将数据读入到buffer cache。sql server 最小的IO单位是8kb大小的data page,所以即使我们查询的结果只有一行内容,我们读取的也是一整个数据页。
sql server对数据的直接操作都发生在buffer cache上面,而不是直接在磁盘上面改。这些从磁盘中读入到buffer cache中且被修改过的页,叫做dirty page,也就是脏页,这些脏页只有在两种情况下会被写回磁盘:
- 当sql server中checkpoint到来的时候。
- 当buffer cache发现不够空间装载新的data page的时候,他会将dirty page 写回到磁盘,而那些从磁盘读入buffer cache中又没有被修改的data page,会被释放掉。lazywriter
将dirty page 写回到磁盘的过程叫做flushing the page。flush这个词在变成的时候也经常见到,表示将内存中的数据强行写回磁盘。
At the time a modification is made to a page in the buffer, a log record is built in the log cache that records the modification. This log record must be written to disk before the associated dirty page is flushed from the buffer cache to disk. If the dirty page is flushed before the log record is written, the dirty page creates a modification on the disk that cannot be rolled back if the server fails before the log record is written to disk. SQL Server has logic that prevents a dirty page from being flushed before the associated log record is written. Log records are written to disk when the transactions are committed.(难以理解,如果log页有cache,那么log也存在丢失。那事务如果一直不提交,日志是不是一直没有被写入到磁盘,那么假如这个时候数据库崩溃的话,日志记录是不是也就丢失了。)
PS:刚刚跟同事讨论,最后一句话的意思可以理解为在事务commit的时候,日志肯定会被写入到磁盘,而在commit之前,日志会被写入到log cache,但是log cache很小。很快会因为log cache满了而将log record写入磁盘。从而保证了日志肯定是先写的,而且是写入到磁盘。thanks to gang。
-----------------------------------------------------------------------
在Microsoft SQL Server企业级平台管理实践一书的7.1节中提到:(ps:2012-8-24)
2. 在任何Insert/Update/Delete提交之前,SQL Server需要保证日志记录能够写入到日志文件里。
严格来讲,SQL Server不是直接将日志记录写入日志文件里的,中间还有一层日志记录缓存。SQL Server会将修改所产生的日志记录写入日志记录缓存区,而SQL Server又会保证缓存区里的内容会很及时地写入日志文件,哪怕有任何意外发生(包括掉电、服务异常等)。所以对用户来讲,可以粗略地理解成,在任何Insert/Update/Delete提交之前,SQL Server都须要将它所对应的日志记录写入日志文件里。每次写的数量和数据的修改量有关系。
3. 当SQL Server做Checkpoint的时候,需要将内存缓冲区中已经发生过修改的数据页面同步到硬盘中的数据文件里。
一般情况下,SQL Server会以一分钟左右的间隔,做checkpoint。如果数据库上的修改比较多,checkpoint的频率会高一些,反之会低一些。做checkpoint的时候,会发生向数据文件的写操作。写的数量和上次checkpoint以来发生的数据修改量有关系。
4. 当SQL Server Buffer Pool发生压力的时候,会触发Lazy Writer,主动将内存里的一些很久没有使用过的数据页面和执行计划清空。如果这些页面上发生的修改还未由checkpoint写回硬盘,Lazy Writer会将其写回。
所以这时候会发生到数据文件的写操作。写的数量和Lazy Writer需要清空的数据页面数量有直接关系,也就是说,和内存的缺口及SQL Server自己的数据修改量都有关系。
----------------------------------------------------------------------------------------
数据页有缓存,日志也有缓存。当data page在cache buffer中被修改的时候,会产生相应的修改记录,我们称它为log record,被保存在log cache中。每一个log record都会有一个dirty page与之相对应,sql server会保证这些log record先于对应的dirty page被写入到磁盘。log record会在事务commit的时候被写入磁盘(但是并不是说只有在commit以后日志才会被写入磁盘,commit之前log也会被写入,但是commit的时候肯定会清空log cache将日志写入磁盘。)。但是dirty page会不会被写入磁盘与事务是否commit无关,只与前面提到的两点(checkpoint和buffer cache空间不足)有关。
假如dirty page先于log写入磁盘,然后发生sql server 服务重启,重启后首先进行的操作就是recovery,而recovery的操作是完全基于日志执行的,recovery以后可能导致事务无法回滚。
举例来说,一个银行转账事务,从A(1000)账户想B(2000)转账100元人民币,这个事务由以下两步操作组成,首先是A-100,然后是B+100。假如A账户减去100的操作记录在脏页A中,其日志记录LogRecordA中对应的内容应该是: A:1000 A:900 而B账户加上100的操作记录在脏页B中,其日志记录LogRecordB对应的内容应该是 B:2000 B:2100 因此dirty page 先于日志写入磁盘,此时假设我们数据都成功写入磁盘,LogRecordA也成功写入磁盘,但是在写LogRecordB之前数据库服务重启,也就是说B:2100这条记录没有被写入到日志文件中去。数据库服务重启以后按照日志进行recovery,那么因为缺少了B:2100的日志记录,B账户还是只有2000,但是A账户确少了100,证明这个事务没有回滚。
综上所述,日志先于数据写入磁盘能够保证事务的ACID.
事务的ROLLBACK与日志的关系
每一个事务都有一个TransactionID,而每条记录也有一个TransactionID与每一个事务相对应。当我们想要rollback一个事务的时候,我们只需要找到所对应TransactionID的所有日志记录,将数据修改到执行事务之前的状态就可以了。
还有,在我看来,回滚是为了保持事务的原子性和一致性,因此,假如事务已经commit了,也就是事务结束了,再去想回滚,那么就没有意思了。如果想回滚之前的操作,可以有专门的软件生成回滚脚本。比如apexsql这个软件,就可以根据日志记录生成回滚脚本。
事务的ACID与Write-Ahead Transaction Log的关系
下面给出原子性和持久性为例加以解释。
原子性:一个事务中的所有操作,要么全部完成, 要么全部撤销。当我们的事务完成了一半的时候数据库崩溃。为了保证事务的原子性,原先执行的操作必须rollback。根据我们之前知道的rollback是根据日志来执行的。如果我们不是write-ahead,那么可能导致事务不能回滚。这个在前面的例子中说过。
持久性:所有commit的事务都会被写入磁盘,这是因为所有commit的事务都被记录到了log中。有log就能保证数据在磁盘中。