在今天的文章里我想谈下每个DBA应该知道的一个重要话题:在SQL Server里如何进行页级别还原操作。假设在SQL Server里你有一个损坏的页,你要从最近的数据库备份只还原有问题的页,而不是还原整个数据库。
一、 如何破坏一个页
第一步我想向你展示下如何建立表(或索引)里有个特定页损坏的情景,首先构造一些数据。
USE master
GO
CREATE DATABASE PageLevelRestores
GO
USE PageLevelRestores
GO
-- Create a table where every record fits onto 1 page of 8kb
CREATE TABLE Test
(
Filler CHAR(8000)
)
GO
-- Insert 4 records
INSERT INTO Test VALUES (REPLICATE('A', 8000))
INSERT INTO Test VALUES (REPLICATE('B', 8000))
INSERT INTO Test VALUES (REPLICATE('C', 8000))
INSERT INTO Test VALUES (REPLICATE('D', 8000))
GO
-- Retrieve the selected records
SELECT * FROM Test;
下一步进行全备,这个备份包含了属于Test表的所有页。这非常重要,因为接下来我们会破坏这个表的一个特定页。为了找出属于Test表的页,我用DBCC IND命令来返回所有属于这个表的页。
-- Perform a full database backup
BACKUP DATABASE PageLevelRestores TO DISK = N'C:\Backups\PageLevelRestores.bak'
GO
-- Retrieve the first data page for the specified table (columns PageFID and PagePID)
DBCC IND(PageLevelRestores, Test, -1)
GO
要破坏一个特定的页,可以使用未公开的DBCC WRITEPAGE命令。
ALTER DATABASE PageLevelRestores SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
-- Let's corrupt page 90...
DBCC WRITEPAGE(PageLevelRestores, 1, 90, 0, 1, 0x41, 1)
DBCC WRITEPAGE(PageLevelRestores, 1, 90, 1, 1, 0x41, 1)
DBCC WRITEPAGE(PageLevelRestores, 1, 90, 2, 1, 0x41, 1)
GO
ALTER DATABASE PageLevelRestores SET MULTI_USER;
这里我模拟了有个存储错误,写了一些垃圾到存储的页里。现在当你从表再次读取数据库,SQL Server会返回你824 I/O错误,因为对损坏页的校验失败了。
-- Retrieve the selected records
SELECT * FROM Test;
一旦SQL Server在I/O访问期间检测到损坏的页,会将其记录在msdb.dbo.suspect_pages里。
SELECT * FROM msdb.dbo.suspect_pages;
对msdb里对特定表进行监控是个很好的想法,可以得到你的数据库里是否有损坏的页。现在我们让事情变得更糟糕,往表里插入另外一条记录。
-- Now we have additional transaction that we don't want to loose...
INSERT INTO Test VALUES (REPLICATE('E', 8000));
二、 如何还原损坏的页
现在你想恢复这个数据库到正确状态且不丢失数据,你会怎么做?
首先要进行所谓的尾日志备份 Tail-Log Backup:备份自上次事务日志备份后的已发生的事务。
-- Backup the transaction log
BACKUP LOG PageLevelRestores TO DISK = 'C:\Backups\PageLevelRestores_LOG1.bak' WITH INIT;
在这里还没有进行过事务日志备份,因此我们的备份会包含自完整备份后,所有已执行的事务。现在可以在SQL Server里初始页级别还原操作。使用传统的RESTORE DATABASE命令,但只要指定想要还原的页,不用还原整个数据库。对于大型数据库,这会有很大的区别。
USE master
-- Restore full database backup
RESTORE DATABASE PageLevelRestores
PAGE = '1:90'
FROM DISK = 'C:\Backups\PageLevelRestores.bak'
WITH NORECOVERY
GO
现在到了棘手的部分:在执行RESTORE DATABASE命令后,要再进行一次事务日志备份,因为接下来你要保证在这个可用页进行的所有改变用作还原。没有这个额外的日志备份,SQL Server不能把你的页重新上线。
-- Backup the tail of the log...
BACKUP LOG PageLevelRestores TO DISK = 'C:\Backups\PageLevelRestores_LOG_TAIL.bak' WITH INIT;
进行完这个额外日志备份后,你可以按正确的顺序恢复所有日志备份,最后把数据库上线。
-- Restore all available log backups in the correct order
RESTORE LOG PageLevelRestores FROM
DISK = 'C:\Backups\PageLevelRestores_LOG1.bak'
WITH NORECOVERY
GO
-- Finally restore the tail log backup
RESTORE LOG PageLevelRestores FROM
DISK = 'C:\Backups\PageLevelRestores_LOG_TAIL.bak'
WITH NORECOVERY
GO
-- Finally finish with the restore sequence
RESTORE DATABASE PageLevelRestores WITH RECOVERY
GO
现在当你再次查询表时,你会看到SELECT语句成功执行没有任何I/O错误,在这个表里没有丢失任何数据。
USE PageLevelRestores
GO
-- Retrieve the selected records
SELECT * FROM Test;