當您建立記憶體優化資料表時,您必須指定 BUCKET_COUNT 參數的值。 本主題會建議判斷參數的適當值 BUCKET_COUNT 。 如果您無法判斷正確的桶數,請改用非叢集索引。 不正確的 BUCKET_COUNT 值,特別是太低的值,可能會大幅影響工作負載效能,以及資料庫的復原時間。 最好是高估桶數。
重複的索引鍵可能會降低哈希索引的效能,因為索引鍵會被哈希到相同的桶,導致該桶的鏈結增加。
如需非叢集哈希索引的詳細資訊,請參閱在 Memory-Optimized 數據表上使用索引的哈希索引和指導方針。
在記憶體優化數據表中,每個哈希索引都會分配一個哈希表。 為索引配置的哈希表大小是由 BUCKET_COUNTCREATE TABLE (Transact-SQL) 或 CREATE TYPE (Transact-SQL) 中的 參數所指定。 桶數量會在內部調整為下一個二的冪次。 例如,指定分桶數量為 300,000 時,實際的分桶數量會是 524,288。
如需貯體計數文章和影片的連結,請參閱如何判斷哈希索引的正確貯體計數(In-Memory OLTP)。
建議
在大部分情況下,桶數應該為索引鍵中相異值數量的 1 到 2 倍。 如果索引鍵包含許多重複的值,則每個索引鍵值平均有超過10個數據列,請改用非叢集索引
您可能不一定能夠預測特定索引鍵可能有或將會有多少值。 如果 BUCKET_COUNT 值在實際索引鍵值數目的 5 倍內,則效能應該可以接受。
若要判斷現有數據中唯一索引鍵的數目,請使用類似下列範例的查詢:
主鍵和唯一索引
因為主鍵索引是唯一的,索引鍵中的相異值數目會對應至數據表中的數據列數目。 如需 AdventureWorks 資料庫中資料表 Sales.SalesOrderDetail 上之 (SalesOrderID,SalesOrderDetailID)的範例主鍵,請發出下列查詢來計算相異主鍵值的數目,其對應至資料表中的資料列數目:
SELECT COUNT(*) AS [row count]
FROM Sales.SalesOrderDetail
此查詢會顯示 121,317 的數據列計數。 如果數據列計數不會大幅變更,請使用值區計數 240,000。 如果數據表中的銷售訂單數目預期為四倍,請使用值區計數 480,000。
非唯一索引
針對其他索引,例如在 (SpecialOfferID, ProductID) 上的多欄位索引,發出下列查詢來判斷唯一索引鍵值的數量:
SELECT COUNT(*) AS [SpecialOfferID_ProductID index key count]
FROM
(SELECT DISTINCT SpecialOfferID, ProductID
FROM Sales.SalesOrderDetail) t
此查詢會傳回 484 之 (SpecialOfferID, ProductID) 的索引鍵計數,指出應該使用非叢集索引,而不是非叢集哈希索引。
判斷重複項的數目
若要判斷索引鍵值的平均重複值數目,請將數據列總數除以唯一索引鍵的數目。
對於(SpecialOfferID,ProductID)的範例索引,其結果為121317 / 484 = 251。 這表示索引鍵值的平均值為 251,因此應該是非叢集索引。
針對桶計數進行疑難排解
若要針對記憶體優化資料表中的桶數問題進行疑難排解,請使用 sys.dm_db_xtp_hash_index_stats (Transact-SQL) 來取得空桶和資料列鏈結長度的統計數據。 下列查詢可用來取得目前資料庫中所有哈希索引的相關統計數據。 如果資料庫中有大型數據表,查詢可能需要幾分鐘的時間才能執行。
SELECT
object_name(hs.object_id) AS 'object name',
i.name as 'index name',
hs.total_bucket_count,
hs.empty_bucket_count,
floor((cast(empty_bucket_count as float)/total_bucket_count) * 100) AS 'empty_bucket_percent',
hs.avg_chain_length,
hs.max_chain_length
FROM sys.dm_db_xtp_hash_index_stats AS hs
JOIN sys.indexes AS i
ON hs.object_id=i.object_id AND hs.index_id=i.index_id
哈希索引健全狀況的兩個主要指標為:
空桶百分比
empty_bucket_percent 指出哈希索引中的空值區數目。
如果 empty_bucket_percent 小於 10%,則桶的數量可能太低。 在理想情況下, empty_bucket_percent 應為 33% 或更高。 如果貯體計數符合索引鍵值的數目,由於哈希分佈,大約 1/3 的貯體是空的。
平均鏈長
avg_chain_length 表示哈希貯體中數據列鏈結的平均長度。
如果 avg_chain_length 大於 10 且 empty_bucket_percent 大於 10%,則可能會有許多重複的索引鍵值,而且非叢集索引會更合適。 平均鏈結長度為1是理想的。
有兩個因素會影響鏈結長度:
重複;所有重複的數據列都是哈希索引中相同鏈結的一部分。
多個索引鍵值會對應至相同的貯體。 桶數越少,就會有更多的桶能映射到多個值。
例如,請考慮下列資料表和腳稿,以在資料表中插入範例數據列:
CREATE TABLE [Sales].[SalesOrderHeader_test]
(
[SalesOrderID] [uniqueidentifier] NOT NULL DEFAULT (newid()),
[OrderSequence] int NOT NULL,
[OrderDate] [datetime2](7) NOT NULL,
[Status] [tinyint] NOT NULL,
PRIMARY KEY NONCLUSTERED HASH ([SalesOrderID]) WITH ( BUCKET_COUNT = 262144 ),
INDEX IX_OrderSequence HASH (OrderSequence) WITH ( BUCKET_COUNT = 20000),
INDEX IX_Status HASH ([Status]) WITH ( BUCKET_COUNT = 8),
INDEX IX_OrderDate NONCLUSTERED ([OrderDate] ASC),
)WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA )
GO
DECLARE @i int = 0
BEGIN TRAN
WHILE @i < 262144
BEGIN
INSERT Sales.SalesOrderHeader_test (OrderSequence, OrderDate, [Status]) VALUES (@i, sysdatetime(), @i % 8)
SET @i += 1
END
COMMIT
GO
腳本會在數據表中插入 262,144 個數據列。 它會在主鍵索引和 IX_OrderSequence 中插入唯一值。 它會在索引中插入許多重複的值IX_Status:腳本只會產生8個不同的值。
BUCKET_COUNT疑難解答查詢的輸出如下所示:
| 索引名稱 | 總桶數量 | 空桶數 | 空桶百分比 | 平均鏈長度 | max_chain_length (最大鏈長) |
|---|---|---|---|---|---|
| IX_Status | 8 | 4 | 50 | 65536 | 65536 |
| IX_訂單順序 | 32768 | 13 | 0 | 8 | 26 |
| PK_SalesOrd_B14003C3F8FB3364 | 262144 | 96319 | 36 | 1 | 8 |
請考慮此資料表上的三個哈希索引:
IX_Status:50% 的桶是空的,這很好。 不過,平均鏈結長度非常高(65,536)。 這表示大量的重複值。 因此,在此情況下,使用非叢集哈希索引並不合適。 應該改用非叢集索引。
IX_OrderSequence:0% 的桶是空的,而這個數值太低了。 此外,平均鏈結長度為8。 由於此索引中的值是唯一的,這表示平均8個值會對應至每個值區。 桶數應該增加。 索引鍵有 262,144 個唯一值,值區計數至少應為 262,144。 如果預期未來成長,數字應該會更高。
主鍵索引(PK__SalesOrder...):36% 的資料桶是空的,這是好的。 此外,平均鏈結長度為 1,這也是不錯的。 不需要變更。
如需針對記憶體優化哈希索引問題進行疑難解答的詳細資訊,請參閱 針對 Memory-Optimized 哈希索引的常見效能問題進行疑難解答。
進一步優化的詳細考慮
本節概述優化桶數量的其他考量。
若要達到哈希索引的最佳效能,請平衡配置給哈希表的記憶體數量,以及索引鍵中的相異值數目。 在點查找和表格掃描的效能之間也存在平衡。
計數值越高,索引中將會有較多空桶。 這會影響記憶體使用量(每個桶 8 個字節)和資料表掃描的效能,因為每個桶都是資料表掃描的一部分。
當桶的數量較少時,就會將更多數值指派給單一桶。 這樣會降低點查詢和插入的效能,因為 SQL Server 可能需要遍歷單一桶中的多個值,以找到搜尋述詞所指定的值。
如果桶數明顯少於索引鍵的總數,許多值將會映射到每個桶。 這會降低大部分 DML 作業的效能,特別是點查閱(個別索引鍵的查閱)和插入作業。 例如,您可能會看到 SELECT 查詢,以及使用相等述詞的 UPDATE 和 DELETE 作業效能不佳,這些述詞與 WHERE 子句中的索引鍵欄相匹配。 低貯體計數也會影響資料庫的復原時間,因為資料庫啟動時會重新建立索引。
重複的索引鍵值
重複的值會增加哈希衝突的效能影響。 如果每個索引鍵的重複次數很低,這通常就不是問題。 但是,如果唯一索引鍵的數目與數據表中的數據行數目之間存在很大的差異,就可能會發生問題。
具有相同索引鍵的所有數據列都會進入相同的重複鏈結。 如果多個索引鍵因為哈希衝突而位於相同的貯體中,索引掃描器一律必須掃描第一個值的完整重複鏈結,才能找到對應至第二個值的第一個數據列。 重複的索引鍵也會讓垃圾回收更難找到數據列。 例如,如果任何鍵有1,000個重複的項目,並且刪除其中一行,垃圾收集器必須掃描這1,000個重複項的鏈結,以將該數據列解除與索引的連結。 即使用來找出刪除的查詢使用了更有效率的索引(主鍵索引)來定位數據列,情況仍然如此,因為垃圾收集器需要從每個索引中取消連結。
對於哈希索引,有兩種方式可減少重複索引鍵值所造成的工作:
請改用非叢集索引。 您可以將欄位新增至索引鍵,而不需對應用程式進行任何修改,藉以減少重複項目。
為索引指定非常高的桶數。 例如,20 到 100 倍的唯一索引鍵數目。 這會減少哈希衝突。
小桌子
對於較小的數據表,記憶體使用率通常並不相關,因為相較於資料庫的整體大小,索引的大小會很小。
您現在必須根據您想要的效能類型做出選擇:
如果索引上效能至關重要的作業主要是點查閱和/或插入作業,那麼使用較多的桶數會更適合,以降低哈希衝突發生的可能性。 將行數增加到三倍甚至更多是最佳選擇。
如果完整索引掃描是主要效能關鍵作業,請使用接近實際索引鍵值數目的貯體計數。
大型數據表
對於大型數據表,記憶體使用率可能會成為問題。 例如,在具有 4 個哈希索引的 2.5 億個數據列數據表中,每個數據區計數為 10 億個,哈希表的額外負荷是 4 個索引 * 10 億個值區 * 8 個字節 = 32 GB 的記憶體使用率。 針對每個索引選擇值區計數 2.5 億時,哈希表的總負荷會是 8 GB。 請注意,除了每個索引新增至每個個別數據列的8個字節記憶體使用量之外,此案例中為8 GB(4 個索引 * 8 個字節 * 2.5 億個數據列)。
完整表掃描通常不在 OLTP 工作負載的效能關鍵路徑中。 因此,您需要在記憶體利用率與查詢和插入操作的效能之間做出選擇。
如果記憶體使用率成為考量,請選擇接近索引鍵值數目的分桶計數。 貯體計數不應明顯低於索引鍵值的數目,因為這會影響大部分 DML 作業,以及伺服器重新啟動后復原資料庫所需的時間。
優化點查閱的效能時,適當的值區計數會是唯一索引值數目的兩倍或甚至三倍。 较高的桶数量表示记忆体使用量增加,并导致完整索引扫描所需时间增加。