共用方式為


記憶體最佳化資料表的記憶體需求估算

適用於:SQL ServerAzure SQL 資料庫Azure SQL 受控執行個體

記憶體最佳化資料表需要有足夠的記憶體,以將所有資料列和索引保留在記憶體中。 因為記憶體是有限的資源,所以請務必瞭解並管理系統上的記憶體使用量。 本節的主題涵蓋了常見的記憶體使用與管理案例。

合理估計每個記憶體最佳化資料表的記憶體需求非常重要,這樣您就可以為伺服器佈建足夠的記憶體。 這適用於新資料表,以及從磁碟型資料表移轉的資料表。 本節描述如何估計保存記憶體最佳化資料表的資料所需的記憶體數目。

如果您正在考慮從磁碟型資料表移轉至記憶體最佳化資料表,請參閱判斷 是否應該將資料表或預存程式移植至 In-Memory OLTP ,以取得哪些資料表最適合移轉的指引。 移轉至 In-Memory OLTP 底下的所有主題,提供從磁碟資料表移轉至記憶體最佳化資料表的指引。

估計記憶體需求的基本指引

在 SQL Server 2016 (13.x) 和更新版本中,記憶體最佳化資料表的大小沒有限制,但資料表確實需要適合記憶體。 在 SQL Server 2014 (12.x) 中,SCHEMA_AND_DATA資料表支援的資料大小為 256 GB。

記憶體最佳化資料表的大小等於資料大小加上一些資料列標頭的附加負荷。 記憶體最佳化資料表的大小大致對應於原始磁碟型資料表的叢集索引或堆積大小。

記憶體最佳化資料表上的索引通常小於磁碟資料表上的非叢集索引。 非叢集索引的大小順序是 [primary key size] * [row count]。 雜湊索引的大小是 [bucket count] * 8 bytes

當有活動的工作負載時,資料列版本控制和各種作業需要額外的記憶體來處理。 所需的記憶體量取決於工作負載,但為了安全起見,建議從記憶體最佳化資料表和索引預期大小的兩倍開始,並觀察實際記憶體耗用量。 列版本管理的額外負荷總是依賴於工作負載的特性,尤其是長時間執行的交易會增加額外負荷。 對於使用較大資料庫 (例如,大於 100 GB) 的大部分工作負載,額外負荷往往受到限制 (25% 或更少)。

如需 In-Memory OLTP 引擎中潛在記憶體額外負荷的詳細資訊,請參閱 記憶體片段化

詳細的記憶體需求計算

記憶體最佳化資料表範例

請考慮下列記憶體最佳化資料表結構描述:

CREATE TABLE t_hk
(  
  col1 int NOT NULL  PRIMARY KEY NONCLUSTERED,  

  col2 int NOT NULL  INDEX t1c2_index   
      HASH WITH (bucket_count = 5000000),  

  col3 int NOT NULL  INDEX t1c3_index   
      HASH WITH (bucket_count = 5000000),  

  col4 int NOT NULL  INDEX t1c4_index   
      HASH WITH (bucket_count = 5000000),  

  col5 int NOT NULL  INDEX t1c5_index NONCLUSTERED,  

  col6 char (50) NOT NULL,  
  col7 char (50) NOT NULL,   
  col8 char (30) NOT NULL,   
  col9 char (50) NOT NULL  

)   WITH (memory_optimized = on)  ;
GO  

使用此結構描述,讓我們確定此記憶體最佳化資料表所需的最小記憶體。

資料表的記憶體

記憶體最佳化表格資料列有三個部分:

  • 時間戳記
    列標頭/時間戳 = 24 個位元組。

  • 索引指標
    在資料表中的每個雜湊索引上,每一筆資料列都有一個 8 位元組的位址指標,指向索引中的下一個資料列。 由於有四個索引,因此每列會為索引指標配置 32 個位元組 (每個索引一個 8 位元組的指標) 。

  • Data
    資料列的資料部分大小是由每個資料行的類型大小總和來判斷。 在資料表中,我們有五個 4 位元組整數、三個 50 位元組字元資料行,以及一個 30 位元組字元資料行。 因此,每個資料列的資料部分為 4 + 4 + 4 + 4 + 4 + 50 + 50 + 30 + 50 (或 200) 個位元組。

以下是記憶體最佳化資料表中 5,000,000 (5 百萬) 個資料列的大小計算。 資料列所使用的記憶體總數估計如下:

資料表列的記憶體

從上述計算得知,記憶體最佳化資料表的每個資料列大小為 24 + 32 + 200 (或 256) 個位元組。 由於我們有 5,000,000,000 個資料列,因此資料表會耗用 5,000,000 * 256 個位元組,或 1,280,000,000 個位元組 - 大約 1.28 GB。

索引的記憶體

每個雜湊索引的記憶體

每個雜湊索引是由 8 位元組位址指標組成的雜湊陣列。 陣列的大小最好由該索引的唯一索引值數目決定。 在目前的範例中,唯一 Col2 值的數目是t1c2_index陣列大小的良好起點。 雜湊陣列太大會浪費記憶體。 雜湊陣列太小會降低效能,因為雜湊至相同索引項目的索引值有太多衝突。

雜湊索引達到的等值查詢速度很快,例如:

SELECT * FROM t_hk  
   WHERE Col2 = 3;

非叢集索引於範圍查詢時更快,例如:

SELECT * FROM t_hk  
   WHERE Col2 >= 3;

如果您想移轉磁碟資料表,您可以使用下列程式碼來判斷 index t1c2_index 的唯一值數目。

SELECT COUNT(DISTINCT [Col2])  
  FROM t_hk;

如果您要建立新資料表,則需要在部署之前估計陣列大小或從測試中收集資料。

如需有關在記憶體中 OLTP 記憶體最佳化資料表中雜湊索引如何運作的資訊,請參閱雜湊索引

設定雜湊索引陣列大小

雜湊陣列大小是由 (bucket_count= value) 設定,其中 value 是大於零的整數值。 如果 value 不是 2 的次方,實際的 bucket_count 會向上取整到下一個 2 的次方。 在資料表範例 (bucket_count = 5000000) 中,由於 5,000,000 不是 2 的乘冪,因此實際值區計數會無條件進位到 8,388,608 (2^23)。 計算雜湊陣列所需的記憶體時,您必須使用此數值,而不是 5,000,000。

因此在此範例中,每個雜湊陣列所需的記憶體為:

8,388,608 * 8 = 2^23 * 8 = 2^23 * 2^3 = 2^26 = 67,108,864 或大約 64 MB。

由於我們有三個雜湊索引,因此雜湊索引所需的記憶體為 3 * 64 MB = 192 MB。

非叢集索引的記憶體

非叢集索引會以 B 型樹狀結構實作,其內部節點包含索引值,以及指向後續節點的指標。 葉結點包含索引值以及指向記憶體中資料列的指標。

不同於雜湊索引,非叢集索引沒有固定的貯體大小。 此索引會隨資料動態成長和壓縮。

非叢集索引所需的記憶體,計算方式如下:

  • 分配給非葉節點的記憶體
    對於一般組態,分配給非葉子節點的記憶體是索引所佔用整體記憶體的較小部分。 因為很小,所以可以放心忽略。

  • 葉節點的記憶體
    分葉節點中,對於資料表內的每個唯一鍵,有一個資料列對應於該唯一鍵指向的資料列。 如果您有多個資料列具有相同的索引鍵 (也就是說,您有非唯一的非叢集索引),則索引分葉節點中只有一個資料列指向其中一個資料列,而其他資料列會彼此連結。 因此,所需的總記憶體大約是:

    • 非集中索引的記憶體 = (指標大小 + 關鍵列資料類型大小的總和) * 唯一鍵行數

非叢集索引最適合用於範圍查閱,如下列查詢所示:

SELECT * FROM t_hk  
   WHERE c2 > 5;  

用於資料列版本管理的記憶體

更新或刪除資料列時,記憶體中 OLTP 會使用樂觀並行控制來避免鎖定。 這表示當更新資料列時,會建立該資料列的其他版本。 此外,刪除是邏輯的 - 現有資料列會標示為已刪除,但不會立即予以移除。 系統會保留舊的資料列版本 (包括已刪除的資料列) 可用,直到所有可能使用該版本的交易完成執行為止。

由於等待記憶體回收循環釋放記憶體時,記憶體中可能會有其他許多資料列,因此您必須有足夠的記憶體來容納其他這些資料列。

您可以計算每秒更新和刪除的最大資料列數目,然後乘以最久的交易所需的秒數 (至少為 1),來估計其他資料列的數目。

該值接著會乘以資料列大小,以取得資料列版本化所需的位元組數。

rowVersions = durationOfLongestTransactionInSeconds * peakNumberOfRowUpdatesOrDeletesPerSecond

然後,透過將過時列數乘以記憶體最佳化表格列的大小來估計過時列的記憶體需求。 如需詳細資訊,請參閱 資料表的記憶體

memoryForRowVersions = rowVersions * rowSize

資料表變數的記憶體

資料表變數所使用的記憶體只會在資料表變數超出範圍時釋出。 從資料表變數中刪除的資料列 (包括隨更新刪除的資料列) 則不受記憶體回收限制。 在資料表變數離開範圍之前,不會釋出記憶體。

在大型 SQL 批次中定義的資料表變數,而不是在預存程序中定義,並在許多交易中使用,可能會耗用大量記憶體。 由於資料表變數中的資料列刪除後不會進行垃圾回收,因此這些已刪除的資料列可能仍然佔用大量記憶體,進而降低效能,因為讀取操作需要跳過這些已刪除的資料列。

用于成長的記憶體

先前的計算會預估資料表目前存在的記憶體需求。 除了此記憶體之外,您還需要估計資料表的成長,並提供足夠的記憶體來容納該成長幅度。 例如,如果您預期 10% 成長,則需要將先前的結果乘以 1.1,以取得資料表所需的記憶體總計。

記憶體碎片化

為了避免記憶體配置呼叫的額外負荷並改善效能,In-Memory OLTP 引擎一律會使用 64 KB 區塊 (稱為超級區塊) 向 SQL Server 作業系統 (SQLOS) 要求記憶體。

每個超級區塊僅包含特定大小範圍內的記憶體配置,稱為 sizeclass。 例如,超級區塊 A 可能具有 1-16 位元組大小類別中的記憶體配置,而超級區塊 B 可能具有 17-32 位元組大小類別中的記憶體配置,依此類推。

依預設,超級區塊也會依邏輯 CPU 進行分割。 這意味著對於每個邏輯 CPU,都有一組單獨的超級塊,並按大小類別進一步細分。 這減少了在不同 CPU 上執行的請求之間的記憶體分配爭用。

當 In-Memory OLTP 引擎進行新的記憶體配置時,它會先嘗試在現有的超級區塊中找到對應要求大小類別和處理該要求的 CPU 所需的可用記憶體。 如果此嘗試成功,則在used_bytes欄中,特定記憶體使用者的值會增加要求的記憶體大小,而allocated_bytes欄中的值則保持不變。

如果現有超級區塊中沒有可用記憶體,則會配置新的超級區塊,其中 used_bytes 的值會依要求的記憶體大小增加,而直欄中的 allocated_bytes 值會增加 64 KB。

隨著時間推移,當配置和解除配置超級區塊中的記憶體時,In-Memory OLTP 引擎所耗用的記憶體總量可能會明顯大於已使用的記憶體數量。 換句話說,記憶可能會變得支離破碎。

垃圾回收可能會 減少已使用的記憶體,但只有當一個或多個超級區塊變成空的並解除配置時,才會減少已配置的記憶體。 這適用於通過 sys.sp_xtp_force_gc 系統預存程序進行的自動和強制記憶體回收。

如果 In-Memory OLTP 引擎的記憶體碎片化和配置的記憶體使用量高於預期,您可以啟用 追蹤標誌 9898。 這會將超級區塊分割方案從每個 CPU 變更為每個 NUMA 節點,從而減少超級區塊的總數和高記憶體碎片的可能性。

此最佳化與具有許多邏輯 CPU 的大型機器更相關。 此項最佳化的權衡是,由於超級區塊較少,記憶體分配爭用可能會增加,這可能會降低整體工作負載吞吐量。 視工作負載模式而定,使用每個 NUMA 記憶體分割所導致的輸送量減少可能會或可能不會明顯。