記憶體最佳化資料表中的資料表與資料列大小

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

在 SQL Server 2016 (13.x) 之前,經記憶體最佳化的資料表的 in-row 資料大小不能超過 8,060 個位元組。 不過,自 SQL Server 2016 (13.x) 開始,可在 Azure SQL 資料庫中建立具有多個大型資料行 (例如,多個 varbinary(8000) 資料行) 和 LOB 資料行 (即 varbinary(max)、varchar(max) 和 nvarchar(max)) 的經記憶體最佳化的資料表,並可使用原生編譯的 Transact-SQL (T-SQL) 模組和資料表類型對它們執行作業。

不符合 8,060 位元組資料列大小限制的資料行,會儲存在資料列外的另一個獨立內部資料表中。 每個資料列外資料行都有一個對應的內部資料表,而該內部資料表則有一個非叢集索引。 如需了解用於列外資料行的這些內部資料表詳細資訊,請參閱 sys.memory_optimized_tables_internal_attributes。

在某些情況中,計算資料列和資料表的大小會很有用:

  • 資料表使用的記憶體數量。

    • 資料表所使用的記憶體數量無法精確計算。 許多因素都會影響使用的記憶體數量。 例如以頁面為單位的記憶體配置、區域性、快取和填充等因素。 此外,還包括與使用中交易相關聯或正等待垃圾回收的多個資料列版本。

    • 資料表中的資料和索引所需的大小下限是由 <table size> 的計算提供,本文稍後將對此進行討論。

    • 計算記憶體用量充其量只是一種估算,因此建議您在部署計畫中納入容量規劃。

  • 資料列的資料大小,以及它是否符合 8,060 個位元組的資料列大小限制? 若要回答這些問題,請使用本文稍後討論的 <row body size> 的計算。

記憶體最佳化資料表由一組資料列和索引所組成,而索引包含指向資料列的指標。 下圖說明包含索引和資料列的資料表,這些索引和資料列各自擁有資料列標頭和主體:

記憶體最佳化資料表圖。

計算資料表大小

資料表的記憶體中大小 (以位元組為單位) 計算如下:

<table size> = <size of index 1> + ... + <size of index n> + (<row size> * <row count>)

雜湊索引的大小會在資料表建立時固定,並取決於實際的雜湊桶數量。 索引定義中指定的 bucket_count 會無條件進位為最接近的 2 次方,以取得 實際貯體數。 例如,如果指定的 bucket_count 是 100000,則索引的實際貯體計數為 131072。

<hash index size> = 8 * <actual bucket count>

非叢集索引的大小順序是 <row count> * <index key size>。

資料列大小是將標頭和內容相加後計算得出:

<row size> = <row header size> + <actual row body size>
<row header size> = 24 + 8 * <number of indexes>

計算資料列內容大小

經記憶體最佳化的資料表中的資料列具有下列元件:

  • 資料列標頭包含實作資料列版本設定所需的時間戳記。 資料列標頭也包含索引指標,用來在雜湊桶中實現資料列串鏈(如先前所述)。

  • 資料列主體包含實際資料行資料,包括一些輔助資訊,如可為 Null 的資料行的 null 陣列以及可變長度資料類型的位移陣列。

下圖說明有兩個索引的資料表之資料列結構:

具有兩個索引之資料表的資料列結構圖。

開始和結束時間戳記表示特定資料列版本有效的期間。 在這個區間內開始的交易可以看到這個資料列版本。 如需更多資訊,請參閱使用記憶體最佳化資料表的交易。

索引指標指向屬於雜湊值區之鏈結中的下一個資料列。 下圖說明有兩個資料行 (姓名、城市) 之資料表的結構,其中包含兩個索引,一個是姓名資料行的索引,另一個是城市資料行的索引。

有兩個資料行與索引的資料表結構的圖表。

在此圖中,名稱 John 和 Jane 被雜湊至第一個桶。 Susan 會被雜湊到第二個桶。 城市 Beijing 和 Bogota 被雜湊至第一個桶。 Paris 和 Prague 被雜湊到第二個桶。

因此,名稱雜湊索引的鏈結如下:

  • 第一個區段:(John, Beijing);(John, Paris);(Jane, Prague)
  • 第二個儲存桶:(Susan, Bogota)

城市索引的鏈結如下:

  • 第一個儲存桶:(John, Beijing)、(Susan, Bogota)
  • 第二個儲存貯體:(John, Paris)、(Jane, Prague)

結束時間戳記 ∞ (無限大) 指出這是資料列的目前有效版本。 自從寫入此資料列版本後,該資料列一直未被更新或刪除。

對於大於 200 的時間,資料表包含下列資料列:

名稱 城市
John 北京
珍 布拉格

不過,開始時間為 100 的任何使用中交易都會看到下列版本的資料表:

名稱 城市
John 巴黎
珍 布拉格
蘇珊 波哥大

下表中討論 <row body size> 的計算。

資料列主體大小有兩種不同的計算方式,也就是計算的大小和實際大小:

  • 計算的大小以計算的資料列主體大小為代表,用以判斷是否超過資料列的大小限制 8,060 位元組。

  • 實際大小以實際的資料列主體大小為代表,為記憶體內部及檢查點檔案的實際資料列主體儲存體大小。

計算的資料列主體大小及實際的資料列主體大小計算方式相似。 唯一的差異在於 (n)varchar(i) 和 varbinary(i) 資料行大小的計算,如下列資料表底部所反映。 計算的資料列主體大小使用宣告的大小 i 作為資料行的大小,而實際的資料列主體大小使用實際的資料大小。

下表說明資料列主體大小的計算方式,表示為 <actual row body size> = SUM(<size of shallow types>) + 2 + 2 * <number of deep type columns>。

區段 大小 註解
淺層類型資料行 SUM(<size of shallow types>)。 個別類型的大小如下所示 (以位元組為單位):

bit:1
tinyint:1
smallint:2
int:4
real: 4
smalldatetime: 4
smallmoney:4
bigint:8
datetime: 8
datetime2: 8
float:8
money:8
numeric (精確度 <= 18):8
time: 8
numeric(精確度 > 18):16
uniqueidentifier:16
淺資料行邊距 可能的值包括:

1 如果有深層類型資料行,且淺層資料行的資料總大小為奇數。

0 否則
深層類型是指 (var)binary 和 (n)(var)char 類型。
深層類型資料行的位移陣列 可能的值包括:

0 如果沒有深層類型資料欄

2 + 2 * <number of deep type columns> 否則
深層類型是指 (var)binary 和 (n)(var)char 類型。
空值陣列 <number of nullable columns> / 8 進位到完整的位元組。 陣列中每個可為 null 的資料行都有 1 個位元。 這會向上取整為整數位元組。
空值陣列填充 可能的值包括:

1 如果有深層類型資料行,且 NULL 陣列的大小為奇數個位元組。
0 否則
深層類型是指 (var)binary 和 (n)(var)char 類型。
填補 如果沒有深層類型資料行:0

如果有深層類型資料行,則會根據淺層資料行所需的最大對齊,加入 0 到 7 個位元組的填充。 每個淺層資料行的對齊都需等於其大小 (如先前所記載),除了 GUID 資料行需要 1 個位元組 (非 16) 的對齊,而數值資料行一律需要 8 個位元組 (絕不是 16) 的對齊。 會採用所有淺層資料行中最大的對齊需求。 會加入 0 到 7 個位元組的填充,使截至目前的總大小(不包括深度類型資料行)成為所需對齊值的倍數。
深層類型是指 (var)binary 和 (n)(var)char 類型。
固定長度的深層類型資料行 SUM(<size of fixed length deep type columns>)

每個資料行的大小如下所示:

i 適用於 char(i) 和 binary(i)。
2 * i 用於 nchar(i)
固定長度深類型資料行是指類型為 char(i)、nchar(i) 或 binary(i) 的資料行。
可變長度的深層類型資料行計算的大小 SUM(<computed size of variable length deep type columns>)

每個資料行的計算後大小如下所示:

i 適用於 varchar(i) 和 varbinary(i)

2 * i 用於 nvarchar(i)
此列僅適用於 計算後的列主體大小。

可變長度的大型類型資料行是指類型為 varchar(i)、nvarchar(i) 或 varbinary(i) 的資料行。 計算的大小是由資料行的最大長度 (i) 所決定。
可變長度的深層類型資料行實際大小 SUM(<actual size of variable length deep type columns>)

每個資料行的實際大小如下所示:

n,其中 n 是儲存在資料行中的字元數,對於 varchar(i)。

2 * n,其中 n 是儲存在資料行中的字元數,對於 nvarchar(i)。

n,其中 n 是儲存在資料行中的位元組數,用於 varbinary(i)。
此資料列只適用於 實際資料列主體大小。

實際大小取決於該資料列中各資料行所儲存的資料。

範例:資料表和資料列大小計算

對於雜湊索引,實際的桶數會向上取整為最接近的 2 的冪次方。 例如,如果指定的 bucket_count 是 100000,則索引的實際貯體計數為 131072。

假設 Orders 資料表具有下列定義:

CREATE TABLE dbo.Orders (
    OrderID INT NOT NULL PRIMARY KEY NONCLUSTERED,
    CustomerID INT NOT NULL INDEX IX_CustomerID HASH WITH (BUCKET_COUNT = 10000),
    OrderDate DATETIME NOT NULL,
    OrderDescription NVARCHAR(1000)
)
WITH (MEMORY_OPTIMIZED = ON);
GO

此資料表具有一個雜湊索引與一個非叢集索引 (主索引鍵)。 它也有三個固定長度的數據行和一個可變長度的數據行,其中一個數據行可為 Null(OrderDescription)。 假設 Orders 資料表有 8,379 個資料列,且 OrderDescription 資料行中值的平均長度為 78 個字元。

若要判斷資料表大小,請先判斷索引的大小。 這兩個索引的 bucket_count 都指定為 10000。 這會進位到最接近的 2 的冪次:16384。 因此,Orders 資料表的索引大小總計為:

8 * 16384 = 131072 bytes

剩下的是資料表資料大小,也就是:

<row size> * <row count> = <row size> * 8379

(範例資料表有 8,379 個資料列)。現在我們有:

<row size> = <row header size> + <actual row body size>
<row header size> = 24 + 8 * <number of indices> = 24 + 8 * 1 = 32 bytes

接下來,我們來計算 <actual row body size>:

  • 淺層類型資料行:

    SUM(<size of shallow types>) = 4 <int> + 4 <int> + 8 <datetime> = 16
    
  • 淺層資料行填充為 0,因為淺層資料行的總大小為偶數。

  • 深層型別資料行的偏移陣列:

    2 + 2 * <number of deep type columns> = 2 + 2 * 1 = 4
    
  • NULL 陣列 = 1

  • NULL 陣列填充 = 1,因為 NULL 陣列大小為奇數,且有深度類型資料行。

  • 填充

    • 8 是最大對齊需求
    • 目前為止的大小為 16 + 0 + 4 + 1 + 1 = 22
    • 最接近的 8 倍數是 24
    • 填充總量為 24 - 22 = 2 個位元組
  • 沒有固定長度的深層類型資料行 (固定長度的深層類型資料行:0。)。

  • 深層類型資料行的實際大小為 2 * 78 = 156。 單一深層類型資料行 OrderDescription 具有 nvarchar 類型。

<actual row body size> = 24 + 156 = 180 bytes

若要完成計算:

<row size> = 32 + 180 = 212 bytes
<table size> = 8 * 16384 + 212 * 8379 = 131072 + 1776348 = 1907420

因此,記憶體中的總資料表大小約為 2 MB。 這並未涵蓋記憶體配置可能造成的負擔,以及存取此資料表的交易所需的任何資料列版本設定。

這個資料表及其索引實際配置和使用的記憶體可透過下列查詢取得:

SELECT * FROM sys.dm_db_xtp_table_memory_stats
WHERE object_id = object_id('dbo.Orders');

列外資料欄限制

在記憶體最佳化資料表中使用 off-row 資料行的某些限制與注意事項如下所示:

  • 如果記憶體最佳化資料表上有資料行存放區索引,則所有資料行都必須能容納於資料列內。
  • 所有索引鍵資料行都必須儲存在資料列內。 如果索引鍵資料行無法容納於列內,新增索引就會失敗。
  • 關於變更具有列外資料行的記憶體最佳化資料表時的注意事項。
  • 對於 LOB,其大小限制與磁碟型資料表相同(LOB 值上限為 2 GB)。
  • 為達最佳效能,建議應將大多數的資料行調整到 8,060 個位元組內。
  • Off-row 資料可能會導致記憶體和/或磁碟使用量過高。