中繼資料載入策略
分四層按需載入,讓第一次按鍵的成本與資料庫大小脫鉤:
| 層 | 內容 | 何時載入 |
|---|---|---|
| 1 | 物件與結構描述名稱 | 啟用 Hover 時新頁面背景預載;建議或 Hover 按需補載,常駐快取 |
| 2 | 單一物件的欄位、參數與說明 | 使用者選取該物件,或滑鼠停留後在背景補上 |
| 3 | 定義本文 | 需要顯示或展開 ALTER 時 |
| 4 | 索引、外來鍵、CHECK、觸發程序、擴充屬性與儲存位置 | 只有展開結構預覽或要一份指令碼時 |
不在這四層裡的有兩份:系統物件(第一次問到才載入、不設有效期)與執行個體名單(定序、語言、時區)—— 後者的快取鍵是伺服器而不是資料庫,見執行個體名單。系統物件載入後 會併回第一層快照的一份獨立索引,只有限定在 sys 或 INFORMATION_SCHEMA 的名稱查得到, 見物件種類。
條件約束(CHECK/DEFAULT/主索引鍵與唯一鍵/外來鍵)沒有自己的第三、四層: 它的每一個欄位都住在父物件的第四層裡。被要求結構時先用一條 ConstraintParent 問出父物件是誰,再走同一個 GetStructureAsync 載那張表—— 父物件的結構本來就有快取,所以同一張表上的第二個條件約束不再問伺服器。 接法與指令碼見 F12 指令碼。
第四層刻意不併進第二層:第二層在按鍵路徑上,使用者輸入 a. 要的是欄位清單, 為此每次多付五次查詢並不值得。同一個理由,第四層那五份走的是同一條連線—— 它們一定是一起要的,分開載入只是在打開結構的那一刻多開幾次連線。
重建定義才需要的欄位(定序、識別值種子、預設值條件約束的名稱、SPARSE)則跟著 第二層一起回來:那是同一條 sys.columns 查詢多取幾欄,不是多一次來回。 系統物件換成 sys.all_columns 問同一份本體——sys.columns 一列都不會回來。 它們聚在 SqlColumnScriptDetail,不攤平進 SqlColumnInfo——讀資料行模型的 四條路徑裡有三條一個都用不到。
MS_Description 也在第二層,雖然它是一筆擴充屬性。決定的是誰要看它: 滑鼠停留提示只讀快取、不等查詢,而第四層要使用者主動打開結構才載入——併在 那裡的話,提示上的說明只有「剛好開過結構」的物件才有,而畫面上看不出那個差別。 資料行的說明沒有多一次來回(Columns 多一個 LEFT JOIN),物件自己的說明是 一條 ObjectDescription:sys.extended_properties 的鍵四段都給定,最多一列。
第四層仍然整批取回所有擴充屬性——那一份是要寫回 sp_addextendedproperty 的, 不是給人看的一句話。兩邊讀的是同一個目錄檢視,同一次失效,不會各說各話。
第三層有三個來源,但下游只看得到一個欄位(SqlObjectDetail.Definition):
| 種類 | 定義從哪裡來 |
|---|---|
| 模組(程序、函式、檢視、觸發程序) | OBJECT_DEFINITION |
| 同義字 | sys.synonyms.base_object_name,組成 CREATE SYNONYM … FOR …; |
| 序列 | sys.sequences 的界限、循環與快取,組成 CREATE SEQUENCE …; |
後兩者的 OBJECT_DEFINITION 一律回傳 NULL——它們的定義就是目錄檢視上的那幾個 欄位,所以那不是「重建一個近似值」,而是把定義本身寫回 T-SQL 的樣子。 組回去的那一份只有 Metadata/Formatting/SqlCatalogScript 一支, 滑鼠停留提示、浮動預覽的指令碼分頁與 F12 三條路徑共用它——各自照著自己的資料 再組一次的症狀,是同一個同義字在三個地方寫法不同,而其中總有一份會忘記更新。
四個界限值在 sys.sequences 裡是 sql_variant,實際型別隨序列自己的型別而變。 查詢在伺服器端就 CONVERT 成字串再讀:用 GetValue 收 sql_variant 拿到的是 裝箱的原生型別,一個 decimal(38,0) 的序列會讓任何一種整數轉型當場溢位, 而那會讓整份中繼資料依失敗規則降級成 「這一輪沒有資料」。
快取以「正規化連線字串(排除認證欄位)+資料庫名稱」為鍵,同一個資料庫 開多個查詢分頁只會查詢一次。中繼資料查詢一律另開連線,不會干擾使用者 正在執行的查詢或明確交易。目前是哪一個資料庫怎麼確認,見目前連線。
第一層過期時先回傳舊的、同時在背景更新,不讓使用者為了重新整理而等待。 物件清單過期幾分鐘的代價,遠低於每隔幾分鐘就有一次按鍵要等一輪資料庫查詢 ——而那一輪還會擋在欄位建議的前面。只有完全沒有資料時才真的等。
使用者按 Ctrl+Shift+D 或 工具 → SqlAssist → 重新整理建議 時則不同:命令只會 立即清空所有層級的快取,不會在按鍵路徑同步查詢資料庫。下一次開啟建議清單時才會 非同步重新讀取,或由下一次 Hover 在背景補載;刻意清空而不是只把時間標成過期, 才能避免那一次仍先看到舊清單。清除前已在途的第一層查詢不得回填失效資料。
第一層預載與一般讀取共用目錄的載入閘及失敗退避。重複 Hover 或多頁同時開啟不會 排出一串相同查詢;只預載目前或明確指名的資料庫,不掃描所有可存取的資料庫。
第二層則會預先載入:每次開啟建議清單時,順便把敘述裡每一張資料表的欄位 在背景撈回來。使用者打完 FROM PUBLISHER a 之後才會按下 a.,那段時間足夠 把欄位準備好,按下點號時直接命中快取。
敘述指名別的資料庫時,那個目錄的第一層也在這裡補——沒有別人會去載它,而 FROM LibArchive.dbo.Loan l 已經等於使用者指名要它。從前要等他真的打出整串限定字 才載,症狀是 SET |、WHERE | 這種沒有限定字的位置永遠列不出跨庫來源的欄位, 而同一份欄位打出 l. 就有。目前這條連線的第一層仍不在這裡觸發:那是建議清單自己的 工作,多排一輪只是重複查詢。
超過 200 毫秒的中繼資料操作一律寫進 SqlAssist.log,不必先打開詳細診斷:
耗時 1840 ms:欄位建議 [dbo].[PUBLISHER](第二層查詢資料庫)
耗時 2100 ms:建議清單(目標 Column,45 筆)2
跨資料庫(LibArchive.dbo.Loan)怎麼載入,見跨資料庫中繼資料。