前言
本篇主要是上一篇文章的補充篇,上一篇我們介紹了SQL Server服務啟動過程所遇到的一些問題和解決方法,可點擊查看,我們此篇主要介紹的是SQL Server啟動過程中關於用戶數據庫加載的流程,並且根據加載過程中所遇到的一系列問題提供解決方案。
其實SQL Server作為微軟的一款優秀RDBMS,它啟動的過程中,本身所帶的那些系統庫發生問題的情況相對還是很少的,我們在平常使用中,出問題的大部分集中於我們自己建立的用戶數據庫。
而且,相對於側重面而言,其實我們更關注的是我們自己建立的用戶數據庫,假如系統數據庫出現問題,甚至實例出現問題,最壞的情況我們重搭環境,但是如果我們應用的用戶數據庫壞掉了,那可不是重搭環境就能解決的。這牽扯到公司利益問題,問題嚴重性不言而喻!
閑言少敘,我們速度進入本篇的正題。
上一篇我們介紹了SQL Server實例啟動的過程,並且分析了其詳細的過程,而在這一流程中,有一個步驟非常關鍵,就是加載恢復用戶數據庫的過程,我們來截取這段日志信息:
上面是一個正常啟動各個用戶庫的流程,SQL Server會采用多線程的進行數據庫啟動,並且在這個過程中進行一致性校驗,確保啟動的數據庫能夠正常使用。
而這過程中會發生很多問題,在分析問題之前,我先要介紹SQL Server數據庫的幾個常見狀態:
RECOVERING(恢復中):
這個狀態表示數據在啟動完成后,正在發生恢復,也就是上面日志中的 Recovery過程,和其它的關系型數據庫一樣,SQL Server對所有的數據庫行為都是先寫事務日志,然后在修改內存中的數據,然后通過后台的一個進程在適當的時候進行寫入硬盤(Lazy write),所以在數據庫運行過程中,磁盤中的數據並不是最新的,如果這個時候關閉了,在下一次啟動過程中SQL Server就要根據事務日志中的記錄,將磁盤中的舊的數據改寫,改寫過程為:
1、重做redo
2、回滾和撤銷 undo/rollback
上面的目的就是為了保證數據庫一致性。
如果上面的流程發生了問題,就會進去到下面這個狀態:
RECOVERY PENDING(掛起還原):
這個過程就是將恢復數據的過程掛起,掛起的原因基本就是不能正常打開所用的數據庫文件。這里先記住這個狀態就行,我在后面的內容會再現這個問題,以及給出解決方案。
如果能找到文件或者能打開文件,但是文件有問題,機會出現下面這個狀態:
SUSPECT(質疑):
這個狀態,我相信很多用戶如果在玩數據庫久了的時候,會偶爾遇到,相對於其它狀態,這個狀態是出現最高的。
原因很簡單:數據庫文件壞掉了。
當經歷了上面的這個幾個狀態都不出現問題,上面的這幾個狀態下,數據庫都是不能使用的,會進入到下面這個狀態:
ONLINE(在線):
這個狀態應該是最期待的了,數據庫在線,正常使用,默認都是正常的在線狀態。
當然,除了上面幾個數據庫自己形成的數據庫狀態,在我們管理員處理數據庫的時候也會更改狀態,這里我們順便提一下:
OFFLINE(離線):有在線狀態就有離線狀態,很簡單,讓數據庫離線,用戶不能使用
RESTORING(還原中):這個狀態很簡單,管理員正在還原該數據庫,不解釋
EMERGENCY(緊急):這個狀態也是管理員用的,就是說明數據庫有問題了,它正在盡量解決
以上幾個狀態中,發生在啟動過程中,並且會發生問題就是上面的RECOVERY PENDING(掛起還原)、SUSPECT(質疑)、RECOVERING(恢復中):
我們依次來看:
RECOVERY PENDING(掛起還原):
出現這個狀態通常的原因是數據庫文件找不到,或者文件找到權限訪問不到,我們來看該問題報錯信息:
在數據庫中存儲方式中,分為主文件組和輔助文件組和日志文件,為了展示方便我們特意建立了個測試庫,來重現該部分問題:
<1>主文件組問題
當不能訪問主文件組文件的時候,也就是上面的CnblogsTestDB.mdf文件,會報如下錯誤:
我們先來看數據庫:
在實例啟動的過程,恰巧有一個庫顯示了上面我們提到的一個狀態:RECOVERING(恢復中),我順便把圖給截圖了,當然出現這個情況很正常,有時候刷新一下就正常,其它用戶庫沒有顯示是因為庫太小,恢復時間太短,我們捕捉不到。
我們來看,上面我們建立的測試庫CnblogsTestDB已經不能訪問了,我們來看一下Error中的錯誤信息:
錯誤信息很明顯,說這個該文件不能訪問,並且確切的說出了這個為操作系統錯誤,那我們看操作系統的錯誤記錄:
可以看到在Windows系統日志中也能看到該部分錯誤信息。
解決方案:
此問題的解決方法還是很簡單的,一般主要是因為權限問題,只需要將數據庫管理員賬戶組,提權到可讀寫權限就可以,然后重啟服務:
上面的情況是找到數據庫文件,但是不能打開數據庫文件,當然還有可能是直接找不到數據庫文件,系統會報出如下錯誤:
會給出17204錯誤,報找不到文件錯誤
解決方案:
a、如果能找到數據文件最好了,拷貝到錯誤制定的路徑下既可以,然后重啟實例
b、不能找到文件了,那就得只能刪除該庫,重新新建同名庫,從備份文件中還原
一般上述問題發生在物理存儲出現了故障,當然不排除某些軟件操作,比如殺毒軟件、還有人為誤刪等原因。如果沒有備份,這可能是一個很大的遭難,基本可以確定的完全還原的可能性不高!所以記住:備份數據庫的重要性!
<2>輔助文件組問題
上面的出現問題的文件為數據庫的主文件組,當我們數據庫在承載到一定數據量的情況下,我么采取多個輔助文件組來容納數據,下面我們來看一下輔助文件組的問題:
同樣的提示的輔助文件組不能正常打開,或者找不到相關的輔助文件組,遇到這樣的問題我們怎么解決呢?
其實SQL Server數據庫輔助文件存儲的主要為數據庫的數據內容信息,關於本庫的一些架構信息是放在主(primary)文件組中,所以我們可以先這樣
解決方案:
a、我們將打不開或者不能訪問的數據庫文件(輔助文件)設置成離線,然后先將能夠正常的數據文件上線,確保除了損壞的那部分文件的其它庫信息能正常訪問,我們通過以下代碼更改:
ALTER DATABASE CnblogsTestDB MODIFY FILE(NAME=CnblogsTestDB2,OFFLINE)
GO
ALTER DATABASE CnblogsTestDB set ONLINE
GO
這樣,我們刷新下數據庫,既可以正常訪問正確的數據信息:
當我們處於生產環境中,生產庫不能正常啟動的時候,此刻的火燒眉毛的時刻,采取上面的方法先確保一部分數據能正常訪問也不失為一種緩議之計。
下面的步驟就是找到該輔助文件,並且確保有正常的權限訪問,更重要的是找到的輔助文件不能是損壞的,然后拷貝至錯誤文件中給出的路徑,然后重啟實例,上線該庫。
b、當然大部分情況下,我們找不到該文件,或者這個文件已經損壞,那就得采取第二種方案,通過備份還原,根據以往的經驗,建議采取的措施是:
先將能訪問的數據庫做一次備份,然后通過文件組恢復的方式,恢復上面出問題的文件組。
<3>日志文件組
其實從市面上的所有數據庫而言,其本身所有的機制都是通過先寫日志,然后通過一個進程后寫入(lazy write)方式寫入到磁盤,這種方式是為了避免IO的阻塞,因為我們都知道磁盤IO這個問題一直是所有文件讀寫的最大瓶頸。
所以,日志文件是數據庫不可分割的一部分。當數據庫在啟動的過程,會通過日志中的記錄做一次數據的一致性校驗,文章的開端有介紹。
所以說,如果日志文件不能訪問,或者說出問題,那我們的SQL Server數據庫會出現什么問題呢?
我們先來看數據庫模式為簡單(SIMPLE)模式的,我將咱們的測試庫設置成簡單模式:
USE CnblogsTestDB
GO
ALTER DATABASE [CnblogsTestDB] SET RECOVERY SIMPLE WITH NO_WAIT
GO
然后我們停掉實例,然后刪除掉該庫的日志文件,然后重新啟動
可以看到處於簡單模式下,如果日志文件出現錯誤,在啟動的過程是不會發生任何問題的,這里的原因我們在啟動Error日志文件中能找到答案:
經過上面的日志分析,我們可以看到,當數據庫處於簡單模式下,數據庫在啟動的過程中,如果發現任何與日志相關的信息,則會重新創建一份日志文件,保證數據庫的正常訪問。
如果這樣那我們數據庫的完整性怎么保證呢,是這樣,如果數據庫處於簡單模式,在我們數據庫關閉的時候,系統會先將該提交的所有事務都寫入到磁盤中去,所有該回滾的就撤銷。
上面能正常創建數據庫日志文件的前提條件有兩條:1、數據庫為簡單模式;2、數據庫正常關閉,保證事務都已正常寫入磁盤
下面我們在看看如果恢復模式為“完整”模式下的,數據庫上次沒有正常的情況,SQL Server數據庫是如何處理的,
我們先將數據庫改成完整恢復模式,停掉實例,然后刪除日志,然后啟動
然后我們啟動,可以看到這個時候,數據庫不能正常訪問的,該錯誤的Error的日志信息為:
windows平台下也為我們記錄了該錯誤的日志信息:
其實出現上面的錯誤,很正常,因為有些數據庫的事務性操作已經記錄到事務日志中,還未寫入磁盤數據頁中,這時候發生了宕機,或者非正常關閉,這個對SQL Server數據庫是能應付的,但是,而在啟動的過程找不到相關的事務日志盡心回滾和寫入操作,所以該庫的數據時非一致性的,所以SQL Server是不讓我們使用該庫,出現此種錯誤,我們的解決方式有如下幾種:
解決方案:
a、如果有備份,最好最快的方式就是恢復數據庫備份或者找到了該日志文件拷貝到錯誤路徑下(推薦)
b、如果沒有備份,我們只能通過使用CHECKDB命令修復數據庫(不推薦)
上述解決方案中CHECKDB命令,是一種萬不得已的方式,而且,我可以明確的告訴你這命令使用的時候會可能造成數據丟失,並且在大數據庫中,運行周期很長!
當然在萬不得已的情況下,我們還的采取,過程如下:
我們先將數據庫設置成EMERGENCY(緊急)模式,並且為單用戶(SINGLE_USER)模式
USE CnblogsTestDB
GO
ALTER DATABASE CnblogsTestDB SET EMERGENCY
GO
ALTER DATABASE CnblogsTestDB SET SINGLE_USER
GO
經過我們上面的設置,將庫設置成了“緊急”模式,並且只為單用戶方式訪問,便於我們進行數據修復
然后我們執行CHECKDB命令,進行數據庫的修復
DBCC CHECKDB(CnblogsTestDB,REPAIR_ALLOW_DATA_LOSS)
GO
經過該命令的修復,數據庫會為系統新建一個日志,但是不能保證事務的一致性,也就是說會因此而丟失數據,所以非常不推薦的一種方式!
並且,在這過程中,如果是大數據庫的話,該修復過程會很漫長,當然我不能給出一個漫長參考值,因為這過程還有會出現其它的錯誤需要修復。
所以酌情考量。
當然,在恢復完成之后,不要忘記將數據庫改回多用戶模式
USE [master]
GO
ALTER DATABASE [CnblogsTestDB] SET MULTI_USER WITH ROLLBACK IMMEDIATE
GO
至此,這個有問題的庫就能夠正常訪問了。
----------------------------------------------------------霸氣的分割線-----------------------------------------------------------------------
在經歷了上面的文件級別錯誤后,在數據庫啟動的過程,還經常出現的是數據頁級別的錯誤,相對於上面的文件錯誤級別,在數據頁中造成的錯誤粒度更小,並且基本不會反映到數據庫級別,也就是說在出現數據頁級別的錯誤時候,該數據時可以正常訪問的,只是在訪問有錯誤的數據頁的時候才會報錯,在我們遇到這種錯誤的時候該如何解決呢?
下面我們依次來分析,首先我們來制作一個經典的824錯誤,以下部分內容牽扯到數據庫部分基礎,限於篇幅,我們不做詳細介紹:
<1>首先我們在我們的測試庫中新建一個表,我們將該表新建成一行為一個數據頁的方式,也就是說一行數據庫在數據庫中就能承載一個數據頁
USE CnblogsTestDB GO CREATE TABLE [dbo].[TestPage] ( [a] [int] NULL, [b] [nvarchar](3900) NULL ) ON [PRIMARY]
腳本很簡單,一張表,兩列,一列int類型,一列nvarchar(3900),一行數據的存儲空間為:3900*2(nvarchar(3900))字節+4(int)+96字節(頁頭)+36字節(行偏移)=7932字節,我們知道一個數據頁存儲的信息為8K=8192字節,包括其它消耗所以該表一行數據如果填充完,一行數據將近乎占據一個數據頁。
我們來添加三行數據,然后查看頁信息:
--插入三條數據 insert [TestPage] values(1,REPLICATE('A',3900)) insert [TestPage] values(2,REPLICATE('B',3900)) insert [TestPage] values(3,REPLICATE('C',3900)) go --查看頁信息 dbcc traceon(3604) --查看庫中頁集合 dbcc extentinfo(CnblogsTestDB,[TestPage])
可以看到,該表中現在有三個數據頁,我們來看看數據頁應該也是近乎沾滿的。
上圖顯示了,通過掃描表信息,共含有3個數據頁,每個數據頁中的數據量存儲占比到了96.55%,也就是說基本上是填充滿了。
當然,我們還可以通過DBCC PAGE命令,來查看每個頁中的具體內容,我們簡單的看一個頁面編號為90的數據頁:
通過上面的命令可以看到,該數據頁中存儲的為表中的第一行的數據,並且在數據庫存儲文件中是以十六進制方式編碼存儲。
當然,如果感覺此方式不直觀,可以利用一個小工具進行數據頁的查看,這里我推薦使用Internal Views(此工具在樺仔的博文中有詳細介紹),可更直觀的展示數據存儲頁信息:
這里我們可以點擊我上面上面查看的第一行的數據內容頁進行查看
經過上面的分析步驟,其實我的目的是想重現在SQL Server啟動過程中,或者在線上的數據庫經常遇到的經典錯誤824錯誤
上述過程是原理篇,因為我們必須知道數據存儲的底層原理,才能理解好這個錯誤的原因,以及找到正確的處理方法。
下一步,我們來重現這個錯誤的原因,我們知道在我新建的測試表中含有兩個字段:a和b,並且a為int類型、b為nvarchar類型
然后我們介紹了底層的存儲機制,我現在將第一列a字段的整形數據內容存儲改成字符串類型,依次來損壞掉該數據頁內容
我先將服務停掉,然后用文件編輯工具,修改此數據頁內容,該數據頁內容為十六進制內容,當然在我搞壞這部分數據頁之前我先做一個完整備份
然后修改該數據頁信息,這里我使用UltraEdit文本編輯工具,打開文件,找到該數據頁內容
我們將上面的源數據更該一下,來把這個數據頁損壞掉
我們保存,然后重新啟動該數據庫看看
這就是我們平常比較常見的824錯誤的過程,而此過程有可能是磁盤壞道造成,或者誤修改文件等諸多原因,但是此問題還是比較常見的
當然,這種數據頁面的損壞可能造成的影響不是庫級別的,也就說不會造成數據庫不能訪問,其它表是能正常訪問的,但是只是在操作此損壞的數據頁的時候才會報錯,但有時候這幾個數據頁的損壞對業務產生的影響有可能就是致命的,所以我們要解決掉。
鄭重提示:上面過程也可以正確的更改數據頁中的數據,但是如果沒有確切的把握,基本上能把數據庫搞癱瘓掉,我是為了重現問題才修改底層元數據,所以在自己的生產庫中千萬不要亂搞!
在數據庫啟動的過程中,會發生一致性校驗,所以該錯誤應該會記錄到Error的錯誤日志文件中,我們來看:
windows平台下的錯誤日志:
當然,在啟動的過程中該問題有可能發生很多,比如磁盤壞道等原因,一系列的數據頁可能就沒法訪問了。所以SQL Server會將這些損壞的頁面記錄到msdb系統庫中,這我們在這個庫中查找到損壞的頁面集合:
至此,我們已經重現了經典的824錯誤,那我們該如何解決此問題呢?
解決方法:
a、如果此問題出現的頁面為數據承載頁,也就說該頁存儲的為內容數據或者為聚集索引的葉子節點數據,並且存在鏡像,版本在SQL Server2005以上,那么這個錯誤基本可以忽略,SQL Server能夠自動幫你修復此錯誤。
b、如果此問題出現在沒有鏡像的環境中,那就要區分是損壞頁面是否為聚集索引葉子節點數據,如果是,那就簡單了,直接重建索引就好了,如果不是,那此種方案還是不能解決,判斷方法如下:
利用DBCC PAGE命令查看當前數據頁內容,根據ObjectId跟蹤該頁位於哪個對象上,Metdata:IndexID的值判斷是否為索引樹中的節點值,如果大於0則表示為索引值,此時,重建該索引既可以。比如:
我們根據該頁的ObjectID,從數據庫中查找該頁所屬對象。
c、如果上述方案都不能滿足,那只有采取此種方案,我們可以利用數據庫備份進行還原,當然為了最大限度的避免數據庫離線,我們最好采取數據頁還原的方式,此種方式最為簡單,還原速度也最快,能夠最大限度的縮短數據庫離線時間,並且保證數據完整性。
這里提示下:在SQL Server2012版本一下,SSMS不提供圖像化數據頁還原方式,在SQL Sever以后的版本中,有圖像化界面操作。
所以,我們只能通過如下腳本進行還原:
RESTORE DATABASE CnblogsTestDB PAGE='1:90' FROM DISK = N'F:\SQLTest\CnlogsTestDB.bak' WITH NORECOVERY
當然有事務日志、更新備份的,需要依次恢復這過程的所有的備份,不要忘記備份尾部日志。
但是此方法也有局限性:
如果損壞的數據頁為
1、分配頁:GAM、SGAM和PFS頁
2、所有數據文件的啟動頁
如果發生損壞的是以上兩種,則無法通過該備份恢復頁方式進行恢復。如果這種情況下,建議考慮找合適的時間段進行全庫的恢復操作。(推薦)
d、上述情況是在存在有備份的情況下,如果沒有數據庫備份,那我們只能選擇最后的一招了,那就是DBCC CHECKDB命令,同樣和上面一樣,此種方式可能會造成數據丟失,所以不建議采用,如果能容忍數據丟失,采用的過程參照文中的上半部分。(不推薦)
至此,我們已經完成了一個SQL Server啟動過程或者平常最經常遇到的一個經典錯誤824錯誤,我們來總結下:
824錯誤原因:大部分是由於磁盤存儲導致的數據頁損壞,導致的SQL Server在讀取的時候發生了錯誤。
導致錯誤場景:磁盤壞道、突然斷電等情況下經常會出現此錯誤。
----------------------------------------------------------霸氣的分割線-----------------------------------------------------------------------
和824錯誤相關的還有一種是823錯誤,我們來介紹下該錯誤信息
由於場景所限,我就不重現該錯誤了,在這里我詳細的介紹下這兩種錯誤的原因和原理,就可以了,如果遇到了,解決的方式基本都是一致的,可參照上面的824錯誤解決方法。
SQL Server在每次寫入頁面的時候,會根據頁面里的數據算出一個校驗值,一同存儲到頁面中去。當下次讀取頁面的時候,再根據這次讀到的頁面數據,算出一個新的校驗值。如果寫入和讀出的數據一模一樣,那么兩個校驗值就是相等的。如果兩個校驗值不相等,就意味着上次SQL Server寫入的數據和這次讀取出來的一定不同,現在讀取出來的數據就有問題了。
823錯誤就代表着SQL Server在向操作系統申請某個頁面讀寫的時候遇到了Windows讀取或寫入請求失敗。所以該問題的原因大部分是源自於操作系統層面,更確切的說是物理文件損壞而導致此錯誤,比如設備驅動程序導致等。
824錯誤則是在讀取數據頁面時候,發現數據頁面有問題,比如讀取出來的校驗值不對等。
當上面描述的823和824錯誤出現大面積的時候,或者直接部分數據文件完全壞掉的情況下,在SQL Server啟動過程中就會出現數據庫SUSPECT“質疑”狀態。
經過我的多次數據頁的破壞和摧殘,我已經順利的將我們的這個測試庫給搞成了質疑狀態,我們來看SUSPECT(質疑)的狀態庫:
這里我直接DBCC CHECKDB命令嘗試着恢復下看看
所以到此,我們要做的就是避免上述錯誤的發生。如果在生產庫中發生了我上面的情況,然后沒有數據庫備份,那么剩下來你要做的事情:我估計就是准備簡歷了.....
結語
本篇文章到此結束了......文章主要還是分析SQL Server啟動過程中,加載用戶數據庫的時候,所遇到的一系列問題,文中部分內容需要有一定數據庫基礎知識才能讀懂,篇幅有限,我們沒有做深入的講解分析,比如上面的幾個重要的命令DBCC PAGE....DBCC CHECKDB..等等,隨便一個都能寫出一系列的內容,我們側重的還是問題的解決,和問題原因分析,后續文章中會介紹這一系列的命令作用,以及正確的使用技巧。
....此篇耗時四天完成....文中部分數據庫錯誤都是我耗費精力一步一步調整出來,目的是真實的展現錯誤明細,其實問題解決容易,問題重現的過程復雜。
如果經常使用SQL Server,其實這些問題都是我們會經常遇到的,所以我們要記住相應的解決方案,做的有備無患!
當然個人能力有限,部分不當之處,還望指出不吝賜教。
文章最后給出本篇的關聯篇:
你所不知道的SQL Server數據庫啟動過程,以及啟動不起來的各種問題的分析及解決技巧
如果您看了本篇博客,覺得對您有所收獲,請不要吝嗇您的“推薦”。