結構健檢
Metadata/Analysis 照一組規則檢查一張資料表,發現寫成指令碼裡的註解:
-- [SCHEMA-001][Warning] IX_Loan_3:INCLUDE 帶了 Remark nvarchar(max),索引會跟著資料長成與資料表同一個量級。預設關閉,由「指令碼附上結構健檢的發現」開啟。
內建規則
| 識別碼 | 嚴重度 | 抓什麼 |
|---|---|---|
| SCHEMA-001 | Warning | INCLUDE 帶了大型物件資料行 |
| SCHEMA-002 | Warning | 索引鍵是另一個索引的前綴 |
| SCHEMA-003 | Warning | 資料表沒有叢集索引(堆積) |
| SCHEMA-004 | Error | 資料表沒有主索引鍵 |
| SCHEMA-005 | Warning | 同語意的資料行型別不一致 |
| SCHEMA-006 | Warning | 列舉語意的資料行沒有 CHECK |
| SCHEMA-007 | Information | 名稱像外來鍵卻沒有外來鍵 |
| SCHEMA-008 | Information | datetime 建議改用 datetime2 |
| SCHEMA-009 | Warning | text/ntext/image 建議改用 max 型別 |
三級嚴重度不是裝飾:把「建議改用 datetime2」與「這張表沒有主索引鍵」放在同一級, 使用者看第三次就會把整個健檢關掉。
SCHEMA-002 只比較啟用的非叢集 B-tree 索引;篩選範圍、鍵排序與 INCLUDE 覆蓋都相容才 提示評估整併,不建議直接刪除。完全相同的兩個索引只報一個;叢集資料行存放區不算堆積。 第四層尚在載入或已失敗時整個分析器不產生發現,避免把未知狀態當成缺索引或缺約束。
一條規則一個型別
ISqlSchemaRule 的實作各自獨立,不是一個巨大的 switch。規則要能單獨關掉, 而「關得掉」是這個功能可以預設開啟的前提。新增一條只有兩個動作:寫一個類別、 加進 SqlSchemaAnalyzer.BuiltInRules,不必動到任何既有的程式碼。
SqlSchemaAnalyzer.ForRules(null) 是全開,傳空集合是「一條都不跑」—— 混為一談的話,使用者關掉最後一條規則會得到全部打開。
規則是延遲列舉的,所以例外會在 foreach 當中才冒出來,圍在分析器那一層才接得到。 接住之後那一條這一次的發現整組丟掉:使用者要的是那份指令碼,不是一則健檢 錯誤,而半份發現比沒有發現更難懂。
順序穩定不是為了好看:發現會寫進指令碼,而那份指令碼要進得了版本控制。 順序每次不同的話,兩份內容相同的指令碼會 diff 出一整片紅。
誤報是這個功能唯一的死法
每一條規則都同時驗「抓得到該抓的」與「不抓不該抓的」。只驗前者的話, 一條「永遠回報有問題」的規則會全綠通過——而誤報正是使用者關掉整個健檢的原因。 已經處理掉的幾種:
- 有唯一索引的資料行不是外來鍵。 一個
PublicId uniqueidentifier以Id結尾,卻是這張表自己的候選鍵。主索引鍵之外,單一資料行的唯一索引與唯一條件 約束也算——漏掉這一半的話 SCHEMA-007 會在每一張表上誤報。 - 型別大類不同的同後綴資料行不比。
LoanId int與PublicId uniqueidentifier都以Id結尾,那個差異多半是刻意的:一個是流水號, 一個是對外的識別碼。SCHEMA-005 只在同一個大類裡比(varchar對nvarchar值得報,int對uniqueidentifier不值得)。 - 沒有說明的資料行不判斷是不是列舉。 SCHEMA-006 認的是「小整數或單字元, 而且說明裡寫了
=」這個組合;沒有說明時報出來就是猜。 - 一個資料行都查不到時什麼都不報。 那是「這一輪沒有資料」(權限被收回、 物件剛被卸除),不是這張表沒有主索引鍵。報出來的話每一次降級都會多出假的發現。
- 主索引鍵與唯一索引不算多餘。 它們同時是條件約束,刪不掉也不該刪, 即使索引鍵確實是別的索引的前綴。
兩個開關不是重複
跑不跑由 SqlScriptContext.Analyzer 決定(null 就不跑),寫不寫由 SqlScriptOptions.IncludeAnalyzerComments 決定。關掉輸出時連跑都不跑: 那一輪要掃過每一個資料行與每一個索引,而結果沒有人會看到。分成兩個是為了 將來的獨立結果面板——那時要跑健檢但不寫進指令碼。
發現排在各物件的第一個敘述之前,不是整份的最前面:多物件時每一張表的發現 要跟著自己那張表,否則一份三十張表的指令碼開頭會是一整頁分不出屬於誰的警告。 那一段不參與批次分隔,全是註解的東西後面接一個 GO 沒有意義。