512 байт не используются со страницы данных SQL Server 8 Кбайт

13

Я создал следующую таблицу:

CREATE TABLE dbo.TestStructure
(
    id INT NOT NULL,
    filler1 CHAR(36) NOT NULL,
    filler2 CHAR(216) NOT NULL
);

а затем создал кластерный индекс:

CREATE CLUSTERED INDEX idx_cl_id 
ON dbo.TestStructure(id);

Затем я заполнил его 30 строками, каждый размер которых составляет 256 байт (на основе объявления таблицы):

DECLARE @i AS int = 0;

WHILE @i < 30
BEGIN
    SET @i = @i + 1;

    INSERT INTO dbo.TestStructure (id, filler1, filler2)
    VALUES (@i, 'a', 'b');
END;

Теперь, основываясь на информации, которую я прочитал в книге «Учебный комплект (экзамен 70-461): запросы к Microsoft SQL Server 2012 (Ицик Бен-Ган)»:

SQL Server внутренне организует данные в файле данных на страницах. Страница размером 8 Кбайт принадлежит одному объекту; например, к таблице или индексу. Страница - это самая маленькая единица чтения и записи. Страницы далее организованы в экстенты. Экстент состоит из восьми последовательных страниц. Страницы из экстента могут принадлежать одному объекту или нескольким объектам. Если страницы принадлежат нескольким объектам, тогда экстент называется смешанным экстентом; если страницы принадлежат одному объекту, то экстент называется равномерным экстентом. SQL Server хранит первые восемь страниц объекта в смешанных экстентах. Когда объект превышает восемь страниц, SQL Server выделяет дополнительные единообразные экстенты для этого объекта. Благодаря этой организации мелкие объекты тратят меньше места, а крупные объекты становятся менее фрагментированными.

Итак, здесь у меня есть первая смешанная страница экстента размером 8 КБ, заполненная 7680 байтами (я вставил 30 строк размера 256 байт, так что 30 * 256 = 7680), чтобы проверить размер, который я запустил, проверка размера proc - он возвращает следующий результат

index_type_desc: CLUSTERED INDEX
index_depth: 1
index_level: 0 
page_count: 1 
record_count: 30 
avg_page_space_used_in_percent: 98.1961947121324
name : TestStructure        
rows : 30   
reserved :  16 KB
data : 8 KB 
index_size : 8 KB       
unused :    0 KB

Таким образом, 16 КБ зарезервировано для таблицы, первая страница 8 КБ предназначена для страницы корневого IAM, вторая страница предназначена для листовой страницы хранения данных, которая занимает 8 КБ и занимает ~ 7,5 КБ, теперь, когда я вставляю новую строку с 256 байт:

INSERT INTO dbo.TestStructure (id, filler1, filler2)
VALUES (1, 'a', 'b');

он не хранится на той же странице, хотя он имеет пространство 256 байт (7680 b + 256 = 7936, что по-прежнему меньше 8 КБ), создается новая страница данных, но эта новая строка может поместиться на той же старой странице Почему SQL Server создает новую страницу, когда он может сэкономить место и время поиска, вставляя ее в существующую страницу?

Примечание: то же самое происходит с индексом кучи.

Альфа Супремум
источник

Ответы:

9

Ваши строки данных не 256 байтов. Каждый из них больше похож на 263 байта. Строка данных с чисто фиксированной длиной имеет дополнительные издержки из-за структуры строки данных в SQL Server. Взгляните на этот сайт и прочитайте о том, как строится строка данных. http://aboutsqlserver.com/2013/10/15/sql-server-storage-engine-data-pages-and-data-rows/

Итак, в вашем примере у вас есть строка данных, которая имеет 256 байтов, добавьте 2 байта для битов состояния, 2 байта для числа столбцов, 2 байта для длины данных и еще 1 или около того для нулевого растрового изображения. Это 263 * 30 = 7890 байт. Добавьте еще 263, и вы превысите ограничение в 8 КБ, что приведет к созданию другой страницы.

dfundako
источник
Ссылка, которую вы предоставили, помогла мне лучше визуализировать структуру страницы, я искал что-то похожее на нее, но не смог ее найти, Thax
Supremum
11

Хотя верно, что SQL Server использует страницы данных 8 КБ (8192 байта) для хранения 1 или более строк, каждая страница данных имеет некоторые издержки (96 байтов), а каждая строка имеет некоторые издержки (не менее 9 байтов). 8192 байта не являются чисто данными.

Для более подробного изучения того, как это работает, смотрите мой ответ на следующий вопрос DBA.SE:

СУММА ДАННЫХ, не соответствующих размеру таблицы из sys.allocation_units

Используя информацию в этом связанном ответе, мы можем получить более четкое представление о реальном размере строки:

  1. Заголовок строки = 4 байта
  2. Количество столбцов = 2 байта
  3. NULL Bitmap = 1 байт
  4. Информация о версии ** = 14 байт (необязательно, см. Сноску)
  5. Общее количество служебных данных на строку (исключая массив слотов) = минимум 7 байт или 21 байт, если имеется информация о версии
  6. Общий фактический размер строки = минимум 263 (256 данных + 7 служебных данных) или 277 байтов (256 данных + 21 служебной информации), если информация о версии присутствует
  7. При добавлении в массив слотов общее пространство, занимаемое каждой строкой, фактически составляет либо 265 байтов (без информации о версии), либо 279 байтов (с информацией о версии).

Использование DBCC PAGEподтверждает мои расчеты, показывая: Record Size 263(для tempdb) и Record Size 277(для базы данных, которая установлена ALLOW_SNAPSHOT_ISOLATION ON).

Теперь, с 30 рядами, то есть:

  • БЕЗ информации о версии

    30 * 263 даст нам 7890 байт. Затем добавьте 96 байтов заголовка страницы для 7986 использованных байтов. Наконец, добавьте 60 байтов (по 2 на строку) массива слотов, чтобы на странице было всего 8046 байтов, а оставшихся 146. Использование DBCC PAGEподтверждает мой расчет, показывая:

    • m_slotCnt 30 (т.е. количество строк)
    • m_freeCnt 146 (т.е. количество байтов, оставленных на странице)
    • m_freeData 7986 (т.е. данные + заголовок страницы - 7890 + 96 - массив слотов не учитывается при вычислении «используемых» байтов)
  • С информацией о версии

    30 * 277 байт, всего 8310 байт. Но 8310 превышает 8192, и это даже не учитывает ни 96-байтовый заголовок страницы, ни 2-байтовый массив слотов строки (30 * 2 = 60 байт), что должно дать нам только 8036 используемых байтов для строк.

    НО, а как насчет 29 строк? Это даст нам 8033 байта данных (29 * 277) + 96 байтов для заголовка страницы + 58 байтов для массива слотов (29 * 2), равного 8187 байтов. И это оставило бы страницу с 5 оставшимися байтами (8192 - 8187; непригодно для использования, конечно). Использование DBCC PAGEподтверждает мой расчет, показывая:

    • m_slotCnt 29 (т.е. количество строк)
    • m_freeCnt 5 (т.е. количество байтов, оставленных на странице)
    • m_freeData 8129 (т.е. данные + заголовок страницы - 8033 + 96 - массив слотов не учитывается при вычислении «используемых» байтов)

Что касается кучи

Кучи заполняют страницы данных немного по-другому. Они поддерживают очень приблизительную оценку количества места, оставшегося на странице. При взгляде на выходе DBCC, посмотрите на строку для: PAGE HEADER: Allocation Status PFS (1:1). Вы увидите что- VALUEто похожее 0x60 MIXED_EXT ALLOCATED 0_PCT_FULL(когда я смотрел на кластерную таблицу) или 0x64 MIXED_EXT ALLOCATED 100_PCT_FULLкогда смотрел на таблицу кучи. Это оценивается для каждой транзакции, поэтому выполнение отдельных вставок, таких как выполняемый здесь тест, может показывать разные результаты для таблиц Clustered и Heap. Однако выполнение одной операции DML для всех 30 строк приведет к заполнению кучи, как и ожидалось.

Однако ни одна из этих подробностей, касающихся кучи, не влияет напрямую на этот конкретный тест, поскольку обе версии таблицы умещаются в 30 строк, а остается только 146 байт. Этого места недостаточно для другой строки, независимо от Clustered или Heap.

Пожалуйста, имейте в виду, что этот тест довольно прост. Расчет фактического размера строки может быть очень сложным в зависимости от различных факторов, таких как: SPARSEсжатие данных, данные больших объектов и т. Д.


Чтобы увидеть детали страницы данных, используйте следующий запрос:

DECLARE @PageID INT,
        @FileID INT,
        @DatabaseID SMALLINT = DB_ID();

SELECT  @FileID = alloc.[allocated_page_file_id],
        @PageID = alloc.[allocated_page_page_id]
FROM    sys.dm_db_database_page_allocations(@DatabaseID,
                            OBJECT_ID(N'dbo.TestStructure'), 1, NULL, 'DETAILED') alloc
WHERE   alloc.[previous_page_page_id] IS NULL -- first data page
AND     alloc.[page_type] = 1; -- DATA_PAGE

DBCC PAGE(@DatabaseID, @FileID, @PageID, 3) WITH TABLERESULTS;

** Значение 14-байт «Информация о версии» будет присутствовать , если база данных установлена либо ALLOW_SNAPSHOT_ISOLATION ONили READ_COMMITTED_SNAPSHOT ON.

Соломон Руцкий
источник
Если быть точным, для пользовательских данных доступно 8060 байт на страницу. Данные ОП все еще ниже.
Роджер Вольф
Информация о версии отсутствует, иначе 30 строк заняли бы 8310 байтов. Все остальное кажется правильным.
Роджер Вольф
@RogerWolf Да, "информация о версии" есть. И так, 30 строк требуют 8310 байтов. Вот почему эти 30 строк, на самом деле, не все умещались в 1 страницу, так как ФП наводят на мысль о том, какой тестовый процесс используется ФП. Но этот тестовый процесс неверен. Только 29 строк помещаются на странице. Я подтвердил это (даже с использованием SQL Server 2012).
Соломон Руцкий,
Вы пытались запустить тест для базы данных, которая не поддерживает RCSI / tempdb? Мне удалось воспроизвести точные цифры, предоставленные ОП.
Роджер Вольф
@RogerWolf Используемая мной БД не поддерживает RCSI, но установлена ​​в ALLOW_SNAPSHOT_ISOLATION. Я также только что попробовал tempdbи увидел, что «информация о версии» не существует, следовательно, 30 строк подходят. Я обновлю, чтобы добавить новую информацию.
Соломон Руцкий,
3

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

Однако вы могли заметить, что SQL Server на самом деле дает вам подсказку, что 31-я строка не помещается на странице. Чтобы следующий ряд поместился на той же странице, avg_page_space_used_in_percentзначение должно быть ниже 100% - (100/31) = 96,774194, и в вашем случае оно намного выше этого.

PS Я полагаю, что видел подробное, вплоть до байтового объяснения структуры страницы данных в одной из книг «Внутренние компоненты SQL Server» Калена Делани, но это было почти 10 лет назад, поэтому, пожалуйста, извините, что не помню больше подробностей. Кроме того, структура страницы имеет тенденцию меняться от версии к версии.

Роджер Вольф
источник
1
Нет. Uniquifier добавляется только к дублирующимся ключевым строкам. Первая строка каждого уникального значения ключа не включает в себя дополнительный 4-байтовый унивификатор.
Соломон Руцкий,
@srutzky, видимо, ты прав. Никогда не думал, что SQL Server будет разрешать ключи переменной ширины. Это безобразно Эффективно, да, но безобразно.
Роджер Вольф