如果数据库比较大,而只是某个数据页损坏了,那么并不需要恢复整个数据库,而只需要通过完整备份,来修复损坏的数据页就可以了,另外,应用后续的日志就可以完全恢复的这个数据页的数据。


测试环境为:SQL Server 2008R2

如果是其他的数据库版本,可能会导致下面的部分代码不能运行。


1、创建一个包含主文件组、读写文件组的测试数据库

use master go  if DB_ID('db_test') is not null    drop database db_test go   CREATE DATABASE db_test ON ( 	NAME = db_test_DATA, 	FILENAME = 'c:\db_test.mdf' ),  FILEGROUP FG_READ_WRITE ( 	NAME = db_test_RW, 	FILENAME = 'c:\db_test_RW.ndf' )  LOG ON ( 	NAME = db_test_LOG, 	FILENAME = 'c:\db_test.ldf' ) GO

2、创建表,插入数据

use db_test go  create table xxx(v int,name varchar(100)) on FG_READ_WRITE  insert into xxx select OBJECT_ID,name from sys.objects go   /* 运行下面的插入数据,由于数据量很大,导致长时间运行. 所以,点了"取消执行查询"按钮.  但通过查询,发现log_reuse_wait_desc的状态为 active transaction:  select name,log_reuse_wait_desc from sys.databases where name = 'db_test'   于是运行如下命令后,状态变为nothing,说明正常了: dbcc checkdb(db_test) */ insert into xxx select v,name from xxx go 10 

3、完整备份

backup database db_test to disk  ='c:\db_test.bak' with format /* 已为数据库 'db_test',文件 'db_test_DATA' (位于文件 1 上)处理了 184 页。 已为数据库 'db_test',文件 'db_test_RW' (位于文件 1 上)处理了 224 页。 已为数据库 'db_test',文件 'db_test_LOG' (位于文件 1 上)处理了 2 页。 BACKUP DATABASE 成功处理了 410 页,花费 0.525 秒(6.091 MB/秒)。 */

4、再次插入数据

insert into xxx select 0,'wc'

5、日志备份

backup log db_test to disk = 'c:\db_test.trn'

6、通过未记载的命令:dbcc writepage来模拟损坏数据页的数据

请不要在生成环境中使用dbcc writepage,因为这个命令可能会导致数据的严重损坏。

  1. dbcc writepage的语法为:  
  2.   
  3. dbcc writepage   
  4. ({ dbid,'dbname' }, fileid, pageid, offset, length, data)  

另外,通过如下的语句,可以查询出需要的修改的数据页,这个最后插入的一条v为0的数据,位于fileID=3,pageID=220,slot=215,这个%%lockres%%是sql server 2008才有的,可以用于返回数据所在的文件id,页id,记录id:

select %%lockres%% as 'RID' from xxx where v = 0 /* v	name	RID 0	wc	3:220:215 */

生成100个字节的错误数据:

select '0x'+REPLICATE('00',100)

模拟损坏数据:

dbcc writepage(db_test,3,220,20,100, 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) /* 消息 8939,级别 16,状态 7,第 1 行 表错误: 对象 ID 68,索引 ID 1,分区 ID 281474981167104,分配单元 ID 72057594037927936 (类型为 LOB data),页 (0:0)。测试(m_freeData >= PageHeaderOverhead () && m_freeData <= (UINT)PAGESIZE - m_slotCnt * sizeof (Slot))失败。值为 0 和 8192。 DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。 */

7、检查数据库,会显示页3:220 的错误:

use master go  dbcc checkdb(db_test) /* 表错误: 对象 ID 0,索引 ID -1,分区 ID 0,分配单元 ID 72057594037927936 (类型为 Unknown),页 ID (3:220) 在其页头中包含错误的页 ID。页头中的 PageId = (0:0)。 CHECKDB 发现有 0 个分配错误和 1 个一致性错误与任何单个的对象都没有关联。  xxx的 DBCC 结果。 消息 8928,级别 16,状态 1,第 1 行 对象 ID 2105058535,索引 ID 0,分区 ID 72057594038779904,分配单元 ID 72057594039828480 (类型为 In-row data): 无法处理页 (3:220)。有关详细信息,请参阅其他错误消息。 对象 'xxx' 的 204 页中有 55081 行。 CHECKDB 在表 'xxx' (对象 ID 2105058535)中发现 0 个分配错误和 1 个一致性错误。 CHECKDB 在数据库 'db_test' 中发现 0 个分配错误和 2 个一致性错误。 对于由 DBCC CHECKDB (db_test)发现的错误,repair_allow_data_loss 是最低的修复级别。 DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。  */

8、备份尾日志:

backup log db_test to disk = 'c:\db_test_trail.trn' with no_truncate /* 已为数据库 'db_test',文件 'db_test_LOG' (位于文件 1 上)处理了 6 页。 BACKUP LOG 成功处理了 6 页,花费 0.100 秒(0.434 MB/秒)。 */

9、通过完整备份,修复数据页 3:220 

RESTORE DATABASE db_test 		page = N'3:220' FROM DISK = 'c:\db_test.bak' with norecovery /* 已为数据库 'db_test',文件 'db_test_RW' (位于文件 1 上)处理了 1 页。 RESTORE DATABASE ... FILE=<name> 成功处理了 1 页,花费 0.177 秒(0.044 MB/秒)。 */

10、还原日志

restore log db_test from disk = 'c:\db_test.trn' with norecovery /* 已为数据库 'db_test',文件 'db_test_RW' (位于文件 1 上)处理了 0 页。 已为数据库 'db_test',文件 'db_test_LOG' (位于文件 1 上)处理了 7 页。 RESTORE LOG 成功处理了 7 页,花费 0.092 秒(0.567 MB/秒)。 */

11、还原尾日志

restore log db_test from disk = 'c:\db_test_trail.trn' with recovery /* 已为数据库 'db_test',文件 'db_test_RW' (位于文件 1 上)处理了 0 页。 RESTORE LOG 成功处理了 0 页,花费 0.024 秒(0.000 MB/秒)。 */ 

12、验证数据是否恢复

--数据全部恢复 select * from xxx