在今天的文章里我想谈下每个DBA应该知道的一个重要话题:在SQL Server里如何进行页级别还原操作。假设在SQL Server里你有一个损坏的页,你要从最近的数据库备份只还原有问题的页,而不是还原整个数据库。
我们来破坏一个页
第一步我想向你展示下如何建立表(或索引)里有个特定页损坏的情景,这里我们会进行一些魔术,因为开箱即用(out-of-box)的SQL Server本身不会引入任何损坏的页(如果有的话,恭喜你找到了一个BUG)。我们从创建一个新的数据库,往新建的表插入一些记录开始。
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
GO
下一步我进行完整数据库备份。这就是说这个备份包含了属于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命令。是的,在SQL Server里有个可用的DBCC WRITEPAGE命令,但请不要告诉任何人……
ALTER DATABASE PageLevelRestores SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO -- 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
GO
为了使用DBCC WRITEPAGE,问题数据库必须设置如代码所示的单用户模式(Single-User mode)。这里我模拟了有个存储错误,写了一些垃圾到存储的页里(是的,这个在你工作中也会碰到!)。现在当你从表再次读取数据库,SQL Server会返回你一个824 I/O错误,因为对损坏页的校验失败了。
-- Retrieve the selected records
SELECT * FROM Test
GO
一旦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))
GO
我们来还原损坏的页
现在你是DBA,你想恢复这个数据库到正确状态且不丢失数据(像我们在最后一步插入的记录)。你会怎么做?首先你要进行所谓的尾日志备份(Tail-Log Backup):你要备份自上次事务日志备份后的已发生的事务。
-- Backup the transaction log
BACKUP LOG PageLevelRestores TO
DISK = 'C:\Backups\PageLevelRestores_LOG1.bak'
WITH INIT
GO
在这里还没有进行过事务日志备份,因此我们的备份会包含自完整备份后,所有已执行的事务。现在我们可以在SQL Server里初始页级别还原操作。这里你使用传统的RESTORE DATABASE的T-SQL命令,但你只要指定想要还原的页,不用还原整个数据库,我们只要还原有问题的页。如果你在处理很大的数据库,这会有很大的区别。
USE master
-- Restore full database backup
RESTORE DATABASE PageLevelRestores
PAGE = '1:90'
FROM DISK = 'C:\Backups\PageLevelRestores.bak'
WITH NORECOVERY
GO
现在到了棘手的部分:在RESTORE DATABASE的T-SQL命令后,你要进行另一个事务日志备份。这个另外的日志备份是需要的,因为接下来你要保证在这个可用页进行的所有改变用作还原。没有这个额外的日志备份,SQL Server不能把你的页重新上线。
-- Backup the tail of the log...
BACKUP LOG PageLevelRestores TO
DISK = 'C:\Backups\PageLevelRestores_LOG_TAIL.bak'
WITH INIT
GO
进行完这个额外日志备份后,最后你可以在正确的顺序里恢复所有的日志备份,最后你可以把数据库恢复上线。
-- 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
GO
小结
在SQL Server里如何进行页级别恢复操作是每个DBA应该知道的。它是你工具箱里最重要的工具之一——尤其当你在处理很大的数据库时。不用恢复整个数据库,你只恢复有问题的页,整个恢复过程会非常快。
最后给你一个问题,各位看官:你是否有过在SQL Server进行页级别的恢复,如果是的话——对你来说它有多难/简单?请畅所欲言!
感谢关注!