20.8.26

Разница между сжатием строк и сжатием страниц

Автор: Brent Ozar, Database Animations: The Difference Between Row Compression and Page Compression

В чём разница между сжатием строк (row compression) и сжатием страниц (page compression) в SQL Server, и когда имеет смысл применять каждое из них?

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

Чтобы увидеть это более подробно, давайте запустим одну из моих Database Animations.

Как работает сжатие строк

Начнём с рассмотрения кластерного индекса таблицы Users из базы данных Stack Overflow, которую вы так хорошо знаете и любите. Эта таблица содержит столбцы фиксированной длины, такие как:

  • Id: INT, 4 байта
  • Reputation: INT, 4 байта
  • Age: INT, 4 байта
  • CreationDate: DATETIME, 8 байт

Однако многим строкам не нужны полные 4 или 8 байт для хранения данных. У большинства людей репутация (особенно у вас) — очень маленькое число, поэтому ей не нужно все 4 байта хранения. Поле Age раньше заполнялось, но теперь по соображениям безопасности оно равно NULL, поэтому в него вообще ничего не попадает. Если мы включим сжатие строк (перестроив кластерный индекс с включённым сжатием строк), посмотрим, как это повлияет на хранение:

При сжатии строк SQL Server автоматически использует минимально возможный объём пространства для хранения соответствующих данных. Чем больше тип данных фиксированной длины используется в вашей модели данных и чем меньше ваши фактические данные, тем больше будет экономия от сжатия. Я представляю сжатие строк как устранение «технического долга» людей, которые говорили: «ДАВАЙТЕ ИСПОЛЬЗОВАТЬ БОЛЬШИЕ ТИПЫ ДАННЫХ НА ВСЯКИЙ СЛУЧАЙ!»

По большей части экономия от сжатия строк влияет только на типы данных фиксированной длины. Если вы используете VARCHAR(4000) для хранения почтового индекса, вы не увидите здесь никакой экономии, потому что SQL Server по умолчанию хранит только фактическую длину данных, а не все 4000 байт. Будем честны — именно здесь те, кто любит большие типы данных для строк, действительно отрывались по полной, и их чрезмерное усердие в выборе больших чисел на самом деле никогда не вредило вам с точки зрения занимаемого места.

Существует один пограничный случай, когда сжатие строк действительно помогает типам данных переменной длины: когда вы храните значения VARCHAR в типах данных NVARCHAR. Подробнее об этом пограничном случае читайте в статье Which Should You Use: VARCHAR or NVARCHAR? Мне не хочется раскрывать спойлеры, но я чувствую, что должен это сделать здесь, потому что большинство опытных архитекторов делают неправильный выбор: вам следует использовать NVARCHAR, даже если вы храните данные VARCHAR, и включать сжатие строк.

Как работает сжатие страниц

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

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

Продолжая наш пример с кластерным индексом Users, давайте подумаем о столбце Location, хранящемся на этой странице. (Чтобы упростить схему, я показываю только содержимое столбца Location, но, конечно, все столбцы хранятся на этих 8-килобайтных страницах.) Скорее всего, у нас будут люди с одинаковыми (или по крайней мере похожими) местоположениями на любой данной 8-килобайтной странице, и именно здесь дедупликация данных начинает экономить место:

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

Это особенно эффективно для некластерных индексов. Внимательные читатели посмотрят на приведённую выше анимацию и скажут: «Подождите, в кластерном индексе маловероятно, что у меня есть несколько строк с одинаковым местоположением. Кластерный индекс отсортирован по Id, а не по Location, и крайне маловероятно, что группа людей из одного места зарегистрируется в одно и то же время по порядку». Это мои любимые читатели, потому что они поднимают руки на занятиях, и я могу сказать: «Хороший вопрос!» — и я искренне радуюсь, потому что они усваивают информацию. Я знаю, вы этого не заметили, и это нормально — вы мой второй любимый тип читателей. В любом случае, на некластерных индексах, особенно на таком индексе, как Location, мы увидим множество дублирующихся значений, хранящихся по порядку на одной 8-килобайтной странице, и экономия от сжатия страниц здесь огромна.

У сжатия на уровне страниц есть несколько подводных камней. Во-первых — и мне не хочется повторяться, — но оно работает только на уровне 8-килобайтной страницы. Если ваши большие строки хранятся вне строки (off-row), сжатие страниц там не работает. Это особенно касается больших строковых данных, таких как JSON и XML, — очень многословных данных с большим количеством повторяющихся строк, именно там, где нам хотелось бы сжатия, но сжатие страниц просто ничего не делает с этим.

Во-вторых, словарь по-прежнему хранится только на уровне страницы. В идеальном мире у нас был бы словарь для содержимого всей таблицы, на уровне столбца, чтобы нам нужно было хранить термин «Хельсинки» только один раз и ссылаться на него везде с одним и тем же номером ссылки словаря, независимо от того, на какой 8-килобайтной странице хранится местоположение человека. Однако этого не происходит, потому что сжатие страниц работает на уровне страницы, а не на уровне столбца или таблицы.

Наконец, наихудший сценарий для сжатия страниц — это данные, которые часто обновляются на месте (на той же 8-килобайтной странице) и не поддаются сжатию. SQL Server будет тратить циклы CPU на проверку того, можно ли сжать какие-либо данные, и каждый раз не находить ничего.

Так что же следует использовать?

Используйте сжатие строк, если:

  • вы последовали моему совету и храните строковые данные в столбцах NVARCHAR, и/или
  • ваши архитекторы использовали большие типы данных переменной длины для хранения крошечных данных.

Используйте сжатие страниц, если:

  • на ваших 8-килобайтных страницах много избыточных данных, хранящихся в строке, на одних и тех же 8-килобайтных страницах (что для некластерных строковых индексов — это все они), и
  • у вас узкое место в чтении страниц данных с диска (PAGEIOLATCH), а не в CPU (SOS_SCHEDULER_YIELD).

Чтобы включить любой из видов сжатия, просто перестройте ваши индексы:

ALTER TABLE dbo.Users REBUILD WITH (DATA_COMPRESSION = PAGE);

Примечание: я создал анимации для этого поста с помощью Claude Code, но весь текст и демонстрации полностью написаны мной.

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

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