假设一个数据库使用完全恢复模型,当在 SQL Server 中写入记录时(通过INSERT
/ UPDATE
etc),预写日志记录将确保在修改数据页之前将更改写入日志文件。
日志和数据页条目都在 RAM 中生成,稍后由检查点提交到磁盘。
如果系统崩溃(为了争论而断电)脏页(在 RAM 中更改但未提交到磁盘的 IE 数据)会发生什么情况,因为 RAM 的内容无法在系统重启后继续存在,这些数据是否丢失?
编辑
经过一些测试,我可以看到脏页没有丢失,但我不确定为什么:
使用本教程
创建测试数据库
CREATE DATABASE DirtyPagesDB
GO
USE DirtyPagesDB
GO
关闭自动检查点
DBCC TRACEON(3505, -1);
DBCC TRACESTATUS();
创建一个表,插入一些数据并发出检查点:
CREATE TABLE t1 (Speaker_Bio CHAR(8000))
GO
INSERT INTO t1 VALUES ('SQL'),('Authority')
GO
CHECKPOINT
确认没有脏页
-- Get the rows of dirtied pages
SELECT
database_name = d.name,
OBJECT_NAME =
CASE au.TYPE
WHEN 1 THEN o1.name
WHEN 2 THEN o2.name
WHEN 3 THEN o1.name
END,
OBJECT_ID =
CASE au.TYPE
WHEN 1 THEN p1.OBJECT_ID
WHEN 2 THEN p2.OBJECT_ID
WHEN 3 THEN p1.OBJECT_ID
END,
index_id =
CASE au.TYPE
WHEN 1 THEN p1.index_id
WHEN 2 THEN p2.index_id
WHEN 3 THEN p1.index_id
END,
bd.FILE_ID,
bd.page_id,
bd.page_type,
bd.page_level
FROM sys.dm_os_buffer_descriptors bd
INNER JOIN sys.databases d
ON bd.database_id = d.database_id
INNER JOIN sys.allocation_units au
ON bd.allocation_unit_id = au.allocation_unit_id
LEFT JOIN sys.partitions p1
ON au.container_id = p1.hobt_id
LEFT JOIN sys.partitions p2
ON au.container_id = p2.partition_id
LEFT JOIN sys.objects o1
ON p1.OBJECT_ID = o1.OBJECT_ID
LEFT JOIN sys.objects o2
ON p2.OBJECT_ID = o2.OBJECT_ID
WHERE is_modified = 1
AND d.name = 'DirtyPagesDB'
AND
(
o1.name = 't1'
OR o2.name = 't1'
);
GO
确认最后一个检查点的时间
SELECT f1.[Checkpoint Begin], f2.[Checkpoint End]
FROM fn_dblog(NULL, NULL) f1
JOIN fn_dblog(NULL, NULL) f2
On f1.[Current LSN] = f2.[Previous LSN]
WHERE f2.Operation IN (N'LOP_BEGIN_CKPT', N'LOP_END_CKPT');
添加更多行
INSERT INTO t1 VALUES ('SQL'),('Authority')
使用上面的查询来确认有脏页
从任务管理器中终止 SQL Server 任务以模拟关机。
启动服务
重新运行上面的命令以获取最后一个检查点时间,它是相同的(IE 除了我们手动执行的检查点之外没有运行检查点)
从表 t1 中选择,所有四个记录都在那里
这种说法并不完全正确。Checkpoint(和 Lazy Writer)将数据页写入磁盘是正确的。但是,当提交事务以保证事务持久性时,日志记录会物理写入磁盘。提交的事务数据永远不会只驻留在内存中(除非延迟持久性)。
所有数据修改都首先写入日志(预写日志记录),然后再写入脏页。页面和日志记录可能包括磁盘上已提交和未提交的数据。
无论恢复模式如何,SQL Server 都会在崩溃恢复期间扫描日志到最后一个检查点,从该点向前滚动所有数据修改,最后回滚未提交的事务。