Размер строк и таблицы для таблиц, оптимизированных для памяти

Таблица, оптимизированная для памяти, представляет собой набор строк, а также индексов, которые содержат указатели на строки. В таблице с оптимизацией для памяти строки не могут быть длиннее 8060 байт. Основные сведения о размере таблицы с оптимизацией для памяти помогут понять, имеет ли компьютер достаточно памяти.

Существуют две причины для вычисления размера таблиц и строк.

  • Какой объем памяти используется таблицей?

    • Объем используемой таблицей памяти невозможно подсчитать точно. На объем используемой памяти влияет множество факторов. Это такие факторы, как постраничное выделение места в памяти, размещение, кэширование и заполнение. Кроме того, несколько версий строк, которые имеют активные связанные транзакции либо ожидают сборку мусора.

    • Минимальный размер, необходимый для данных и индексов в таблице, определяется вычислением для [размера таблицы], о котором рассказывается ниже.

    • Вычисление используемой памяти в самом лучшем случае может быть выполнено лишь приближенно, поэтому рекомендуется включить планирование вместимости в планы разработки.

  • Каков размер строки данных, и укладывается ли он в ограничение размера строки 8060 байт? Чтобы получить ответ на эти вопросы, используйте вычисление для [размера текста строки], о котором рассказывается ниже.

На следующей схеме показана таблица с индексами и строками, которые в свою очередь содержат заголовки и текст:

Таблица, оптимизированная для памяти.
Таблица, оптимизированная для памяти, состоящая из индексов и строк.

Размер, занимаемый таблицей в памяти (в байтах) вычисляется следующим образом.

[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 size] = [row header size] + [actual row body size]  
[row header size] = 24 + 8 * [number of indices]  

Размер текста строки

Вычисление [размера текста строки] демонстрируется в следующей таблице.

Размер текста строки вычисляется двумя способами: вычисляемый размер и фактический размер.

  • Вычисляемый размер, далее [вычисляемый размер строки текста], используется для того, чтобы определить, не превышает ли размер строки ограничение в 8060 байт.

  • Фактический размер, далее [фактический размер строки текста], представляет собой фактический размер хранилища текста строки в памяти и в файле контрольных точек.

Оба показателя [вычисляемый размер строки текста] и [фактический размер строки текста] вычисляются аналогичным образом. Отличается только вычисление размера столбцов (n)varchar(I) и столбцов varbinary (I), как показано в нижней части таблицы. Вычисляемый размер строки использует в качестве размера столбца декларируемый размер i , тогда как фактический размер строки использует фактический размер данных.

В следующей таблице описано вычисление размера текста строки как [фактический размер текста строки] = SUM([размер мелких типов]) + 2 + 2 * [число столбцов глубокого типа].

Section Размер Комментарии
Столбцы поверхностных типов SUM [размер поверхностных типов]

Размеры отдельных типов:

Бит | 1

Tinyint | 1

Smallint | 2

Int | 4

Реальный | 4

Smalldatetime | 4

Smallmoney | 4

Bigint | 8

Datetime | 8

Datetime2 | 8

Float 8

Money 8

Числовой (precision <=18) | 8

Время | 8

Numeric(precision>18) | 16

Uniqueidentifier | 16
Заполнение столбца поверхностного типа Возможны следующие значения:

1, если в таблице присутствуют столбцы глубоких типов, а общий размер данных в столбцах поверхностного типа является нечетным числом.

0 в остальных случаях
Глубокие типы — это типы (var)binary и (n)(var)char.
Массив смещений для столбцов глубоких типов Возможны следующие значения:

0, если в таблице нет столбцов глубоких типов

2 + 2 * [количество столбцов глубоких типов] во всех остальных случаях
Глубокие типы — это типы (var)binary и (n)(var)char.
Массив значений NULL [количество столбцов, в которых допустимы значения NULL] / 8, с округлением в сторону увеличения до целого числа байт. Массив содержит один бит для каждого столбца, допускающего значения NULL. Эта величина округляется в сторону увеличения до целого числа байт.
Заполнение массива значений NULL Возможны следующие значения:

1, если в таблице имеются столбцы глубоких типов данных и размер массива значений NULL равен нечетному числу байтов.

0 в остальных случаях
Глубокие типы — это типы (var)binary и (n)(var)char.
Заполнение Если в таблице нет столбцов глубоких типов: 0

Если есть столбцы глубоких типов данных, добавляется 0–7 байт заполнения, исходя из наибольшего выравнивания, требующегося для столбцов поверхностных данных. Каждый столбец поверхностных типов требует выравнивания, равного его размеру (как показано в документе выше), за исключением столбцов GUID, которые требуют выравнивания на 1 байт (а не на 16), и числовых столбцов, которые всегда требуют выравнивания 8 байт (ни в коем случае не 16). Используется наибольшее требование выравнивания среди всех столбцов поверхностных типов, и заполнение 0–7 байт добавляется таким образом, чтобы общий размер на этот момент (без столбцов глубоких типов) был кратным числу необходимых выравниваний.
Глубокие типы — это типы (var)binary и (n)(var)char.
Столбцы глубоких типов фиксированной длины SUM ([размер столбцов глубоких типов фиксированной длины])

Размер каждого столбца составляет:

i для типов char(i) и binary(i).

2 * i для типа nchar(i)
Столбцы глубоких типов данных фиксированной длины — это столбцы типов char(i), nchar(i) или binary(i).
Столбцы глубоких типов данных переменной длины [вычисляемый размер] SUM([вычисляемый размер столбцов глубоких типов данных переменной длины])

вычисляемый размер каждого столбца составляет:

i для типов varchar(i) и varbinary(i)

2 * i для типа nvarchar(i)
Эта строка применяется только к [вычисляемому размеру текста строки]

Столбцы глубоких типов переменной длины — это столбцы типов varchar(i), nvarchar(i) или varbinary(i). вычисляемый размер определяется максимальной длиной (i) столбца.
Столбцы глубоких типов данных переменной длины [фактический размер] SUM([фактический размер столбцов глубоких типов данных переменной длины])

Фактический размер каждого столбца составляет:

n, где n — количество символов, хранящихся в столбце, для типа varchar(i).

2 * n, где n — количество символов, хранящихся в столбце, для типа nvarchar(i).

n, где n — число байтов, хранящихся в столбце, для типа varbinary(i).
Эта строка применяется только к [фактическому размеру текста строки].

Фактический размер определяется данными, которые хранятся в столбцах в данной строке.

Структура строки

Строки в таблице, оптимизированной для памяти, включают следующие компоненты.

  • Заголовок строки содержит метку времени, необходимую для управления версиями строки. Заголовок строки также содержит указатель индекса, который позволяет реализовать цепочку строк в хэш-контейнере (описано выше).

  • Текст строки содержит фактические данные столбцов, которые включают некоторые вспомогательные сведения, такие как массив значений NULL для столбцов, допускающих значение NULL, и массив смещений для типов данных с переменной длиной.

На следующем рисунке показана структура строк для таблицы с двумя индексами.

Структура строк для таблицы с двумя индексами.

Метки времени начала и конца показывают период, в котором определенная версия строки является допустимой. Транзакции, запускаемые в данном интервале, могут видеть эту версию строки. Дополнительные сведения см. в разделе Транзакции в таблицах, оптимизированных для памяти.

Указатели индекса указывают на следующую строку в цепочке, принадлежащей хэш-контейнеру. На следующем рисунке показана структура таблицы с двумя столбцами (имя, город) и двумя индексами, один для столбца name и второй для столбца city.

Структура таблицы с двумя столбцами и индексами.

На этом рисунке имена Джон и Джейн хэшированы на первый контейнер. Сьюзан хэширована на втором контейнере. Города Пекин и Богота хэшированы на первом контейнере. Париж и Прага хэшированы на втором контейнере.

Таким образом, цепочки для хэш-индекса по именам выглядят следующим образом.

  • Первый контейнер: (Джон, Пекин); (Джон, Париж); (Джейн, Прага)

  • Второй контейнер: (Сьюзан, Богота)

Цепочки для индекса по городам выглядят следующим образом:

  • Первый контейнер: (Джон Пекин), (Сьюзан, Богота)

  • Второй контейнер: (Джон, Париж), (Джейн, Прага)

Конечная метка времени ∞ (бесконечность) указывает, что это действительная на данный момент версия строки. Строка была обновлена или удалена с того момента, как была записана эта версия.

Для времени больше 200 таблица содержит следующие строки.

Имя Город
Джон Пекин
Джейн Прага

Однако любая активная транзакция с начальным временем 100 увидит следующую версию таблицы.

Имя Город
Джон Париж
Джейн Прага
Сьюзан Богота

Пример: вычисление размера строки и таблицы

Для хэш-индекса фактическое число контейнеров округляется в сторону увеличения до ближайшей степени числа 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 содержит 8379 строк, а средняя длина значений в столбце OrderDescription составляет 78 символов.

Чтобы определить размер таблицы, сначала необходимо определить размер индексов. Для обоих индексов указан показатель bucket_count, равный 10 000. Эта величина округляется в большую сторону до ближайшей степени числа 2: 16384. Поэтому общий размер индексов для таблицы Orders составляет:

8 * 16384 = 131072 bytes  

Остается найти размер данных таблицы, который равен

[row size] * [row count] = [row size] * 8379  

(Пример таблицы содержит 8379 строк.) Теперь у нас есть:

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

Теперь давайте рассчитаем [фактический размер текста строки].

  • Столбцы поверхностных типов:

    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 мегабайт. Это значение не учитывает потенциальные издержки при выделении памяти, а также управление версиями строк, необходимое для доступа транзакций к этой таблице.

Фактический размер памяти, выделяемый для данной таблицы и используемый ею и ее индексами, можно получить при помощи следующего запроса:

select * from sys.dm_db_xtp_table_memory_stats  
where object_id = object_id('dbo.Orders')  

См. также:

Таблицы, оптимизированные для памяти