在經(jīng)歷了上面的文件級(jí)別錯(cuò)誤后,在數(shù)據(jù)庫(kù)啟動(dòng)的過程,還經(jīng)常出現(xiàn)的是數(shù)據(jù)頁(yè)級(jí)別的錯(cuò)誤,相對(duì)于上面的文件錯(cuò)誤級(jí)別,在數(shù)據(jù)頁(yè)中造成的錯(cuò)誤粒度更小,并且基本不會(huì)反映到數(shù)據(jù)庫(kù)級(jí)別,也就是說在出現(xiàn)數(shù)據(jù)頁(yè)級(jí)別的錯(cuò)誤時(shí)候,該數(shù)據(jù)時(shí)可以正常訪問的,只是在訪問有錯(cuò)誤的數(shù)據(jù)頁(yè)的時(shí)候才會(huì)報(bào)錯(cuò),在我們遇到這種錯(cuò)誤的時(shí)候該如何解決呢?
下面我們依次來(lái)分析,首先我們來(lái)制作一個(gè)經(jīng)典的824錯(cuò)誤,以下部分內(nèi)容牽扯到數(shù)據(jù)庫(kù)部分基礎(chǔ),限于篇幅,我們不做詳細(xì)介紹:
<1>首先我們?cè)谖覀兊臏y(cè)試庫(kù)中新建一個(gè)表,我們將該表新建成一行為一個(gè)數(shù)據(jù)頁(yè)的方式,也就是說一行數(shù)據(jù)庫(kù)在數(shù)據(jù)庫(kù)中就能承載一個(gè)數(shù)據(jù)頁(yè)

USE CnblogsTestDB
GO
CREATE TABLE [dbo].[TestPage]
(
[a] [int] NULL,
[b] [nvarchar](3900) NULL
) ON [PRIMARY]

腳本很簡(jiǎn)單,一張表,兩列,一列int類型,一列nvarchar(3900),一行數(shù)據(jù)的存儲(chǔ)空間為:3900*2(nvarchar(3900))字節(jié)+4(int)+96字節(jié)(頁(yè)頭)+36字節(jié)(行偏移)=7932字節(jié),我們知道一個(gè)數(shù)據(jù)頁(yè)存儲(chǔ)的信息為8K=8192字節(jié),包括其它消耗所以該表一行數(shù)據(jù)如果填充完,一行數(shù)據(jù)將近乎占據(jù)一個(gè)數(shù)據(jù)頁(yè)。
我們來(lái)添加三行數(shù)據(jù),然后查看頁(yè)信息:

--插入三條數(shù)據(jù)
insert [TestPage]
values(1,REPLICATE('A',3900))
insert [TestPage]
values(2,REPLICATE('B',3900))
insert [TestPage]
values(3,REPLICATE('C',3900))
go--查看頁(yè)信息
dbcc traceon(3604)--查看庫(kù)中頁(yè)集合
dbcc extentinfo(CnblogsTestDB,[TestPage])


可以看到,該表中現(xiàn)在有三個(gè)數(shù)據(jù)頁(yè),我們來(lái)看看數(shù)據(jù)頁(yè)應(yīng)該也是近乎沾滿的
上圖顯示了,通過掃描表信息,共含有3個(gè)數(shù)據(jù)頁(yè),每個(gè)數(shù)據(jù)頁(yè)中的數(shù)據(jù)量存儲(chǔ)占比到了96.55%,也就是說基本上是填充滿了。
當(dāng)然,我們還可以通過DBCC PAGE命令,來(lái)查看每個(gè)頁(yè)中的具體內(nèi)容,我們簡(jiǎn)單的看一個(gè)頁(yè)面編號(hào)為90的數(shù)據(jù)頁(yè):

通過上面的命令可以看到,該數(shù)據(jù)頁(yè)中存儲(chǔ)的為表中的第一行的數(shù)據(jù),并且在數(shù)據(jù)庫(kù)存儲(chǔ)文件中是以十六進(jìn)制方式編碼存儲(chǔ)。
當(dāng)然,如果感覺此方式不直觀,可以利用一個(gè)小工具進(jìn)行數(shù)據(jù)頁(yè)的查看,這里我推薦使?Internal Views(此工具在樺仔的博文中有詳細(xì)介紹),可更直觀的展示數(shù)據(jù)存儲(chǔ)頁(yè)信息:

這里我們可以點(diǎn)擊我上面上面查看的第一行的數(shù)據(jù)內(nèi)容頁(yè)進(jìn)行查看

經(jīng)過上面的分析步驟,其實(shí)我的目的是想重現(xiàn)在SQL Server啟動(dòng)過程中,或者在線上的數(shù)據(jù)庫(kù)經(jīng)常遇到的經(jīng)典錯(cuò)誤824錯(cuò)誤
上述過程是原理篇,因?yàn)槲覀儽仨氈罃?shù)據(jù)存儲(chǔ)的底層原理,才能理解好這個(gè)錯(cuò)誤的原因,以及找到正確的處理方法。
下一步,我們來(lái)重現(xiàn)這個(gè)錯(cuò)誤的原因,我們知道在我新建的測(cè)試表中含有兩個(gè)字段:a和b,并且a為int類型、b為nvarchar類型
然后我們介紹了底層的存儲(chǔ)機(jī)制,我現(xiàn)在將第一列a字段的整形數(shù)據(jù)內(nèi)容存儲(chǔ)改成字符串類型,依次來(lái)?yè)p壞掉該數(shù)據(jù)頁(yè)內(nèi)容
我先將服務(wù)停掉,然后用文件編輯工具,修改此數(shù)據(jù)頁(yè)內(nèi)容,該數(shù)據(jù)頁(yè)內(nèi)容為十六進(jìn)制內(nèi)容,當(dāng)然在我搞壞這部分?jǐn)?shù)據(jù)頁(yè)之前我先做一個(gè)完整備份

然后修改該數(shù)據(jù)頁(yè)信息,這里我使用UltraEdit文本編輯工具,打開文件,找到該數(shù)據(jù)頁(yè)內(nèi)容

我們將上面的源數(shù)據(jù)更該一下,來(lái)把這個(gè)數(shù)據(jù)頁(yè)損壞掉

我們保存,然后重新啟動(dòng)該數(shù)據(jù)庫(kù)看看

這就是我們平常比較常見的824錯(cuò)誤的過程,而此過程有可能是磁盤壞道造成,或者誤修改文件等諸多原因,但是此問題還是比較常見的
當(dāng)然,這種數(shù)據(jù)頁(yè)面的損壞可能造成的影響不是庫(kù)級(jí)別的,也就說不會(huì)造成數(shù)據(jù)庫(kù)不能訪問,其它表是能正常訪問的,但是只是在操作此損壞的數(shù)據(jù)頁(yè)的時(shí)候才會(huì)報(bào)錯(cuò),但有時(shí)候這幾個(gè)數(shù)據(jù)頁(yè)的損壞對(duì)業(yè)務(wù)產(chǎn)生的影響有可能就是致命的,所以我們要解決掉。
鄭重提示:上面過程也可以正確的更改數(shù)據(jù)頁(yè)中的數(shù)據(jù),但是如果沒有確切的把握,基本上能把數(shù)據(jù)庫(kù)搞癱瘓掉,我是為了重現(xiàn)問題才修改底層元數(shù)據(jù),所以在自己的生產(chǎn)庫(kù)中千萬(wàn)不要亂搞!
在數(shù)據(jù)庫(kù)啟動(dòng)的過程中,會(huì)發(fā)生一致性校驗(yàn),所以該錯(cuò)誤應(yīng)該會(huì)記錄到Error的錯(cuò)誤日志文件中,我們來(lái)看:

windows平臺(tái)下的錯(cuò)誤日志:

當(dāng)然,在啟動(dòng)的過程中該問題有可能發(fā)生很多,比如磁盤壞道等原因,一系列的數(shù)據(jù)頁(yè)可能就沒法訪問了。所以SQL Server會(huì)將這些損壞的頁(yè)面記錄到msdb系統(tǒng)庫(kù)中,這我們?cè)谶@個(gè)庫(kù)中查找到損壞的頁(yè)面集合:

至此,我們已經(jīng)重現(xiàn)了經(jīng)典的824錯(cuò)誤,那我們?cè)撊绾谓鉀Q此問題呢?
解決方法:
a、如果此問題出現(xiàn)的頁(yè)面為數(shù)據(jù)承載頁(yè),也就說該頁(yè)存儲(chǔ)的為內(nèi)容數(shù)據(jù)或者為聚集索引的葉子節(jié)點(diǎn)數(shù)據(jù),并且存在鏡像,版本在SQL Server2005以上,那么這個(gè)錯(cuò)誤基本可以忽略,SQL Server能夠自動(dòng)幫你修復(fù)此錯(cuò)誤。
b、如果此問題出現(xiàn)在沒有鏡像的環(huán)境中,那就要區(qū)分是損壞頁(yè)面是否為聚集索引葉子節(jié)點(diǎn)數(shù)據(jù),如果是,那就簡(jiǎn)單了,直接重建索引就好了,如果不是,那此種方案還是不能解?,判斷方法如下:
利用DBCC PAGE命令查看當(dāng)前數(shù)據(jù)頁(yè)內(nèi)容,根據(jù)ObjectId跟蹤該頁(yè)位于哪個(gè)對(duì)象上,Metdata:IndexID的值判斷是否為索引樹中的節(jié)點(diǎn)值,如果大于0則表示為索引值,此時(shí),重建該索引既可以。比如:

我們根據(jù)該頁(yè)的ObjectID,從數(shù)據(jù)庫(kù)中查找該頁(yè)所屬對(duì)象。
c、如果上述方案都不能滿足,那只有采取此種方案,我們可以利用數(shù)據(jù)庫(kù)備份進(jìn)行還原,當(dāng)然為了最大限度的避免數(shù)據(jù)庫(kù)離線,我們最好采取數(shù)據(jù)頁(yè)還原的方式,此種方式最為簡(jiǎn)單,還原速度也最快,能夠最大限度的縮短數(shù)據(jù)庫(kù)離線時(shí)間,并且保證數(shù)據(jù)完整性。
這里提示下:在SQL Server2012版本一下,SSMS不提供圖像化數(shù)據(jù)頁(yè)還原方式,在SQL Sever以后的版本中,有圖像化界面操作。
所以,我們只能通過如下腳本進(jìn)行還原:
RESTORE DATABASE CnblogsTestDB
PAGE='1:90'FROM DISK = N'F:SQLTestCnlogsTestDB.bak'WITH NORECOVERY
當(dāng)然有事務(wù)日志、更新備份的,需要依次恢復(fù)這過程的所有的備份,不要忘記備份尾部日志。
但是此方法也有局限性:
如果損壞的數(shù)據(jù)頁(yè)為
1、分配頁(yè):GAM、SGAM和PFS頁(yè)
2、所有數(shù)據(jù)文件的啟動(dòng)頁(yè)
如果發(fā)生損壞的是以上兩種,則無(wú)法通過該備份恢復(fù)頁(yè)方式進(jìn)行恢復(fù)。如果這種情況下,建議考慮找合適的時(shí)間段進(jìn)行全庫(kù)的恢復(fù)操作。(推薦)
d、上述情況是在存在有備份的情況下,如果沒有數(shù)據(jù)庫(kù)備份,那我們只能選擇最后的一招了,那就是DBCC CHECKDB命令,同樣和上面一樣,此種方式可能會(huì)造成數(shù)據(jù)丟失,所以不建議采用,如果能容忍數(shù)據(jù)丟失,采用的過程參照文中的上半部分。(不推薦)
至此,我們已經(jīng)完成了一個(gè)SQL Server啟動(dòng)過程或者平常最經(jīng)常遇到的一個(gè)經(jīng)典錯(cuò)誤824錯(cuò)誤,我們來(lái)總結(jié)下:
824錯(cuò)誤原因:大部分是由于磁盤存儲(chǔ)導(dǎo)致的數(shù)據(jù)頁(yè)損壞,導(dǎo)致的SQL Server在讀取的時(shí)候發(fā)生了錯(cuò)誤。
導(dǎo)致錯(cuò)誤場(chǎng)景:磁盤壞道、突然斷電等情況下經(jīng)常會(huì)出現(xiàn)此錯(cuò)誤。
----------------------------------------------------------霸氣的分割線-----------------------------------------------------------------------
和824錯(cuò)誤相關(guān)的還有一種是823錯(cuò)誤,我們來(lái)介紹下該錯(cuò)誤信息
由于場(chǎng)景所限,我就不重現(xiàn)該錯(cuò)誤了,在這里我詳細(xì)的介紹下這兩種錯(cuò)誤的原因和原理,就可以了,如果遇到了,解決的方式基本都是一致的,可參照上面的824錯(cuò)誤解決方法。
SQL Server在每次寫入頁(yè)面的時(shí)候,會(huì)根據(jù)頁(yè)面里的數(shù)據(jù)算出一個(gè)校驗(yàn)值?一同存儲(chǔ)到頁(yè)面中去。當(dāng)下次讀取頁(yè)面的時(shí)候,再根據(jù)這次讀到的頁(yè)面數(shù)據(jù),算出一個(gè)新的校驗(yàn)值。如果寫入和讀出的數(shù)據(jù)一模一樣,那么兩個(gè)校驗(yàn)值就是相等的。如果兩個(gè)校驗(yàn)值不相等,就意味著上次SQL Server寫入的數(shù)據(jù)和這次讀取出來(lái)的一定不同,現(xiàn)在讀取出來(lái)的數(shù)據(jù)就有問題了。
823錯(cuò)誤就代表著SQL Server在向操作系統(tǒng)申請(qǐng)某個(gè)頁(yè)面讀寫的時(shí)候遇到了Windows?取或?qū)懭胝?qǐng)求失敗。所以該問題的原因大部分是源自于操作系統(tǒng)層面,更確切的說是物理文件損壞而導(dǎo)致此錯(cuò)誤,比如設(shè)備驅(qū)動(dòng)程序?qū)е碌取?/p>
824錯(cuò)誤則是在讀取數(shù)據(jù)頁(yè)面時(shí)候,發(fā)現(xiàn)數(shù)據(jù)頁(yè)面有問題,比如讀取出來(lái)的校驗(yàn)值不對(duì)等。
當(dāng)上面描述的823和824錯(cuò)誤出現(xiàn)大面積的時(shí)候,或者直接部分?jǐn)?shù)據(jù)文件完全壞掉的情況下,在SQL Server啟動(dòng)過程中就會(huì)出現(xiàn)數(shù)據(jù)庫(kù)SUSPECT“質(zhì)疑”狀態(tài)。
經(jīng)過我的多次數(shù)據(jù)頁(yè)的破壞和摧殘,我已經(jīng)順利的將我們的這個(gè)測(cè)試庫(kù)給搞成?質(zhì)疑狀態(tài),我們來(lái)看SUSPECT(質(zhì)疑)的狀態(tài)庫(kù):

這里我直接DBCC CHECKDB命令嘗試著恢復(fù)下看看

所以到此,我們要做的就是避免上述錯(cuò)誤的發(fā)生。如果在生產(chǎn)庫(kù)中?生了我上面的情況,然后沒有數(shù)據(jù)庫(kù)備份,那么剩下來(lái)你要做的事情:我估計(jì)就是準(zhǔn)備簡(jiǎn)歷了.....
結(jié)語(yǔ)
文章主要還是分析SQL Server啟動(dòng)過程中,加載用戶數(shù)據(jù)庫(kù)的時(shí)候,所遇到的一系列問題,文中部分內(nèi)容需要有一定數(shù)據(jù)庫(kù)基礎(chǔ)知識(shí)才能讀懂,篇幅有限,我們沒有做深入的講解分析,比如上面的幾個(gè)重要的命令DBCC PAGE....DBCC CHECKDB..等等,隨便一個(gè)都能寫出一系列的內(nèi)容,我們側(cè)重的還是問題的解決,和問題原因分析,后續(xù)文章中會(huì)介紹這一系列的命令作用,以及正確的使用技巧。
文中部分?jǐn)?shù)據(jù)庫(kù)錯(cuò)誤都是我耗費(fèi)精力一步一步調(diào)整出來(lái),目的是真實(shí)的展現(xiàn)錯(cuò)誤明細(xì),其實(shí)問題解決容易,問題重現(xiàn)的過程復(fù)雜。
如果經(jīng)常使用SQL Server,其實(shí)這些問題都是我們會(huì)經(jīng)常遇到的,所以我們要記住相應(yīng)的解決方案,做的有備無(wú)患!