Почему пространство данных таблицы может в 4 раза превышать размер необработанных данных?

18

У меня есть таблица с 490 M строк и 55 ГБ табличного пространства, так что около 167 байтов на строку. Таблица имеет три столбца: a VARCHAR(100), a DATETIME2(0)и a SMALLINT. Средняя длина текста в VARCHARполе составляет около 21,5, поэтому необработанные данные должны составлять около 32 байтов в строке: 22 + 2 для VARCHAR, 6 для DATETIME2и 2 для 16-разрядного целого числа.

Обратите внимание, что пространство выше - это только данные, а не индексы. Я использую значение, указанное в разделе Свойства | Хранение Генерал | Пространство данных.

Конечно, должны быть некоторые издержки, но 135 байтов на строку кажутся большими, особенно для большой таблицы. Почему это может быть? Кто-нибудь еще видел подобные множители? Какие факторы могут повлиять на количество необходимого дополнительного пространства?

Для сравнения я попытался создать таблицу с двумя INTполями и 1 M строк. Требуемое пространство данных составляло 16,4 МБ: 17 байтов на строку по сравнению с 8 байтами необработанных данных. В другой тестовой таблице с символом INTи, VARCHAR(100)заполненным тем же текстом, что и в реальной таблице, используется 39 байтов на строку (44 тыс. Строк), где я ожидал 28 плюс.

Таким образом, производственный стол имеет значительно больше накладных расходов. Это потому что оно больше? Я ожидал бы, что размеры индекса будут примерно N * log (N), но я не понимаю, почему пространство, необходимое для фактических данных, должно быть нелинейным.

Заранее спасибо за любые указатели!

РЕДАКТИРОВАТЬ:

Все поля перечислены NOT NULL. Реальная таблица имеет кластеризованный PK на VARCHARполе и DATETIME2поле в указанном порядке. Для двух тестов первым INTбыл (кластеризованный) PK.

Если это имеет значение: таблица представляет собой запись результатов пинга. Поля: URL, дата / время пинга и время ожидания в миллисекундах. Данные постоянно добавляются и никогда не обновляются, но данные периодически удаляются, чтобы сократить их до нескольких записей в час на URL.

РЕДАКТИРОВАТЬ:

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

Джон на все руки
источник

Ответы:

11

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

Всегда стоит проверять состояние фрагментации с помощью sys.dm_db_index_physical_stats в этих ситуациях.

Изменить: после обновления в комментариях

Средняя плотность страниц (до перестройки кластерного индекса) составляла 24%, что идеально соответствует исходному вопросу. Страницы были заполнены только на 1/4, поэтому общий размер в 4 раза превышал размер необработанных данных.

Марк Стори-Смит
источник
7

Структуры на диске имеют накладные расходы:

  • заголовок строки
  • нулевое растровое изображение + указатель
  • смещения столбцов переменной длины
  • указатели версии строки (необязательно)
  • ...

Принимая 2 х 4 байта в столбцах, у вас есть

  • Заголовок строки 4 байта
  • 2-байтовый указатель на растровое изображение NULL
  • 8 байтов для 2 столбцов типа int
  • 3 байта NULL растровое изображение

Ух 17 байт!

Вы можете сделать то же самое для вашей второй тестовой таблицы, которая имеет больше накладных расходов, чем ваша оригинальная таблица:

  • 2 байта для подсчета столбцов переменной длины
  • 2 байта на столбец переменной длины

Почему разница? Кроме того (я не буду ссылаться на них)

  • Вы когда-нибудь перестраивали индексы для их дефрагментации?
  • удаляет не вернуть место
  • страницы данных будут разделены, если вы вставите в середину
  • обновления могут вызывать прямые указатели (оставляет пробел)
  • переполнение строки
  • удален столбец varchar без перестроения индекса или DBCC CLEANTABLE
  • куча или таблица (куча не имеет кластеризованного индекса = записи разбросаны по всему)
  • Уровень изоляции RCSI (дополнительные 14 байтов на строку)
  • конечные пробелы (по умолчанию SET ANSI_PADDING включено) в varchar. Используйте DATALENGTH для проверки, а не LEN
  • Запустите sp_spaceused с @updateusage = 'true'
  • ...

Смотрите это: SQL Server: Как создать таблицу, которая занимает одну страницу размером 8 КБ?

От ТАК:

ГБН
источник
Пример столбца int размером 2x4 байта не на 100% корректен. У вас будет 4-байтовый заголовок строки (2 байта состояния и 2 байта для размера данных фиксированной длины). Тогда у вас будет 2x4 байта для данных. Два байта для количества столбцов и один байт для нулевого растрового изображения, что дает общую длину записи 15 байтов, а не 17.
Марк С. Расмуссен
@ Марк С. Расмуссен: Откуда вы берете «2 байта для фиксированного размера данных»? MSDN? И нулевое растровое изображение всегда составляет 3 байта: sqlskills.com/blogs/paul/post/… + msdn.microsoft.com/en-us/library/ms178085%28v=sql.90%29.aspx
gbn
Вау, отличная деталь! Я учел поле длины VARCHARs в моей оценке выше, но не количество столбцов. В этой таблице нет полей NULLable (это должно было упоминаться), она все еще выделяет для них байты?
Джон на все руки
Восстанавливая индексы влияют на данные части пространства требуется? Возможно, перестроить кластерный индекс будет. Вставки случаются посередине, очень много, хотя, если бы я поменял местами порядок кластеризации, это остановило бы. Большинство остальных не должны применяться в этом случае, но это отличная ссылка для общего случая. Я проверю ваши ссылки. Хорошая вещь!
Джон на все руки
1
@gbn 2 байта для размера данных фиксированной длины являются частью 4-байтового заголовка строки, который вы упомянули. Это указатель, указывающий на конец части фиксированной длины данных / начала столбца / нулевого растрового изображения. Растровое изображение NULL не всегда три байта. Если вы включите число столбцов, то это будет минимум три байта, но может быть и больше - я разделил растровое изображение и количество столбцов в своем описании. Кроме того, битовое изображение NULL присутствует не всегда , хотя оно будет в этом случае.
Марк С. Расмуссен
5

Изменились ли типы данных с течением времени? Были ли удалены столбцы переменной длины? Часто ли дефрагментировали индексы, но никогда не перестраивали? Было ли удалено много строк или было значительно обновлено много столбцов переменной длины? Хорошая дискуссия здесь .

Аарон Бертран
источник
Я на 97% уверен, что я не изменил тип данных и не удалил поле. Если бы я это сделал, было бы очень рано, когда в таблице было гораздо меньше строк. Нет никаких удалений или обновлений, данные только добавляются.
Джон на все руки
Исправление: есть в удалениях, и совсем немного. Таблица имеет значительный чистый рост, поэтому я предполагаю, что это пространство будет быстро использовано повторно.
Джон на все руки
При большом количестве удалений данные могут или не могут быть повторно использованы. Что такое ключ кластеризации таблицы? Вставки в середине таблицы или в конце?
Мрденни
Кластерный ключ является составным, на VARCHARи DATETIME2полях, в таком порядке. Вставки будут равномерно распределены по первому полю. Для второго поля новые значения и всегда будут больше любых существующих.
Джон на все руки