DBCC修复不同情况下的损坏
四. Database Mirroring和AlwaysOn的页面自动修复功能
SQL Server 对数据库损坏的错误类型做了细化,在此对几个典型的错误作一下介绍。
错误信息是:“在文件 '%ls'中、偏移量为 %#016I64x 的位置执行 %S_MSG 期间,操作系统已经向 SQL Server 返回了错误 %ls。”
“The operating systemreturned error %ls to SQL Server during a %S_MSGat offset %#016I64x in file '%ls'.” 例如: Msg 823, Level 24, State 3, Line 1 The operating system returned error 5(Access is denied.) to SQLServer during a write at offset 0x0000000000e000 in file 'FilePath\FileName'.
823错误代表SQLServer在向操作系统申请某个页面读写的时候遇到Windows读取或写入请求失败。Windows返回的错误代码和相应的文本会插入消息中。对于读取操作,SQL Server在报出823错误之前已经重试读取请求4次。
从错误产生的机制可以看出,823错误是发出一个页面读写请求时发生的,和读写的内容没有关系。所以823错误和SQLServer本身无关。通常是物理文件损坏导致此错误,但也可能是设备驱动程序导致的。如果某个数据文件上反复出现823错误,要不就是硬件设备出了问题,要不就是数据文件已经发生了非常严重的损坏。这个错误基本上意味着数据页里的有效数据已经丢失,一般DBCCCHECKDB很难修复。
错误信息是:“SQLServer检测到基于一致性的逻辑I/O错误 %ls。在文件 '%ls' 中、偏移量为 %#016I64x 的位置对数据库 ID %d 中的页 %S_PGID 执行 %S_MSG 期间,发生了该错误。”
“SQL Server detecteda logical consistency-based I/O error: %ls. It occurred during a %S_MSG of page %S_PGIDin database ID %d at offset %#016I64x in file '%ls'.”
例如:
SQL Server detected a logical consistency-based I/O error: tornpage (expected signature: 0x0; actual signature: 0x4e0372a8).
It occurredduring a read of page (1:0) in database ID 13 at offset 0000000000000000 infile 'S:\Microsoft SQL Server\MSSQL.1\MSSQL\Data\www71_global_Data.mdf'.
此错误表明Windows报告已从磁盘成功读取页,但SQL Server检测到页中存在逻辑错误。那么SQLServer会检测到哪些“逻辑错误”呢?常见的错误类型有以下几种。
Checksum
SQL Server可以在写入每个页面时,根据页面里的数据算出一个校验值,一同存储到页面里去。当下次读取页面的时候,再根据这次读到的页面数据,算出一个新的校验值。如果写入和读出的数据一模一样,那么两个校验值一定是相等的。而如果两个校验值不相等,意味着上次SQLServer写入的数据和这次读出来的内容一定不同,现在读出来的数据有问题。通过这种方法,SQL Server能够发现数据页面损坏。
TornPage
残缺页(Torn Page)保护其实是一种对电源故障导致的页损坏进行检测的方法。例如,意外电源故障可能导致一个页面只有一部分被写入了磁盘。使用残缺页保护时,页的每个512B扇区末尾会放置一个2位签名(在将原来的2位复制到页头之后)。每次进行写操作时,这个签名在二进制数01和10之间交替,这样始终可以确定是否只有部分扇区写到磁盘。如果稍后读取页时发现某个位的状态不正确,则说明该页没有被正确写入,因此检测到问题页面,称为残缺页。相对于Checksum,残缺页检测使用的资源最少,但是它的算法太简单,无法检测到磁盘硬件故障导致的所有错误。
ShortTransfer
读到的数据长度比预期的少。例如,一个读取要求预期可以读到8KB的数据,可是实际只返回了4 KB。这也意味着当前读到的页面有损坏。
Bad PageId
在读到页面后,SQL Server会比较页面开头存储的页面编号和自己请求的目标编号。如果发现自己想要读取的页面是第200页,而读到的内容里显示它是第100页,SQL Server就会触发824错误。这种错误经常是因为I/O子系统没有正确地处理SQL Server的请求,传给SQL Server一个错误的页面,甚至是一个空页面。
RestorePending
在SQL Server 的企业版里,用户可以要求在做还原的时候跳过一些有损坏的页面(continue after error)。这些跳过的页面就被标识成“Restore Pending”。如果有用户想去访问它,也会遇到824错误。
StaleRead
一些硬件系统经常发生漏写的现象(SQL Server要求将某个页面写入硬盘文件,I/O子系统报告写入已经完成,可是SQLServer下次读取的时候,读到的还是写入前的内容)。由于读到的老版本页面本身没有什么问题,Checksum和Tong Page算法都不能检查到错误。对这一类问题,SQLServer也有对策。在打开SQL Server启动参数开关-T818以后,SQL Server会在内存里维护一个哈希表,记录下自己所有做过写入动作的页面最新的LSN(Log Sequence Number)值。在下次读出页面的时候,会去比较这两个值是否相等。由于LSN是个自动增长的唯一值,每个发生新修改的页面,LSN的值会比原来的要大。所以如果读到的LSN与内存中存放的不一致,就说明上次的写入请求没有真正完成。这时824错误也会被触发。
从上面的介绍可以看出,824虽然是一个“逻辑错误”,是SQLServer主动发现的数据损坏,但是损坏的来源,大都不是SQL Server自己。这里的错误,主要是由于预期的写入没有完全完成导致的。所以824错误的原因,基本上还是在I/O子系统。由于SQL Server的读写请求是发给Windows,再由Windows发给底层的磁盘系统的,所以问题有可能发生在Windows以下的每一层,例如磁盘驱动器存在故障、磁盘固件存在问题、设备驱动程序不正确等。可以负责任地说,SQLServer自己是不会导致824错误的。
由于824错误是发生在页面这一级的逻辑错误,所以很多时候DBCC CHECKDB能够修复。但是这种修复也仅仅是逻辑上的,页面里面存储的数据在824错误发生之前就已经丢失,SQL Server无法将它们修复回来。所以824错误基本也意味着部分数据丢失。
错误信息是:“尝试在数据库%d 中提取逻辑页 %S_PGID 失败。该逻辑页属于分配单元%I64d,而非 %I64d。”
“Attempt to fetchlogical page %S_PGIDin database %d failed. It belongs to allocation unit %I64d not to %I64d.”
例如:
Attempt to fetch logical page (1:584) in database 2 failed. Itbelongs to allocation unit 445237904015360 not to 72057594060079104.
605也是一个非常有名的数据库损坏错误。此错误通常表示指定数据库中的页或分配已损坏。SQLServer会在根据页链接或使用索引分配映射(IAM)读取属于表的页时,检测到此损坏。分配给表的所有页必须属于与该表相关联的分配单元之一。如果页眉中包含的分配单元ID不匹配与表相关联的分配单元ID,将引发此异常。错误消息中列出的第一个分配单元ID是页眉中显示的ID,而第二个分配单元值则是与表相关联的ID。
严重级别为21表示可能存在数据损坏。造成的原因包括损坏的页链、损坏的IAM或该对象的sys.objects目录视图中存在无效条目。这些错误通常由硬件或磁盘设备驱动程序故障而引起。
严重级别为12表示可能存在暂时性错误,即在缓存中出现错误,但不表示对磁盘上的数据造成破坏。暂时性的605错误可由以下条件引发:
-
- 操作系统过早地通知SQL Server已完成某个I/O操作;尽管不存在实际的数据损坏,但显示错误消息。
- 运行带有优化器提示NOLOCK的查询,或将事务隔离级别设置为READ UNCOMMITTED。当使用NOLOCK或READ UNCOMMITTED的查询尝试读取被其他用户移走或更改的数据时,将发生605错误。若要验证是否为暂时性的605错误,可以稍后重新运行该查询。
通常,如果在数据访问期间发生该错误,但后续的DBCCCHECKDB操作在没有出错的情况下完成,则605错误可能是暂时的。
由于605这个错误意味着一些页面分配出了问题,所以也是一个非常严重的数据库损坏。一般用DBCC CHECKDB也很难修复。
其他
在SQL Server内部,除了文件页面分配和每个页面内部格式以外,还有一些其他的约束规则。下面是一些常见的错误例子。
PFS页面头有损坏:
Msg 8946, Level 16, State 12, Line 1Table error: Allocation page(1:13280496) has
invalid PFS_PAGEpage header values.Type is 0. Check type, alloc unit ID and page
ID on the page.
系统表上的聚集索引页面上有损坏:
Server: Msg 8966, Level 16, State 1, Line 1 Could not read andlatch page (1:18645)
with latch type SH. sysindexes failed.
Msg 7985, Level 16, State 2, Server SUNART, Line 1System tablepre-checks: Object
ID 4. Could not read and latch page (1:51) withlatch type SH. Checkstatement
terminated due to unrepairable error.
某个字段的值不符合字段数据类型定义:
Msg 2570, Level 16, State 3, Line 1Page (1:152), slot 0 in objectID 2073058421,
index ID 0, partition ID 72057594038321152, alloc unit ID72057594042318848 (type
"In-row data"). Column "c1" value is out ofrange for data type "datetime". Update
column to a legal value.
元数据有损坏:
Msg 3854, Level 16, State 1, Line 2Attribute (referenced_major_id=2089058478)of
row (class=0,object_id=2105058535,column_id=0,referenced_major_id=2089058478,referenced_
minor_id=0)in sys.sql_dependencies has amatching row (object_id=2089058478)in
sys.objects (type=SN) that is invalid.
遇到这些错误,管理员需要用DBCCCHECKDB命令来检查和修复。有些错误是可以不丢数据就能修复的,有些是要丢数据才能修复物理层面错误的,有些是即使丢数据也没办法修复的。现在就来介绍CHECKDB的使用技巧。
SQL Server 损坏修复 之二 DBCC CHECKDB
DBCC CHECKDB指令可以完成两项任务:(1)检查数据库里有没有损坏发生。(2)尽力修复数据库损坏,使数据能够重新被正常访问。所以哪怕是一个正常运行的数据库,也建议定期运行这句指令,以确保没有损坏发生。对于已经发生访问错误的数据库,应该在第一时间运行这句指令,了解损坏的范围和程度。
那么DBCCCHECKDB究竟做了哪些检查呢?
在做些什么
DBCC CHECKDB通过依次执行下列操作检查指定数据库中所有对象的逻辑和物理完整性:
- 检查一些关键的系统表。
- 对数据库运行DBCC CHECKALLOC。
- 对数据库中的每个表和视图运行DBCC CHECKTABLE。
- 对数据库运行DBCC CHECKCATALOG。
- 验证数据库中每个索引视图的内容。
- 验证数据库中的Service Broker数据。
这意味着运行了DBCC CHECKDB,就不必再单独运行DBCC CHECKALLOC、DBCCCHECKTABLE或DBCC CHECKCATALOG命令。也意味着单独运行DBCCCHECKALLOC、DBCC CHECKTABLE和DBCCCHECKCATALOG命令,虽然不能完全完成DBCC CHECKDB的所有功能,但是至少完成了大部分功能。
检查一些关键系统表
在检查数据库之前,SQLServer需要去了解这个数据库里到底存放了什么样的数据,也就是所谓数据库的“元数据”(Metadata)。没有这些信息,SQL Server无法知道自己将要去访问什么样的表格,也没办法知道自己应该怎么解释将要读到的记录。
关键系统表有:
sysallocunits。
syshobts。
syshobtcolumnes。
sysrowsets。
sysrowsetcolumns。
对于普通用户访问,这些表都是不可见的。只有在DAC模式下的连接,才能看到它们。它们的结构对普通用户也是透明的。
这里的每一张系统表都有一个聚集索引。SQLServer会像做CHECKTABLE一样,对这些系统表做一遍检查,确保这些表格里的每一页面及页面里的每一条数据都可以正确地读出来。
如果中间发现问题,例如发现某个页面不能被正常访问,SQLServer就会报错。例如:
Server: Msg 8966, Level 16, State 1, Line 1 Could not read and latch page (1:33245) with latch type SH.Sysobjects failed.
对于小的数据库,这些存放元数据库的系统表不会占用太多的页面。如果发生硬件问题,损坏的几率比较小。但是对一些有成千上万对象的数据库,这些原数据系统表本身就可能使用了很多页面,发生损坏的几率也随之增大。由于这些系统表是正确读取一切数据的根本,所以如果任意一张系统表上发生了损坏,DBCCCHECKDB都会直接失败,数据库也无法做任何修复。此时恢复数据库的唯一方法,只有恢复数据库的备份。
对数据库运行DBCC CHECKALLOC
DBCC CHECKALLOC将检查数据库中所有页的分配。它还可验证各种内部结构,这些结构可用于跟踪这些页,以及它们之间的关系。
DBCC CHECKALLOC将返回以下结果集(值可能有所不同,我们以AdventureWorks数据库为范例)。
DBCC results for 'AdventureWorks'. *************************************************************** Table sys.sysrowsetcolumns Object ID 4. Index ID 1, partition ID 262144, alloc unit ID 262144 (type In-rowdata). FirstIAM (1:139). Root (1:66). Dpages 0. Index ID 1, partition ID 262144, alloc unit ID 262144 (type In-rowdata). 13 pages used in 1 dedicated extents. Total number of extents is 1. *************************************************************** '...' *************************************************************** Table Production.TransactionHistoryArchive Object ID 158623608. Index ID 1, partition ID 72057594047037440, alloc unit ID72057594053459968 (type In-row data). FirstIAM (1:804). Root (1:6568). Dpages0. Index ID 1, partition ID 72057594047037440, alloc unit ID72057594053459968 (type In-row data). 622 pages used in 79 dedicated extents. Index ID 2, partition ID 72057594052804608, alloc unit ID72057594059227136 (type In-row data). FirstIAM (1:2897). Root (1:12608). Dpages122. Index ID 2, partition ID 72057594052804608, alloc unit ID72057594059227136 (type In-row data). 124 pages used in 17 dedicated extents. Index ID 3, partition ID 72057594052870144, alloc unit ID72057594059292672 (type In-row data). FirstIAM (1:2899). Root (1:12832). Dpages166. Index ID 3, partition ID 72057594052870144, alloc unit ID72057594059292672 (type In-row data). 168 pages used in 22 dedicated extents. Total number of extents is 118. |
--对于每一个表格对象,检查它每个索引上的每个分区的数据分配情况
'...' Processed 317 entries insystem catalog for database ID 5. File 1. The number ofextents = 2898, used pages = 22552, and reserved pages = 23179. File 1 (number of mixed extents =104, mixed pages = 827). Object ID 4, index ID 1, partition ID262144, alloc unit ID 262144 (type In-row data), data extents 1, pages 13,mixed extent pages 9. '...' |
--对于每个数据文件,检查上面的数据页面分配情况
The total number of extents= 2898, used pages = 22552, and reserved pages = 23179 in this database. (number of mixed extents = 104, mixedpages = 827) in this database. CHECKALLOC found 0allocation errors and 0 consistency errors in database 'AdventureWorks'. DBCC execution completed.If DBCC printed error messages, contact your system administrator. |
-- 对于整个数据库的总结
对数据库中的每个表和视图运行DBCC CHECKTABLE
对于指定的表,DBCCCHECKTABLE将检查以下内容:
- 是否已正确链接索引、行内、LOB及行溢出数据页。
- 索引是否按照正确的顺序排列。
- 各指针是否一致。
- 每页上的数据是否合理(包括计算列)。
- 页面偏移量是否合理。
- 基表的每一行是否在每个非聚集索引中具有匹配的行,以及非聚集索引的每一行是否在基表中具有匹配的行。
- 已分区表或索引的每一行是否都位于正确的分区中。
DBCC CHECKTABLE返回以下正确结果集。
DBCC results for 'HumanResources.Employee'. There are 288 rows in 13 pages for object 'Employee'. DBCC execution completed. If DBCC printed error messages, contactyour system administrator.
对数据库运行DBCC CHECKCATALOG
检查指定数据库系统表里记录的元数据逻辑一致性。可能的错误例如:
Attribute (object_id=130099504)of row (object_id=130099504,column_id=1) in sys.columns does not have a matching row (object_id=130099504) in sys.objects. --在sys.columns视图里,有一个列不属于sys.objects里的任何一个表格或视图 Attribute (referenced_object_id=130099504,key_index_id=2) of row (object_id=1447012236)in sys.foreign_keysdoes not have a matching row (object_id=130099504,index_id=2) in sys.indexes. --在外键视图sys.foreign_keys里的一个外键在sys.indexes里找不到对应的索引 Attribute (parent_object_id=130099504) of row (object_id=2061966422) in sys.objects does not have a matchingrow (object_id=130099504) insys.objects. --某个对象的父对象不存在。(例如有一个主键对象存在,但是主键所依附的表格不存在了。)
这些错误在正常的数据库里不应该出现,除非用户自己去直接修改了系统表里的数据。
验证数据库中每个索引视图的内容
SQL Server支持在某些视图上建立索引,以提高视图的性能。在视图中一些经过计算才能得到的字段值,SQLServer会将它们存储在索引页面里。下次可以直接使用,而不需要再做计算。
DBCC CHECKDB会将视图再计算一遍,验证索引页面里存储的字段值都是正确的,从而保证索引视图的数据可靠性。但是如果视图查询的源数据量非常大,CHECKDB会使用tempdb里的空间协助计算。这个过程会比较消耗资源和时间。如果想要跳过这一步,可以使用PHYSICAL_ONLY这个参数。
由于索引页里的数据都是根据源数据计算出来的,如果CHECKDB发现有任何问题,修复起来还是比较容易的。只要把现有的有错误的数据删除,新计算一遍重新存储起来就可以。所以索引视图上的问题一般不会导致数据丢失。
对于XML字段上建立的索引,SQL Server在这一步也会做类似的验证。
验证数据库中的Service Broker数据
如果数据库使用了ServiceBroker的功能,SQL Server还会调用ServiceBroker的组件,检查相关的系统对象(Service Broker的queue、pipeline等)是否正常。这部分和传统的表格索引等没直接的关系。
最后,DBCCCHECKDB会打印出一句总结的话:
CHECKDB found 0allocation errors and 0 consistency errors in database 'AdventureWorks'.
如果您的数据库没有说“0allocation errors”和“0 consistency errors”,而是有若干个错误,那就意味着数据库有损坏了,赶紧修吧。
提供的修复方法
默认DBCCCHECKDB只会验证数据库是否完好,不会主动去做数据库修复动作。要尝试修复数据库,需要将数据库设成单用户模式,才能使用以下三个修复选项之一。
· REPAIR_ALLOW_DATA_LOSS
尝试修复报告的所有错误。这些修复可能会导致一些数据丢失。
· REPAIR_FAST
保留该语法只是为了向后兼容。未执行任何修复操作,请不要使用。
· REPAIR_REBUILD
执行次要、快速修复(例如,修复非聚集索引中的额外键)及耗时修复(例如,重新生成索引)。执行这些修复时不会有丢失数据的危险。
如果已通过使用ALTERDATABASE语句将数据库设置为紧急模式,那么假如指定了REPAIR_ALLOW_DATA_LOSS选项,则DBCC CHECKDB可以对数据库执行某些特殊修复,恢复的数据库在物理层面的一致性,使其能够重新访问。但是这种修复是以丢失数据为前提的。所以应当是最后手段,并只有在无法从备份还原数据库时才采用。
将数据库设成紧急模式并以REPAIR_ALLOW_DATA_LOSS子句运行DBCC CHECKDB时,将执行以下操作
- 将由于I/O或校验错误而被标记为不可访问的页重新标记为可访问,就如同这些错误没有出现过一样。这样用户将能够访问这些页面,虽然页面里的内容肯定有问题。
- 将尝试使用常规的基于日志的恢复技术恢复数据库。
- 如果由于事务日志损坏而导致数据库恢复失败,则将重建事务日志。但是重建事务日志可能会导致数据库里的事务不一致。
如果DBCCCHECKDB命令成功,则说明数据库在物理结构上是正确的,并且数据库状态将设置为ONLINE,用户可以正常访问。但是,数据库可能包含一种或多种事务不一致的情况。修复操作也不会考虑表本身或表之间可能存在的任何约束。如果指定的表与一个或多个约束有关,建议在修复操作后运行DBCCCHECKCONSTRAINTS。如果必须使用REPAIR,则运行不带有修复选项的DBCCCHECKDB来查找要使用的修复级别。如果必须使用REPAIR_ALLOW_DATA_LOSS级别,则建议在运行带有此选项的DBCC CHECKDB之前备份数据库。因为DBCCCHECKDB造成的修改,用户可能会无法接受,而这个时候也没办法再用备份恢复的方法恢复数据库了。
对于REPAIR_ALLOW_DATA_LOSS,这里的data loss不仅仅是指一些数据记录可能被删除,还指有些错误的记录会保留在数据库里,让用户继续使用。所以其实这是一种比较危险的选择,只能在万不得已的时候选用。
如果一个数据库使用REPAIR_ALLOW_DATA_LOSS级别都不能修复,管理员又能怎么办呢?选择真的十分有限。以下是可用的几种方法:
(1)按照预先的备份恢复策略,恢复数据库备份。
这个可以说是最好的办法,能够将数据库恢复到一个一致的时间点。强烈建议管理员使用这个选择。
(2)如果损坏发生在某些用户对象上(用户表、视图、存储过程等),可以把它们DROP掉试试。
(3)将数据库设成紧急只读模式,用“SELECT ... INTO”或其他方式,将数据导入到一个新建的空数据库里。
设置紧急模式的指令是:
ALTER DATABASE <DB_Name>SET EMERGENCY
这种方法能够从数据库中将所有能读出来的数据都读出来,挽救尽可能多的数据。但是损坏严重程度不一样,丢的数据多少也不一样。这样救回来的数据库各个数据表的状态将会不一致,一般在逻辑上会有很大的问题。
还是老生常谈,恢复备份永远是最好的选择。
许多用户在运行DBCCCHECKDB之前有很大的顾虑,担心CHECKDB会造成数据库阻塞,严重影响正常的应用运行。在前面的章节里也介绍了,CHECKDB的过程其实是一个比较复杂的过程,SQL Server为了验证一个数据库的正确性,要做的事情还是非常多的。运行它而造成一些性能影响,也是难免的。另一方面,许多数据库损坏是无法修复,或者虽然可以在物理上修复,但是逻辑上的错误是无法挽回的。如果等到用户访问数据的时候才发现数据库损坏,可能已经为时已晚,损失巨大了。所以对每个数据库定期做CHECKDB工作,必须是数据库管理员的日常工作之一。那么管理员怎样在这两者之间平衡,既能够有一个比较合理的周期CHECKDB,又尽量不影响数据库应用性能呢?
首先,SQL Server作为一个能够支持超大数据库的数据库管理系统,在DBCC CHECKDB上不断地改进着。在现在的应用中,数百GB以至上TB的SQL Server数据库比比皆是。不是说有用户在使用数据库,就不能运行CHECKDB的。SQL Server通过下面这些技术大大提高了CHECKDB的速度,降低了它对并发用户的影响,防止了阻塞。
内部数据库快照
在执行DBCCCHECKDB命令时,数据库引擎创建一个数据库快照,并将其置于在事务上一致的状态。然后,DBCC命令对该快照运行检查,而不是对数据库本身做检查。DBCC命令完成后,将删除该快照。这样,CHECKDB命令就不需要申请许多锁,可以良好地防止在执行时出现阻塞和并发问题。所以从阻塞的角度讲,CHECKDB完全可以在多用户的模式下正常使用,不需要等到一个所有用户都离线的时候再做。
并行检查对象
默认情况下,DBCCCHECKTABLE对对象执行多个线程并行检查,以加快检查速度。并行度由查询处理器自动确定。最大并行度的配置与配置并行查询相同。若要限制DBCC检查可使用的处理器的最大数目,请使用sp_configure改变maxdegree of parallelism选项。
并行度其实是一把双刃剑。越多的处理器在同时进行数据库检查,CHECKDB能够完成得越快。可是在这段时间里,就会有越多的系统资源被使用在CHECKDB上。其他同时在运行的连接可能就拿不到足够的资源,性能反而会受到更大的影响。所以管理员需要根据具体情况,在两者之间做一个平衡。决定是尽快完成CHECKDB更重要呢,还是要兼顾其他连接的性能。通过使用跟踪标志2528,可以禁用并行检查。
PHYSICAL_ONLY
这个选项可以以较小的开销检查数据库的物理一致性,并且能检测出会危及用户数据安全的残缺页、校验和错误及常见的硬件故障。但对于有些检查会忽略,如FILESTREAM数据。因此,针对生产系统中频繁使用的情况,建议使用PHYSICAL_ONLY选项。使用PHYSICAL_ONLY选项可以极大地缩短对大型数据库运行DBCC CHECKDB的时间。
既然有了这些技术,那么现在SQLServer运行一次CHECKDB大概要多久呢?很不幸,这真是一个非常难以回答的问题。运行的时间和下面这些因素有关系。
(1)数据库自身大小。
(2)当前系统I/O子系统的读写能力与繁忙程度。
CHECKDB需要把数据库从头到尾通读一遍。这本身就是一个巨大的I/O工作。I/O子系统能多快地完成SQL Server提出的读写请求,是影响到CHECKDB速度的最重要的因素。
(3)当前系统CPU负荷。
CHECKDB本身也是个验证工作。让计算机去验证什么事情,就意味着让CPU去做计算。所以有时候CHECKDB也会导致系统CPU负载加重。
(4)当前数据库的并发修改量。
虽然SQL Server使用快照技术大大提高了数据库运行CHECKDB时候的并发度,但是如果数据库上的修改非常频繁,快照数据库的维护本身会是一个很耗资源的任务,会反过来影响CHECKDB的速度。
(5)存放tempdb磁盘的速度。
当数据库非常大的时候,SQLServer需要很多内存来存放一些中间结果。有时候仅放在内存里是不合适也是不可能的,需要借助tempdb里的空间。那么tempdb磁盘的读写速度对CHECKDB的速度也会有显著影响。
(6)数据库里对象的类型。
不同类型的对象,需要验证其正确性所花费的代价是不一样的。比较费资源的对象有非聚集索引、计算列(computed column)、off-rowLOB values、Service Broker、XML索引、索引视图(indexed view)等。如果这些对象在一个数据库里使用得比较多,那么同样的大小,这样的数据库CHECKDB会更耗时一些。
(7)CHECKDB使用的参数。
有些参数可以让SQL Server少做一些检查。例如WITH NOINDEX可以让SQL Server不用去做费时费力的非聚集索引检查。WITH PHYSICAL_ONLY可以让SQLServer只做物理结构完整性检查等。
(8)数据库里面的错误类型和错误的数目。
如果SQL Server在CHECKDB的过程中发现了错误,它就会用更加复杂的算法来衡量错误的性质、范围和影响,以便将最准确有用的信息返回。这会使得CHECKDB的时间大大延长。
根据2012年时的经验,一个大于1 TB的数据库如果没有错误,CHECKDB在有些机器上用8小时就能够跑完。而一个有成百上千错误的数据库,哪怕只有两三百GB,也有可能一天都跑不完。这个区别是很显著的。
虽然比较难以估计做一个DBCCCHECKDB需要多长时间,但是读者可以通过下面的语句,查询做DBCC CHECKDB进度如何。有时候进度可能不太精确,但是我们至少心里有数大概什么时候能够做完。
SELECT session_id,request_id, percent_complete, estimated_completion_time ,
DATEADD(ms,estimated_completion_time,GETDATE())AS EstimatedEndTime,
start_time, status,command
FROM sys.dm_exec_requests
WHERE command like '%DBCC%'
超大数据库上的最佳实践
谈了半天,结论还是两个:对超大数据库,CHECKDB本身是一个比较昂贵的任务,可能会影响并行运行的其他连接的性能;而不运行CHECKDB又是很危险的,如果长期不运行,突然发现已经积累了很多错误,这种风险几乎无法承担。那么怎么安排好这个任务呢?
如果数据库里设计了分区表(partitionedtable)机制,做起来可能比较简单一些。对于存储历史数据的分区文件组,由于数据本身已经不会发生修改,我们可以把文件组类型设成只读模式,防止任何误修改。每个月或者每两个星期对它们运行一次DBCCCHECKFILEGROUP即可。对于当前的数据,由于随时都可能发生读写,发生损坏的几率也高得多。所以可以每个星期,甚至一个星期两次,单独做DBCCCHECKFILEGROUP。
如果数据库没有分区机制,CHECKDB就是一个宏大的工程了。几乎可以说,没有机会让管理员找到一个时间窗口能把一个完整的CHECKDB做完。这时候怎么做呢?
有一种折中的方法,是把数据库里面的所有表格按照它们占用的页面数量大致分为7组,每组的页面数目大致一样。然后按照这样的方式把CHECKDB里面的关键任务分散在每天运行。
周一到周三:
每天运行一组DBCCCHECKTABLE。
周四:
DBCC CHECKALLOC + 一组DBCCCHECKTABLE。
周五周六:
每天运行一组DBCCCHECKTABLE。
周日:
DBCC CHECKALLOC + DBCC CHECKCATALOG + 一组DBCC CHECKTABLE。
用这种方法,可以在代价比较小的前提下完成CHECKDB的大部分工作。TB级数据库的管理员可以考虑试试。
如果您有一台备用服务器,则可以考虑不在生产环境上运行DBCCCHECKDB。而是把生产环境的数据库通过备份方式,还原到备用服务器上,在备用服务器运行DBCCCHECKDB。如果备用服务器上检查结果数据是完整的,这说明在对生产环境数据库做备份的那一刻,数据是完整的。如果备用服务器上执行的结果显示数据完整性有问题,则一定要在生产环境再次运行DBCCCHECKDB,以明确问题到底出在哪个环节。是生产环境的数据已经有问题,还是还原到备用服务器时出问题。这时候检查数据完整性的优先级高于性能影响。所以在生产环境再次运行DBCCCHECKDB是必须的。使用这种方法的缺点是,需要额外一台服务器,以及比较完善的自动化执行方案。
如果数据库或数据库备份受损,在检查数据库完整性的时候,会发现各种各样的错误。在这一节里,我们针对不同的损坏部位,给出不同的应对方法。首先,我们创建一个测试数据库。
CREATE DATABASE TESTDB GO USE TESTDB GO CREATE TABLE TESTTABLE (ID int, NAME nvarchar(50) ) |
--建立两个索引,其中一个是聚集索引,另外一个是非聚集索引
CREATE CLUSTERED INDEX idx1 on TESTTABLE (ID)
CREATE INDEX idx2 on TESTTABLE(NAME)
-- 插入300行数据
DECLARE @i INT SET @i = 1 WHILE (@i <= 300) BEGIN INSERT INTO TESTTABLE VALUES(@i, CONCAT('name_', @i)) SET @i = @i + 1 END |
备份文件损坏
对前面建立的测试数据库做一个全备份,名为TESTDB.BAK。因为数据库比较小,所以备份文件也会比较小,用二进制文件编辑器如UltraEdit更改此文件一些地方的内容,以模拟该文件受损的情形,另存为TESTDB_BAD.BAK。执行如下命令,对数据库进行恢复:
RESTORE DATABASE TESTDB FROM DISK='D:\temp\TESTDB_BAD.bak'
在做恢复的时候,可能会碰到如下的错误,并且恢复过程会中断。
Processed 312 pages for database 'TESTDB', file 'TESTDB' on file 1. Processed 3 pages for database 'TESTDB', file 'TESTDB_log' on file1. Msg 3167, Level 16, State 1, Line 1 RESTORE could not start database 'TESTDB'. Msg 3013, Level 16, State 1, Line 1 RESTORE DATABASE is terminating abnormally. Msg 824, Level 24, State 2, Line 1 SQL Server detected a logical consistency-based I/O error: incorrectchecksum (expected: 0x9cb7ecfe; actual: 0x6f316d79).It occurred during a readof page (1:19) in database ID 8 at offset 0x00000000026000 in file 'C:\ProgramFiles\Microsoft SQL Server\MSSQL11.DENALI\MSSQL\DATA\TESTDB.mdf'. Additional messages in the SQL Server errorlog or system event log may provide more detail. This is a severe errorcondition that threatens database integrity and must be corrected immediately. Complete a full database consistency check (DBCC CHECKDB). This error can becaused by many factors; for more information, see SQL Server Books Online. |
这时,可以使用WITH CONTINUE_AFTER_ERROR参数尝试再次恢复:
RESTORE DATABASE TESTDB FROM DISK='D:\temp\TESTDB_BAD.bak'
WITH CONTINUE_AFTER_ERROR
加CONTINUE_AFTER_ERROR, 虽然备份文件受损,但是会尽最大可能来继续恢复,显示恢复完成。
Processed 312 pages for database 'TESTDB', file 'TESTDB' on file 1. Processed 3 pages for database 'TESTDB', file 'TESTDB_log' on file1. Restore was successful but deferred transactions remain. Thesetransactions cannot be resolved because there are data that is unavailable.Either use RESTORE to make that data available or drop the filegroups if younever need this data again. Dropping the filegroup results in a defunctfilegroup. RESTORE WITH CONTINUE_AFTER_ERROR was successful but some damage wasencountered. Inconsistencies in the database are possible. RESTORE DATABASE successfully processed 315 pages in 0.271 seconds(9.055 MB/sec). |
不幸的是,对数据库做完恢复后,该数据库有可能会马上处于Suspect(质疑)状态。我们还是无法使用该数据库。 我们把该数据库设置为紧急状态,并执行数据库修复命令:
ALTER DATABASE TESTDB SET EMERGENCY DBCC CHECKDB(TESTDB) DBCC CHECKDB(TESTDB, REPAIR_ALLOW_DATA_LOSS) use TESTDB SELECT * FROM TESTTABLE |
上述命令都无法执行成功,并且会报如下错误:
Msg 945, Level 14, State 2, Line 4
Database 'TESTDB' cannot be opened due to inaccessible files orinsufficient memory or disk space. Seethe SQL Server errorlog for details.
碰到这种情况,即使在恢复数据库的时候使用CONTINUE_AFTER_ERROR参数,但是还是无济于事。对于这个受损的备份文件,我们只能遗弃。
根据备份文件受损的地方不同,有时候还是能够使用上述方法找回一部分数据的。这很大部分依赖于在备份文件中受损的位置。所以CONTINUE_AFTER_ERROR这个参数,对于受损的数据库备份恢复,虽然是一个不错的尝试,但也并不是万能的。
日志文件损坏
下面是一个模拟日志文件损坏的测试。请运行如下的脚本操作。第一个UPDATE语句是更新ID=295的数据,并且马上做一个CHECKPOINT的动作,这会使得数据的更新,立刻写入数据文件中。第二个UPDATE语句打算要更新ID=296的数据,这两个操作是放在同一个事务内进行的。但是在第一个UPDATE语句结束后,不等WAITFOR语句结束,请马上杀掉数据库服务进程(Sqlservr.exe)。这样,在数据文件里,ID=295的记录已经被修改,ID=296的记录还没被修改。如果日志文件不损坏,下次SQLServer重新启动时,SQL会发现这个做到一半的事物,将ID=295的记录修改回滚。
BEGIN TRANSACTION UPDATE TESTTABLE SET NAME = 'xxxx' where ID = 295 CHECKPOINT WAITFOR DELAY '0:1:0' UPDATE TESTTABLE SET NAME = 'xxxx' where ID = 296 COMMIT TRANSACTION |
在这个测试里,我们编辑该数据库的日志文件,人为对数据库的日志文件造成损坏。当该数据库服务重新启动的时候,该数据库会处于RECOVERY_PENDING状态,查看数据库错误日志,有如下的错误,表明日志文件受损。
2012-04-15 22:49:57.930 spid26s Error: 824, Severity: 24, State: 2.
2012-04-15 22:49:57.930 spid26s SQL Server detected a logicalconsistency-based I/O error: incorrect checksum (expected: 0x6c1e7b9c; actual:0xd6a87d4a). It occurred during a read of page (2:0) in database ID 8 at offset0000000000000000 in file 'C:\Program Files\Microsoft SQL Server\MSSQL11
2012-04-15 22:49:57.930 spid26s Error: 5105, Severity: 16, State: 1.
2012-04-15 22:49:57.930 spid26s A file activation error occurred. Thephysical file name 'C:\Program Files\Microsoft SQLServer\MSSQL11.DENALI\MSSQL\DATA\TESTDB_log.ldf' may be incorrect. Diagnose andcorrect additional errors, and retry the operation.
2012-04-15 22:49:57.950 spid26s File activation failure. The physicalfile name "C:\Program Files\Microsoft SQLServer\MSSQL11.DENALI\MSSQL\DATA\TESTDB_log.ldf" may be incorrect.
2012-04-15 22:49:57.950 spid26s The log cannot be rebuilt because therewere open transactions/users when the database was shutdown, no checkpointoccurred to the database, or the database was read-only. This error could occurif the transaction log file was manually deleted or lost due to a
针对这种日志损坏,读者可以用下面的方法,重建数据库日志,以恢复数据库的正常访问:
ALTER DATABASE TESTDB SET EMERGENCY ALTER DATABASE TESTDB Rebuild LOG on (name=xxxx_log, filename='D:\temp\xxxx_log.LDF') ALTER DATABASE TESTDB SET MULTI_USER |
在重建数据库日志以后,一定要运行DBCC CHECKDB以确保没有一致性错误。
有一个要注意的地方,对数据库日志进行重建以后,我们会发现ID为295的NAME变成了xxxx,而ID为296的NAME依旧是name_296。如下图10-2所示。这从数据库的逻辑上来讲是没有问题的。但是从应用的角度,可能会大有问题。应用程序可能有逻辑需求,要求ID为295和ID为296的名字要么同时改变,要么同时不改变,在应用程序中,我们开启了事务以确保这一点。但是经过日志重建后,这点不能得到保证了。
因此,重建数据库的日志文件,是迫不得已的办法,会破坏数据的一致性。还是建议从好的数据库备份中恢复数据,这样能保证数据在业务逻辑上的一致。
用户数据文件损坏
让我们接着模拟数据库文件损坏的各种情形。新建测试数据库后,把TESTDB下线,编辑该数据库的数据文件,以模拟该数据文件受损的情形。然后把该数据库上线,运行DBCC CHECKDB命令,会报告发现数据库受损。根据受损的部位,有以下几个情形:
· 非聚集索引页面受损
具体的错误信息如下:
Msg 8939, Level 16, State 98, Line 1
Table error: Object ID 245575913, index ID 2, partition ID72057594039173120, alloc unit ID 72057594043564032 (type In-row data), page(1:228). Test (IS_OFF (BUF_IOERR, pBUF->bstat)) failed. Values are 133129and -4.
Msg 8928, Level 16, State 1, Line 1
Object ID 245575913, index ID 2, partition ID 72057594039173120,alloc unit ID 72057594043564032 (type In-row data): Page (1:228) could not beprocessed. See other errors for details.
Msg 8980, Level 16, State 1, Line 1
Table error: Object ID 245575913, index ID 2, partition ID72057594039173120, alloc unit ID 72057594043564032 (type In-row data). Indexnode page (1:264), slot 0 refers to child page (1:228) and previous child(0:0), but they were not encountered.
Msg 8978, Level 16, State 1, Line 1
Table error: Object ID 245575913, index ID 2, partition ID72057594039173120, alloc unit ID 72057594043564032 (type In-row data). Page(1:265) is missing a reference from previous page (1:228). Possible chainlinkage problem.
DBCC results for 'TESTTABLE'.
There are 300 rows in 2 pages for object "TESTTABLE".
CHECKDB found 0 allocation errors and 4 consistency errors in table'TESTTABLE' (object ID 245575913).
读者会发现数据库受损是发生在表格TESTTABLE,而且是索引编号为2的存储上面。索引编号大于1的都是非聚集索引。因此,读者可以重建非聚集索引来尝试对数据库进行修复。
DROP INDEX idx2 ON TESTTABLE GO CREATE INDEX idx2 ON TESTTABLE(NAME) |
再次运行DBCC CHECKDB(TESTDB)命令,检查结果显示,数据库中的数据是一致的。由于受损的地方恰好在非聚集索引页上,所以我们对数据库可以进行不丢失数据修复,如果受损在其他地方,如聚集索引页,则丢失数据难以避免。
· 聚集索引页面受损
根据第八章数据库空间管理中提到的方法,我们执行下面一系列查询,然后对聚集索引页面进行篡改:
SELECT * FROM sys.objects WHERE NAME = 'TESTTABLE'
-- 结果为245575913
SELECT * FROM sys.partitions WHERE object_id = '245575913'
-- 有两个索引,其中聚集索引所在地partition_id为72057594039107584
SELECT * FROM sys.system_internals_allocation_units WHERE
container_id = '72057594039107584'
-- 得知第一个数据页为(1, 226)
DBCC TRACEON(3604)
DBCC PAGE('TESTDB', 1, 226, 1)
DBCC TRACEOFF(3604)
-- 得知数据页的具体内容,编辑数据文件,找到相应位置,对该数据页面的内容进行篡改
得知数据页的具体内容后,把该数据库下线, 根据第一个数据页的查询得到的字符串,编辑数据文件,对该数据页内容进行篡改。然后上线该数据库,并对数据库做一致性检查,会发现如下错误:
Msg 8978, Level 16, State 1, Line 1
Table error: Object ID 245575913, index ID 1, partition ID72057594039107584, alloc unit ID 72057594043498496 (type In-row data). Page(1:231) is missing a reference from previous page (1:226). Possible chainlinkage problem.
Msg 8939, Level 16, State 98, Line 1
Table error: Object ID 245575913, index ID 1, partition ID72057594039107584, alloc unit ID 72057594043498496 (type In-row data), page(1:226). Test (IS_OFF (BUF_IOERR, pBUF->bstat)) failed. Values are 133129and -4.
Msg 8928, Level 16, State 1, Line 1
Object ID 245575913, index ID 1, partition ID 72057594039107584,alloc unit ID 72057594043498496 (type In-row data): Page (1:226) could not beprocessed. See other errors for details.
Msg 8980, Level 16, State 1, Line 1
Table error: Object ID 245575913, index ID 1, partition ID72057594039107584, alloc unit ID 72057594043498496 (type In-row data). Indexnode page (1:230), slot 0 refers to child page (1:226) and previous child(0:0), but they were not encountered.
DBCC results for 'TESTTABLE'.
There are 63 rows in 1 pages for object "TESTTABLE".
CHECKDB found 0 allocation errors and 4 consistency errors in table'TESTTABLE' (object ID 245575913).
这次,数据页受损是在聚集索引页面上(索引编号为1),也就是在数据页面上,因此我们无法通过重建索引的方法对数据进行修复。我们采用REPAIR_ALLOW_DATA_LOSS的方法,对数据库进行修复。
ALTER DATABASE TESTDB set EMGERGENCY ALTER DATABASE TESTDB set single_user withrollback immediate GO DBCC CHECKDB(TESTDB, 'REPAIR_ALLOW_DATA_LOSS') GO ALTER DATABASE TESTDB set multi_user |
检索修复后的数据库,读者会发现,原来有300条记录的,现在只剩下了63条。有237条数据在修复的过程中丢失了。这是因为修复程序发现页内数据有问题,采用比较保守的办法,是把整个页面的数据删除,以维护数据的一致性。
· 系统页面受损
数据库有一些系统页面或系统表格非常重要,如在第八章数据库空间管理提及的PFS页面,GAM页面和SGAM页面。如果这些页面受损,那么整个数据库基本上很难修复。下面是一个示例。首先查询TESTDB的GAM页面信息,得到Slot 1里面的数据0000381f。
把数据库TESTDB下线,用二进制文件编辑器打开数据文件,对字符串0000381f进行篡改,然后把该数据库上线。数据库一上线后,TESTDB马上处于置疑(Suspect)状态,如图10-5所示。
我们把该数据库设为紧急模式,然后执行DBCC CHECKDB(TESTDB)
ALTER DATABASE TESTDB SET Emergency
DBCC CHECKDB(TESTDB)
会报如下的错误信息:
DBCC results for 'TESTDB'.
Msg 8998, Level 16, State 1, Line 1
Page errors on the GAM, SGAM, or PFS pages prevent allocationintegrity checks in database ID 5 pages from (1:0) to (1:517631). See othererrors for cause.
用下面的命令尝试对数据库进行修复:
ALTER DATABASE TESTDB SET Emergency ALTER DATABASE TESTDB set single_user with rollback immediate DBCC CHECKDB(TESTDB, 'REPAIR_ALLOW_DATA_LOSS') |
由于系统页面损坏,所以即使用REPAIR_ALLOW_DATA_LOSS也无法对数据库进行修复。修复程序提示如下错误:
Msg 5028, Level 16, State 4, Line 3 The system could not activate enough of the database to rebuild thelog. DBCC results for 'TESTDB'. CHECKDB found 0 allocation errors and 0 consistency errors indatabase 'TESTDB'. Msg 7909, Level 20, State 1, Line 3 The emergency-mode repair failed.You must restore from backup. |
因此,如果系统页面或者系统表受损,则只能从备份当中恢复数据
10.3.4 系统数据库损坏
系统数据库包括master数据库,tempdb数据库,MSDB数据库和MODEL数据库。由于tempdb数据库会在数据库重新启动时,再次创建,因此对于tempdb数据库的损坏,我们可以重新启动数据库对其进行恢复。
master数据库,MSDB数据库和MODEL数据库一般情况下不会进行很多改动,所以如果碰到系统数据库损坏,从备份中恢复系统数据库是最好的办法。如果我们没有对系统数据库做备份,那么修复系统数据库的方法,则是从相同版本的数据库上,拷贝系统数据库的数据文件和日志文件,然后替换受损的系统数据库。具体方法,可参考前面章节对移动数据库的介绍。这样,损失的数据,包括master里面的数据和MSDB里面的数据。用户可以通过重建登录以及重建Job来进行恢复。
四. Database Mirroring和AlwaysOn的页面自动修复功能
在SQL Server 2008Enterprise或更高版本上有一个很诱人的新功能。如果某个配置了数据库镜像或者AlwaysOn的数据库发生数据页面访问错误(823、824和829),数据库伙伴会自动尝试解决错误。无法读取页的伙伴会从其他伙伴请求新副本。如果此请求成功,则将以新副本替换不可读的页,这通常会解决发生在镜像或AlwaysOn的主服务器上的页面损坏。
1. 数据库镜像的页面自动修复功能
例如,在镜像的主服务器上的主体数据库第100页上发生了824错误,SQL Server会到镜像伙伴服务器上的数据库里,把第100页的内容读出来。如果镜像伙伴服务器上的数据库没有损坏,这个页面读取请求就能够成功。SQLServer会自动将主服务器上损坏的页面替换成读到的正确内容,从而达到修复页面的目的。数据库镜像伙伴进行的自动页修复不同于DBCC修复。自动页修复会保留所有数据。相反,使用DBCC REPAIR_ALLOW_DATA_LOSS选项更正错误可能需要删除某些页(从而会删除数据)。
由于数据库损坏大部分和硬件有关系,而两套硬件同时损坏的概率是很小的,所以这个功能看上去的确很诱人。
当然它也有一定的局限性。数据库镜像自动页修复只尝试修复特定数据文件中的页,此数据文件是指对其执行的操作由于表10-2中列出的某一错误而失败的数据文件。
表 10- 2 Database Mirroring支持修复的错误类型
错误号
|
说明
|
导致自动页修复尝试的实例
|
823
|
仅当操作系统对数据执行循环冗余检查(CRC)失败时才执行此操作 | ERROR_CRC。此错误的操作系统值为23 |
824
|
逻辑错误 | 逻辑数据错误,例如残缺写或错误的页校验和 |
829
|
页已标记为还原已挂起 | 全部 |
也不是所有的页面都能被自动修复的。以下控制页类型无法通过数据库镜像修复:
· 文件头页(页ID 0)。
· 页9(数据库启动页)。
· 分配页:全局分配映射(GAM)页、共享全局分配映射(SGAM)页和页可用空间(PFS)页。
处理主体数据库中的I/O错误
在主体数据库中,仅当节点处于SYNCHRONIZED状态且主体服务器仍向镜像服务器发送日志记录时,才会尝试自动页修复。自动页修复尝试中操作的基本顺序如下:
(1)当主体数据库中的数据页上发生读取错误时,主体服务器将使用相应的错误状态在suspect_pages表中插入一行。然后,主体服务器从镜像服务器请求此页的副本。此请求指定当前在刷新日志末尾的页ID和LSN。此页将标记为“还原已挂起”。这会使它在自动页修复尝试期间不可访问。修复尝试期间对此页的访问尝试将失败,并显示错误829(还原已挂起)。
(2)收到页请求后,镜像服务器将等待,直到将日志重做到请求中指定的LSN处。然后,镜像服务器会尝试访问镜像数据库中的此页。如果可以访问此页,则镜像服务器将此页的副本发送到主体服务器。否则,镜像服务器将向主体服务器返回错误,并且自动页修复尝试失败。
(3)主体服务器处理包含此页新副本的响应。
(4)自动页修复尝试修复可疑页后,此页将在suspect_pages表中标记为已还原(event_type = 4)。
(5)如果此页I/O错误导致出现任何延迟的事务,则修复此页后,主体服务器将尝试解决这些事务。
处理镜像数据库中的I/O错误
SQL Server会用以下方式处理在镜像数据库中发生的数据页I/O错误。
(1)如果镜像服务器在其重做日志记录时遇到一个或多个页I/O错误,则镜像会话将进入SUSPENDED状态。此时,镜像服务器使用相应的错误状态在suspect_pages表中插入一行。然后,镜像服务器从主体服务器请求此页的副本。
(2)主体服务器尝试访问主体数据库中的此页。如果可以访问此页,则主体服务器将此页的副本发送到镜像服务器。
(3)如果镜像服务器收到它请求的每一页的副本,则镜像服务器将尝试恢复镜像会话。如果自动页修复尝试修复了可疑页,此页将在suspect_pages表中标记为已还原(event_type = 4)。
2. AlwaysOn的页面自动修复功能
AlwaysOn的页面自动修复功能和镜像非常相似。下面是它具体的工作过程:
主数据库上的页面损坏
1. 在主副本上访问页面,得到了823,824或829错误。访问操作被中止。Suspect_pages表中会插入一条记录来标示该质疑页面。该页面会被标记为“还原/挂起”状态,在自动修复操作完成之前(无论是修复成功还是失败),任何访问这个页面的操作都会得到829错误。
2. 主副本发送广播请求到所有辅助副本上,请求该页面的副本来修复受损页。广播请求中会包含所需页面的ID,以及主副本的当前LSN(被写入到LDF文件的最后一个日志)。
3. 当辅助数据库将日志重做到请求中所指定的LSN后,它就会尝试去访问请求中所包含的页ID。如果成功获得这个页面,就把这个页面发送给主副本。如果失败,那么整个自动修复就失败,辅助副本会返回一个错误给主副本。因此,修复页面所需要的时间主要取决于辅助数据需要多长时间才能追上主数据的LSN。
4. 主副本可能从多个辅助副本上都收到页面。第一个收到的页面会用来替代主数据库上损坏的页面。后续那些来自其他辅助副本的页面就被丢弃了。
5. 当自动修复完成工作后,suspect_pages 该页面所对应记录的event_type 列会被设置成5。
辅助数据库上的页面损坏
1. 如果辅助副本在其重做日志记录时遇到823,824或829错误,那么辅助数据库将进入挂起状态。 同时在辅助副本的suspect_pages 表中插入一行记录来表示这个损坏页面。
2. 辅助数据库向主数据库请求该页的当前副本。
3. 主数据库响应该请求将页面副本和当前的LSN发送给辅助副本,并且只允许辅助副本上的重做线程来访问这个页面。
4. 辅助副本上的重做线程继续运行。直到辅助数据库上重做的LSN达到并超过了主数据发送来的页面副本的LSN时,该页面才能被辅助副本上所有的工作线程所访问。从此,该页面就在内存中替换了辅助数据库中的损坏页面。等到下次checkpoint的时候,该页面就会被写回到磁盘上。
每当可用性组使用自动页面修复功能修复了一个受损页面后,在动态管理视图sys.dm_hadr_auto_page_repair中就会多出一条记录,代表了该自动修复操作。你可以通过监视该动态管理视图来了解系统中发生的自动修复情况。
所以,如果镜像或者AlwaysOn能够正常工作,数据库的安全性就能够得到很大的提升。虽然配置镜像或者AlwaysOn会增加维护成本,但是还是建议有条件的系统试一试。
来源:http://www.uml.org.cn/sjjm/201212041.asp
参考文档