19.12.25

Любопытный случай… ограничения размера строки в 8060 байт

Автор: Paul Randal, The Curious Case of… the 8060-byte row size limit

На прошлой неделе мне задали вопрос о том, почему существует ограничение в 8060 байт на размер строки и почему страницы файлов данных иногда показывают более 8060 байт свободного места при просмотре с помощью DBCC PAGE.

Прежде чем объяснять, позвольте мне прояснить, что ограничение в 8060 байт касается только той части записи, которая хранится на странице данных или индекса в единице распределения «внутристроковых данных» (in-row data). Запись может иметь множество столбцов LOB, которые хранятся вне строки в единице распределения «данных LOB» (например, столбцы varchar(max) или FILESTREAM), и/или не-LOB столбцов переменной длины, которые были вытеснены из строки для хранения в единице распределения «переполнения строки» (например, столбцы char(1-8000), nchar(1-4000) или sqlvariant). Таким образом, фактический размер строки практически не ограничен.

Я мог бы перефразировать исходный вопрос так: учитывая, что размер страницы составляет 8192 байта, и только 96 байт используется для заголовка страницы, почему размер внутристроковой записи ограничен 8060 байтами? Куда деваются остальные 36 байт доступного пространства?

Ответ заключается в том, что строка размером в 8060 байт на самом деле может занимать гораздо больше места.

  1. Во-первых, существует 2-байтовое смещение строки, которое хранится в конце страницы и указывает механизму хранения, где строка начинается на странице. Таким образом, используется 8062 байта, остаётся 34 байта.
  2. Во-вторых, если строка находится на странице данных кучи и является перенаправленной записью (forwarded record), к концу записи добавляется 10-байтовый обратный указатель (на исходное местоположение записи). Он используется на случай, если запись снова придётся переместить, и тогда заглушка перенаправления (в исходном местоположении записи) будет обновлена, чтобы указывать на новое местоположение перенаправленной записи. Таким образом, в этом случае используется 8072 байта, остаётся 24 байта.
  3. В-третьих, если у строки есть предыдущая версия, потому что она была обновлена после включения изоляции моментальных снимков для зафиксированных операций чтения (read committed snapshot isolation) или изоляции моментальных снимков (snapshot isolation) для базы данных, к концу записи будет добавлен 14-байтовый тег версионности, содержащий местоположение предыдущей версии записи в хранилище версий в tempdb и метку времени версионности, когда была создана текущая версия записи. Таким образом, в этом случае используется 8076 байт, остаётся 20 байт.
  4. В-четвёртых, запись может быть перенаправленной записью кучи, которая также имеет предыдущую версию, поэтому имеет и 10-байтовый обратный указатель, и 14-байтовый тег версионности на конце. Таким образом, в этом случае используется 8086 байт, остаётся 10 байт.

И эти 10 байт доступны для использования любыми будущими функциями. Возможно, некоторые из них используются версионностью на странице, которую реализует ускоренное восстановление базы данных (Accelerated Database Recovery) в SQL Server 2019, но я ещё не изучал это подробно.

Вот и всё: запись размером в 8060 байт может фактически требовать 8086 байт или более физического хранилища из-за различных функций механизма хранения.



Комментариев нет:

Отправить комментарий