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

Применяется к:SQL ServerAzure SQL DatabaseAzure, управляемому экземпляру SQL Azure

До SQL Server 2016 (13.x) размер данных в строке оптимизированной для памяти таблицы не может превышать 8 060 байт. Однако начиная с SQL Server 2016 (13.x) и в Базе данных SQL Azure теперь можно создать оптимизированную для памяти таблицу с несколькими большими столбцами (например, несколькими столбцами varbinary(8000) и бизнес-столбцами (т. е. varbinary(max), varchar(max) и nvarchar(max)) и выполнять операции с ними с помощью встроенных скомпилированных модулей T-SQL и типов таблиц.

Столбцы, превышающие максимальный размер строки в 8060 байт, размещаются вне строки в специальной внутренней таблице. У каждого такого столбца имеется соответствующая внутренняя таблица, которая, в свою очередь, имеет один некластеризованный индекс. Дополнительные сведения об этих внутренних таблицах, используемых для столбцов вне строк, см. в sys.memory_optimized_tables_internal_attributes (Transact-SQL).

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

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

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

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

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

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

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

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

Размер таблицы вычислений

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

[table size] = [size of index 1] + ... + [size of index n] + ([row size] * [row count])  

Размер хэш-индекса фиксируется на момент создания таблицы и зависит от фактического числа контейнеров. Значение bucket_count, указанное спецификацией индекса, округляется в сторону увеличения до ближайшей степени числа 2 для получения фактического числа контейнеров. Например, если заданное число bucket_count равно 100 000, то фактическое число контейнеров для индекса составляет 131 072.

[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, и массив смещений для типов данных с переменной длиной.

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

Row structure for a table that has two indexes.

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

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

Structure of a table with two columns and indexes.

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

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

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

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

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

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

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

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

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

Имя City
Джон Пекин
Джейн Prague

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

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

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

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

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

  • Фактический размер (далее — фактический размер строки) представляет собой фактический размер строки в памяти и в файлах контрольных точек.

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

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

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

Bit: 1

Tinyint: 1

Smallint: 2

Int: 4

Real: 4

Smalldatetime: 4

Smallmoney: 4

Bigint: 8

Datetime: 8

Datetime2: 8

Float: 8

Money: 8

Числовой (точность <=18): 8

Time: 8

Числовой (точность>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).
Эта строка применяется только к фактическому размеру строки.

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

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

Для хэш-индекса фактическое число контейнеров округляется в сторону увеличения до ближайшей степени числа 2. Например, если заданное число bucket_count равно 100 000, то фактическое число контейнеров для индекса составляет 131 072.

Рассмотрим таблицу 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: 16 384. Поэтому общий размер индексов для таблицы 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')  

Ограничения столбцов вне строки

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

  • Если имеется индекс columnstore для таблицы, оптимизированной для памяти, то все столбцы должны умещаться "в строке".
  • Но все ключевые столбцы индекса должны хранится "в строке". Если ключевой столбец индекса не помещается "в строке", добавление индекса завершается ошибкой.
  • Пояснения по изменению таблицы, оптимизированной для памяти, со столбцами "вне строки".
  • Для больших объектов (LOB) ограничение размера соответствует аналогичному ограничению для таблиц на диске (лимит 2 ГБ на значения LOB).
  • Для обеспечения оптимальной производительности рекомендуется проследить, чтобы большинство столбцов умещалось в 8060 байт.

Некоторые из этих особенностей подробно рассмотрены в записи блога о новых возможностях выполняющейся в памяти OLTP в SQL Server 2016, появившихся после выпуска CTP3.

См. также

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