512 байти не використовуються зі сторінки даних 8 SQL Server на SQL Server


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 (Itzik Ben-Gan)":

SQL Server внутрішньо впорядковує дані у файл даних на сторінках. Сторінка є одиницею 8 Кб і належить до одного об’єкта; наприклад, до таблиці чи індексу. Сторінка - найменша одиниця читання та письма. Далі сторінки впорядковані в розширення. Обсяг складається з восьми послідовних сторінок. Сторінки в тій чи іншій мірі можуть належати одному об'єкту або декільком об'єктам. Якщо сторінки належать до декількох об'єктів, то міру називають змішаною мірою; якщо сторінки належать одному об’єкту, то обсяг називається рівномірним масштабом. SQL Server зберігає перші вісім сторінок об'єкта в змішаних формах. Коли об'єкт перевищує вісім сторінок, SQL Server виділяє додаткові рівномірні розширення для цього об’єкта. Завдяки цій організації дрібні об’єкти витрачають менше місця, а великі об'єкти менш фрагментовані.

Отже, у мене є перша змішана сторінка розміром 8 КБ, заповнена 7680 байтами (я вставив 30 разів 256 байт рядка розміру, тому 30 * 256 = 7680), щоб перевірити розмір, у якого я запустив розмір, перевірити проц - він повертає наступний результат

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 Кб сторінка призначена для сторінки Root 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 = 7 890 байт. Додайте ще 263, і ви перевищили обмеження на 8 кб сторінки, що змусить створити ще одну сторінку.


Посилання, яке ви надали, допомогло мені покращити уявлення про структуру сторінки, я шукав щось подібне до неї, але не зміг її знайти, Thax
Alphas Supremum

11

Незважаючи на те, що SQL Server використовує 8k (8192 байт) сторінок даних для зберігання 1 або більше рядків, кожна сторінка даних має деякий накладний (96 байт), а кожен рядок має деяку накладну (принаймні 9 байт). 8192 байт - це не суто дані.

Для більш детального вивчення того, як це працює, дивіться мою відповідь на наступне запитання DBA.SE:

SUM з DATALENGTH не відповідає розміру таблиці від 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 рядків заповнить купу, як очікувалося.

Однак жодна з цих деталей щодо Heaps безпосередньо не впливає на цей конкретний тест, оскільки обидві версії таблиці вміщують 30 рядків із лише 146 байтами. Це не вистачає місця для іншого ряду, незалежно від кластера чи купи.

Зверніть увагу, що цей тест досить простий. Обчислення фактичного розміру рядка може бути дуже складним залежно від різних факторів, таких як: SPARSEстиснення даних, LOB-дані тощо.


Щоб переглянути деталі сторінки даних, використовуйте наступний запит:

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 рядків насправді не вкладаються на одну сторінку, оскільки ОП ведеться повірити будь-яким тестовим процесом, який використовує ОП. Але цей тестовий процес неправильний. На сторінці розміщується всього 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" Калена Делані, але це було майже 10 років тому, тож, вибачте, не пам'ятаю більше деталей. Крім того, структура сторінки має тенденцію змінюватися від версії до версії.


1
Ні. Унікалізатор додається лише для дублювання рядків ключів. Перший рядок кожного унікального значення ключа не включає додаткові 4-байтові уніфікатори.
Соломон Руцький

@srutzky, мабуть, ти маєш рацію. Ніколи не думав, що SQL Server дозволить клавіші змінної ширини. Це некрасиво. Ефективна, так, але потворна.
Роджер Вольф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.