数据库文件损坏可能是DBA面临到的最头疼的问题,在这篇文章中,我将向大家解释一些不应该在数据库文件损坏时对数据库的一些操作,然后根据具体情况为大家讲解一些应该根据情况做出的操作,帮助您解决此方面的问题。

如何确定文件损坏?

文件损坏还是比较容易确定的,当有查询访问非合法的页数据时,查询就会以高严重性级别错误而导致终止。备份和重建索引的作业会失败。一些典型的错误提示如下:

SQL Server detected a logical consistency-based I/O error: incorrect checksum (expected: 0xfdff74c9; actual: 0xfdff74cb). It occurred during a read of page (1:69965) in database ID 13 at offset 0x0000002229a000 in file 'D:\Develop\Databases\Broken1.mdf'.
Attempt to fetch logical page 1:69965 in database 13 failed. It belongs to allocation unit 72057594049069056 not to 281474980642816. 

 

更可怕的事情在于,如果平常没有定时的完整性检查,那么错误可能存在小时、天、甚至上月,这样产生的后续问题就很难处理了。

本文不会讨论数据库的质疑状态,因为解释为什么会质疑,发现数据库质疑的原因、解决质疑的种种方法都可以拿一整篇文章或者书来讨论。

数据文件损坏时我们应该做什么?

  1. 不要惊慌(译者注:废话..)
  2. 不要分离数据库
  3. 不要重启数据库服务器
  4. 不要急切的运行REPAIR
  5. 运行完整性检查
  6. 最后,做一次深入的分析,分析产生问题的根本原因

不要惊慌

惊慌的后果往往是不理性的思考甚至不思考

不要分离数据库

当服务器报告数据文件损坏时,一般来说某些被破坏页确实存在在SQL数据文件中,所以,分离附加、备份还原、重启数据库服务这些技俩是不能排除错误的

不要重启数据库服务

同上,重启数据库服务并不能改善情况。

比之分离附加,重启有可能会使情况更糟糕,如果SQL在实施“重启恢复”(restart-recovery)的时候碰到了数据文件损坏,那么数据库就会被标注为质疑状态,将以后的修复工作变得更加困难。

不要急切的运行REPAIR

有些工程师可能会使用CheckDB 并使用 Allow Date Loss 选项并相信此举会解决很多问题,其实在很多时候,运行DBCC Allow Data Loss 并不是推荐的方法,因为这不能修复所有错误,并且可能会导致数据丢失。

在很多情况下,此举是最后迫不得已的选择,只应在其他方法都无法解决的情况下使用,而不应该作为首选方法使用。

运行完整性检查

为了确定修复数据错误的方法,或者到底数据的哪部分出现了错误,你应该运行 CheckDB 并使用 All_ErrorMsgs 选项。另外,No_Infomsgs 选项可以禁止显示具体在哪页出现的错误,这些对我们排错并没有太多帮助。

对于大型数据库,CheckDB可能使用很长的时间,但我们应该等待它运行完成以报告所有错误,我们的修复策略应该是基于所有错误来制定的。

分析根本原因

 

 

当修复完成时,我们还应该继续排查更根本的原因,不然错误还是有可能再次发生的,大部分问题可能是IO系统,用户误操作或者是杀毒软件等原因造成的。

修复错误

根据CheckDB的结果,我们可以指定相应的修复策略了,在这里笔者列举一些常见的错误及其修复方法。

这个列表从错误的严重性排列,以从轻微到严重的顺序,每个等级的错误都会给出典型的错误提示及其解决方法。

Inaccurate Space MetaData

Msg 2508, Level 16, State 3, Line 1
The In-row data RSVD page count for object "Broken1", index ID 0, partition ID 76911687695381, alloc unit ID 76911687695381 (type In-row data) is incorrect. Run DBCC UPDATEUSAGE.

对于索引 ID 为 0、分区 ID 为 1、分配单元 ID 为 1 (类型为  )的对象 " broken",计数 1 不正确。请运行 DBCC UPDATEUSAGE。

此错误提示数据页在保留空间中不正确的值,在SQL 2000中,也此错误也有可能是数据条目或者数据页的数目跟索引或表中记录的不一致所导致的。

CheckDB 不会修复类似错误,在SQL 2005 中CheckDB只会给出一个警告信息。

这不是什么严重的错误,按照提示运行DBCC UPDATEUSAGE行了,这通常发生在SQL 2000升级为2005/2008后,在SQL 2005/2008中一般不会遇到。

Msg 8914, Level 16, State 1, Line 1
Incorrect PFS free space information for page (1:26839) in object ID 181575685, index ID 1, partition ID 293374720802816, alloc unit ID 76911687695381 (type LOB data). Expected value 0_PCT_FULL, actual value 100_PCT_FULL.

对象 ID 1,索引 ID 3,分区 ID 3,分配单元 ID 4 (类型为 5)中页 6 的 PFS 可用空间信息不正确。所需的值为 7,而实际的值却为 8。

此错误表示PFS Page( Page Free Space)包含不正确值,像上错误一样,不是什么严重的错误。可以使用CheckDB + Repair_Allow_Date_Loss 选项进行修复,如果数据库中仅存在这种错误,修复并不会丢失数据。

Corruption only in the nonclustered indexes

如果CheckDB返回的错误提示的ID是2或者比2更大的数字,则表示出错的部分是非聚集索引。因为非聚集索引是可以由表数据重新生成的,所以修复这些错误并没有数据的丢失。
如果CheckDB返回的错误全部是此类错误,则推荐使用CheckDB Repair_Rebuild选项来进行修复。
Msg 8941, Level 16, State 1, Line 1
Table error: Object ID 181575685, index ID 4, page (3:224866). Test (sorted [i].offset >= PAGEHEADSIZE) failed. Slot 159, offset 0x1 is invalid.
表错误: 对象 ID 181575685,索引 ID 4,分区 ID (3:224866). 页 (3:224866). 测试(sorted [i].offset >= PAGEHEADSIZE)失败。槽 159,偏移量 0x1 无效。

Msg 8942, Level 16, State 1, Line 1
Table error: Object ID 181575685, index ID 4, page (3:224866). Test (sorted[i].offset >= max) failed. Slot 0, offset 0x9f overlaps with the prior row.
表错误: 对象 ID 181575685,索引 ID 4,分区 ID (3:224866)测试(sorted[i].offset >= max)失败。槽 159,偏移量 0x1 和前一行重叠。

这些流行的错误通过删除并重建索引可以解决。联机创建索引会先读取原来的索引来创建新索引,所以新的索引还会出问题,这样的话只能先删除旧的,然后创建新的。

其实这些就是CheckDB + Repair_Rebuild 选项所做的事情,但在使用CheckDB时必须使用单用户模式。所以,手工进行重建工作还是非常合适的,这样数据库可以保持联机。

Corruption in the LOB pages Msg 8964, Level 16, State 1, Line 1
Table error: Object ID 181575685, index ID 1, partition ID 72057594145669120, alloc unit ID 72057594087800832 (type LOB data). The off-row data node at page (1:2444050), slot 0, text ID 901891555328 is not referenced.
表错误: 对象 ID 181575685,索引 ID 1,分区 ID 72057594145669120,分配单元 ID 72057594087800832 (类型为 LOB)。位于页 (1:2444050),槽 0,文本 ID 901891555328的行外数据节点未被引用。

这表示某些LOB数据的行外数据节点没有被任何行引用。出现这种情况的原因可能是:聚集索引损坏、堆结构损坏、已损坏的页被重新分配了。
如果CheckDB返回这些错误,那么运行CheckDB Allow_Data_Loss 将简单的抛弃这些页,相应的数据将会变得不完整。

Data Purity errors Msg 2570, Sev 16, State 3, Line 17
Page (1:1103587), slot 24 in object ID 34, index ID 1, partition ID 281474978938880, alloc unit ID 281474978938880 (type "In-row data"). Column "modified" value is out of range for data type "datetime". Update column to a legal value.
页 (1:1103587),槽 24 位于对象 ID 34,索引 ID 1,分区 ID 23432123423234,分配单元 ID 1234345667567875 (类型为"In-row data")中。列 "modified" 的值超出了数据类型"datetime"的范围。请将该列更新为合法的值。

这表示在某些列中的数据超出了值的合法性范围,举个例子,可能是datetime类型的值,但是分钟计数大于了1440,一个Unicode类型的值但是其中的字节值无法被2整除,这些错误在SQL2000升级到更高版本时是不会被检查的,所以,我们升级是最好运行一次CheckDB 使用Data_purity选项,但是CheckDB没有办法修复这些错误,(因为数据库无法知道怎么修复这些错误)此类错误的修复也很简单,就是手工修改其值,困难的地方在于如何找到包含错误值的行,KB中的这篇文章有更详细的论述:http://support.microsoft.com/kb/923247
Corruption in the clustered index or heap

如果错误发生在聚簇索引的页级或者堆表中,出现此错误时数据已经有丢失,因为聚簇索引中的页级存储的是具体数据,并且数据没有冗余。

如果CheckDB发现了此类错误,那么修复的推荐选项是使用Repair_Allow_Data_loss选项运行CheckDB。

此类错误的经典错误信息一般是:
Server: Msg 8976, Level 16, State 1, Line 2
Table error: Object ID 181575685, index ID 1, partition ID 76911687695381, alloc unit ID 76911687695381 (type In-row data). Page (1:22417) was not seen in the scan although its parent (1:479) and previous (1:715544) refer to it.
Server: Msg 8939, Level 16, State 1, Line 2
Table error: Object ID 181575685, index ID 0, page (1:168576). Test (m_freeData >= PAGEHEADSIZE && m_freeData <= (UINT)PAGESIZE - m_slotCnt * sizeof (Slot)) failed. Values are 44 and 8028.

这些只是部分例子,还有更多的错误,判别这类错误的主要依据是Index是的ID为0或者是1,如果CheckDB返回的Index ID是0或者1,则基本可以判定基础表的数据已经损坏。

这种错误也可以修复,但修复这些错误的代价是丢失一些行甚至页的数据。更值得注意的是,当这些数据被删除的时候,CheckDB不会检查外键,并且不会触发Delete触发器。这可能会引起一些引用完整性问题,并引发逻辑性的数据不一致,所以,修复并不是推荐的解决手段。

如果保存的有完整的数据库备份,从备份中恢复会解决这些问题,如果有没有切断日志链的日志备份将会更好,因为这样可以修复到完美状态,没有任何的数据丢失。(完善备份策略才是数据安全的王道~)

如果没有完整的数据库备份,那么只有使用Repair_allow_data_loss模式来运行CheckDB,这需要数据库运行在单用户模式下。

想知道次模式在聚簇索引的情况下会删除多少数据,可以查看该日志:http://sqlskills.com/BLOGS/PAUL/post/CHECKDB-From-Every-Angle-Using-DBCC-PAGE-to-find-what-repair-will-delete.aspx

Corruption in the Metadata Msg 3853, Level 16, State 1, Line 1
Attribute (object_id=181575685) of row (object_id=181575685,column_id=1) in sys.columns does not have a matching row (object_id=181575685) in sys.objects.
This type of error usually appears in a database upgraded from SQL 2000, where someone did direct updates to the system tables.

在SQL的系统表中并没有强制的主外键约束,所以在SQL2000中,从Sysobjects中删除一行(比如一个表)是完全可能的(在05和08中,SQL为了安全考虑,禁止了对系统表的直接操作,只能通过相应的命令进行-译者注),但在Syscolumns和Sysindexes表中却有此表的列和此表的索引。

在SQL 2000中,CheckDB并不贵检测系统目录,所以此类错误一般不能被发现,但在SQL 2005中,CheckDB会进行这些检查,所以,只有在SQL2005中,这些错误可能会出现。
修复这些错误不是一件简单的事,CheckDB不会修复这些错误,如果在升级到05之前,有最近做的完整的SQL2000的数据库备份,那么可以先将数据恢复到SQL 2000中,然后运行升级。

如果没有SQL 2000的备份,或者备份时间太久,那么只有两种可能去修复这些错误,一个是直接编辑系统表,这是个非常复杂和危险的操作,因为系统表并没有文档并且05的系统表比以前更复杂,更详细的情况可见此Blog:http://www.sqlskills.com/BLOGS/PAUL/post/TechEd-Demo-Using-the-SQL-2005-Dedicated-Admin-Connection-to-fix-Msg-8992-corrupt-system-tables.aspx ;另外的修复手段是使用脚本生成并导出一份生成库的脚本,新建一个库,并导出原库的数据,这是个推荐的解决方案。

Irreparable Corruption

CheckDB并不能解决一切问题,所有此类型的数据只能靠完整的备份来恢复。
此外,只能通过生成脚本的方法来恢复部分数据,典型的此类错误如下:

Msg 7985, Level 16, State 2, Line 1
System table pre-checks: Object ID 4. Could not read and latch page (1:358) with latch type SH.
Check statement terminated due to unrepairable error.
Msg 8921, Level 16, State 1, Line 1
Check terminated. A failure was detected while collecting facts. Possibly tempdb out of space or a system table is inconsistent.

CheckDB需要一些关键的系统表来判断数据库的健康情况,如果这些关键的表损坏,那么CheckDB分析错误都是一件不可能完成的任务,更不用提修复了。

Damaged allocation pages
Msg 8946, Level 16, State 12, Line 1
Table error: Allocation page (1:2264640) has invalid PFS_PAGE page header values. Type is 0. Check type, alloc unit ID and page ID on the page.
Msg 8998, Level 16, State 2, Line 1
Page errors on the GAM, SGAM, or PFS pages prevent allocation integrity checks in database ID 13 pages from (1:2264640) to (1:2272727)

发生这些错误的情况下,数据库的存储映射页已经损坏,这些页是用来标记那些硬盘上那些页和区是已经使用和未使用的,CheckDB是不会修复这些错误的,并且这些错误非常棘手,删除这些页也并不可取,因为每页页都映射着4G的存储数据。