Sql-Server

在 SQL Server 2012 中索引 PK GUID

  • August 21, 2020

我的開發人員已將他們的應用程序設置為使用 GUID 作為幾乎所有表的 PK,並且預設情況下 SQL Server 已在這些 PK 上設置聚集索引。

該系統相對年輕,我們最大的表剛剛超過一百萬行,但我們正在研究我們的索引,並希望能夠在不久的將來根據需要快速擴展。

所以,我的第一個傾向是將聚集索引移動到 created 欄位,它是 DateTime 的 bigint 表示。但是,我可以使 CX 獨一無二的唯一方法是在此 CX 中包含 GUID 列,但首先創建 order by。

這會使集群鍵太寬並且會提高寫入性能嗎?讀取也很重要,但此時寫入可能是一個更大的問題。

GUID 的主要問題,尤其是非順序的,是:

  • 鍵的大小(16 字節對 INT 的 4 字節):這意味著如果這是您的聚集索引,您將儲存 4 倍於鍵中的數據量以及用於任何索引的額外空間。
  • 索引碎片:由於鍵值的完全隨機性,幾乎不可能對非順序 GUID 列進行碎片整理。

那麼這對你的情況意味著什麼?這取決於您的設計。如果您的系統只是關於寫入並且您不關心數據檢索,那麼 Thomas K 概述的方法是準確的。但是,您必須記住,通過採用這種策略,您會為讀取和儲存數據帶來許多潛在問題。正如Jon Seigel指出的那樣,您還將佔用更多空間,並且基本上會出現記憶體膨脹。

圍繞 GUID 的主要問題是它們的必要性。開發人員喜歡它們,因為它們確保了全域唯一性,但這種唯一性是必要的,這種情況很少見。但是請考慮,如果您的最大值數小於 2,147,483,647(4 字節有符號整數的最大值),那麼您可能沒有為您的密鑰使用適當的數據類型。即使使用 BIGINT(8 個字節),您的最大值也是 9,223,372,036,854,775,807。如果您需要某個唯一鍵的自動遞增值,這對於任何非全域數據庫(以及許多全域數據庫)通常就足夠了。

最後,就使用堆與聚集索引而言,如果您只是在寫入數據,堆將是最有效的,因為您可以最大限度地減少插入的成本。但是,SQL Server 中的堆在數據檢索方面效率極低。我的經驗是,如果您有機會聲明一個聚集索引,那麼它總是可取的。我已經看到向表(超過 40 億條記錄)添加聚集索引將整體選擇性能提高了 6 倍。

附加資訊:

GUID 作為 OLTP 系統中的鍵和集群沒有任何問題(除非您的表上有很多索引會受到集群大小增加的影響)。事實上,它們比 IDENTITY 列更具可擴展性。

人們普遍認為 GUID 是 SQL Server 中的一個大問題——很大程度上,這是完全錯誤的。事實上,GUID 在具有超過 8 個核心的機器上可以顯著提高可擴展性:

對不起,您的開發人員是對的。在擔心 GUID 之前先擔心其他事情。

哦,最後:為什麼你首先想要一個集群索引?如果您關心的是具有大量小索引的 OLTP 系統,那麼使用堆可能會更好。

現在讓我們考慮一下碎片(GUID 將引入的)對您的讀取有什麼影響。碎片化存在三個主要問題:

  1. 頁面拆分消耗磁碟 I/O
  2. 半整頁的記憶體效率不如整頁
  3. 它會導致頁面無序儲存,從而降低順序 I/O 的可能性

由於您對問題的關注是關於可擴展性,我們可以將其定義為“添加更多硬體使系統執行得更快”,這些是您最少的問題。依次解決每一個問題

廣告 1) 如果你想要規模,那麼你可以買得起 I/O。即使是便宜的三星/英特爾 512GB SSD(幾美元/GB)也能讓你獲得超過 100K 的 IOPS。您不會很快在 2 插槽系統上使用它。如果你遇到這種情況,再買一個就可以了

廣告 2)如果您在表格中刪除,無論如何您將有半整頁。即使你不這樣做,記憶體也很便宜,而且對於除了最大的 OLTP 系統之外的所有系統 - 熱數據應該適合那裡。當您尋求規模時,希望將更多數據打包到頁面中是次優化的。

廣告 3) 由頻繁的頁面拆分、高度碎片化的數據建構的表執行隨機 I/O 的速度與順序填充的表完全相同

關於加入,您可能會在類似 OLTP 的工作負載中看到兩種主要的加入類型:散列和循環。讓我們依次看一下:

**散列連接:**散列連接假定掃描小表並通常尋找更大的表。小表很可能在記憶體中,因此 I/O 不是您關心的問題。我們已經談到了這樣一個事實,即碎片索引中的查找成本與非碎片索引中的成本相同

**循環連接:**將尋找外部表。相同的成本

您可能還會進行大量不良表掃描 - 但 GUID 再次不是您關心的問題,正確的索引才是。

現在,您可能正在進行一些合法的範圍掃描(尤其是在加入外鍵時),在這種情況下,與非碎片數據相比,碎片數據的“打包”更少。但是讓我們考慮一下在索引良好的 3NF 數據中您可能會看到的連接是:

  1. 來自表的連接,該表具有指向它所引用的表的主鍵的外鍵引用
  2. 另一種方式

廣告 1)在這種情況下,您將對主鍵進行一次搜尋 - 將 n 連接到 1。是否分片,相同的成本(一次搜尋)

廣告 2)在這種情況下,您正在加入同一個鍵,但可能會檢索不止一行(範圍搜尋)。在這種情況下,連接是 1 到 n。但是,您尋找的外部表,您正在尋找 SAME 鍵,它在碎片索引中與在非碎片索引中一樣可能位於同一頁面上。

考慮一下這些外鍵。即使您已經“完美”地順序放置了我們的主鍵 - 指向該鍵的任何內容仍然是非順序的。

當然,您可能正在某家銀行的某個 SAN 中的虛擬機上執行,該虛擬機成本低,流程高。那麼所有這些建議都將失去。但如果這就是你的世界,那麼可擴展性可能不是你想要的——你正在尋找性能和高速度/成本——它們都是不同的東西。

引用自:https://dba.stackexchange.com/questions/52601