Почему рекомендуется хранить большие двоичные объекты в отдельных таблицах SQL Server?

29

В этом ответе SO с высоким рейтингом рекомендуется помещать изображения в отдельные таблицы, даже если есть связь 1: 1 с другой таблицей:

Если вы решите поместить свои изображения в таблицу SQL Server, я настоятельно рекомендую использовать отдельную таблицу для хранения этих изображений - не сохраняйте фотографию сотрудника в таблице сотрудника - храните их в отдельной таблице. Таким образом, таблица «Сотрудник» может оставаться скудной, средней и очень эффективной, при условии, что вам не всегда нужно выбирать фотографию сотрудника, как часть ваших запросов.

Зачем? У меня сложилось впечатление, что SQL Server хранит в таблице только указатель на некоторую выделенную структуру данных BLOB , так зачем пытаться вручную создать еще один уровень косвенности? Действительно ли это значительно повышает производительность? Если да, то почему?

Heinzi
источник

Ответы:

15

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

Основная проблема, которую они вызывают (для меня), связана с индексацией. Используя XML с планами запросов, потому что у всех есть их, давайте сделаем таблицу:

SELECT TOP 1000
ID = IDENTITY(INT,1,1),
deq.query_plan
INTO dbo.index_test
FROM sys.dm_exec_cached_plans AS dec
CROSS APPLY sys.dm_exec_query_plan(dec.plan_handle) AS deq

ALTER TABLE dbo.index_test ADD CONSTRAINT pk_id PRIMARY KEY CLUSTERED (ID)

Это всего 1000 строк, но проверка по размеру ...

sp_BlitzIndex @DatabaseName = 'StackOverflow', @SchemaName = 'dbo', @TableName = 'index_test'

Это более 40 МБ только для 1000 строк. Предполагая, что вы добавляете 40 МБ каждые 1000 строк, это может стать довольно уродливым довольно быстро. Что происходит, когда вы попали в миллион строк? Это примерно 1 ТБ данных.

NUTS

Любые запросы, которым необходимо использовать ваш кластеризованный индекс, теперь должны считывать все эти BLOB-данные в память для уточнения: при обращении к столбцу BLOB-данных.

Можете ли вы придумать лучшие способы использования памяти SQL Server, чем хранение больших двоичных объектов? Потому что я уверен, что могу.

Расширяя его до некластеризованных индексов:

CREATE INDEX ix_noblob ON dbo.index_test (ID)

CREATE INDEX ix_returnoftheblob ON dbo.index_test (ID) INCLUDE (query_plan)

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

Если вы добавите его как INCLUDEDстолбец в некластеризованный индекс, чтобы избежать сценария поиска ключа, вы получите гигантские некластеризованные индексы:введите описание изображения здесь

Больше проблем они вызывают:

  • Если кто-то запускает SELECT *запрос, он получает все эти данные BLOB.
  • Они занимают место в резервных копиях и восстанавливают, замедляя их
  • Они замедляются DBCC CHECKDB, потому что я знаю, что вы проверяете на коррупцию, верно?
  • И если вы выполняете какие-либо операции с индексами, они также замедляют это.

Надеюсь это поможет!

Эрик Дарлинг
источник
7
Потому что пользователи обычно набирают SELECT *.
Брент Озар
Я думаю, что недостатки, о которых вы упомянули, являются частью того, почему он рекомендовал поместить фотографии в отдельную таблицу. Если я запускаю различные отчеты о пользователях, мне не нужен их файл изображения. Если я загружаю страницу профиля одного пользователя, тогда я присоединяюсь к таблице BLOB-объектов, верно? Я что-то здесь
упускаю
11

Насколько велики эти изображения и сколько вы ожидаете иметь? Хотя я в основном согласен с @sp_BlitzErik , я думаю, что есть некоторые сценарии, в которых это можно сделать, и поэтому было бы полезно получить более четкое представление о том, что на самом деле запрашивается здесь.

Вот некоторые варианты, которые следует учитывать, чтобы смягчить большинство негативных аспектов, указанных Эриком:

Обе эти опции предназначены для того, чтобы быть средним звеном между хранением больших двоичных объектов либо полностью в SQL Server, либо полностью снаружи (за исключением строкового столбца для сохранения пути). Они позволяют BLOB-объектам быть частью модели данных и участвовать в транзакциях, не тратя пространство в пуле буферов (то есть в памяти). Данные BLOB по-прежнему включены в резервные копии, что делает их занимающими больше места и занимающими больше времени для резервного копирования ичтобы восстановить. Тем не менее, я с трудом воспринимаю это как настоящий минус, учитывая, что если оно является частью приложения, то необходимо каким-то образом выполнить резервное копирование, а наличие только строкового столбца, содержащего путь, полностью отключается и позволяет файлам BLOB получить удаляется без указания на это в БД (т.е. неверные указатели / отсутствующие файлы). Это также позволяет «удалять» файлы внутри БД, но они все еще существуют в файловой системе, которую в конечном итоге необходимо будет очистить (например, от головной боли). Но если файлы ОГРОМНЫЕ, то, возможно, лучше оставить полностью вне SQL Server, за исключением столбца пути.

Это помогает с вопросом «внутри или снаружи», но не затрагивает вопрос «одна таблица против нескольких таблиц». Могу сказать, что помимо этого конкретного вопроса, безусловно, существуют веские случаи для разделения таблиц на группы столбцов на основе шаблонов использования. Часто, когда у одного есть 50 или более столбцов, есть те, к которым часто обращаются, а некоторые нет. Некоторые столбцы часто пишутся, а некоторые в основном читаются. Разделение часто обращающихся и редко используемых столбцов на несколько таблиц, имеющих отношение 1: 1, довольно часто полезно, потому что зачем тратить пространство в пуле буферов для данных, которые вы, вероятно, не используете (аналогично тому, как хранить большие изображения в обычном режиме).VARBINARY(MAX)столбцы это проблема)? Вы также повышаете производительность часто используемых столбцов, уменьшая размер строки и, следовательно, размещая больше строк на странице данных, делая чтение (как физическое, так и логическое) более эффективным. Конечно, вы также вносите некоторую неэффективность, когда вам нужно дублировать PK, и теперь иногда вам нужно объединить две таблицы, что также усложняет (хотя и незначительно) некоторые запросы.

Итак, есть несколько подходов, которые вы можете использовать, и что лучше всего зависит от вашей среды и того, что вы пытаетесь достичь.


У меня сложилось впечатление, что SQL Server хранит в таблице только указатель на некоторую выделенную структуру данных BLOB.

Не все так просто. Вы можете найти некоторую полезную информацию здесь, каков размер указателя большого объекта для (MAX) типов, таких как Varchar, Varbinary, Etc? , но основы таковы:

  • TEXT, NTEXTи IMAGEтипы данных (по умолчанию): 16-байтовый указатель
  • VARCHAR(MAX), NVARCHAR(MAX), VARBINARY(MAX)(По умолчанию):
    • Если данные могут поместиться в строке, то она будет размещена там
    • Если данные меньше, чем ок. 40 000 байт (в связанном сообщении блога верхний предел показывает 40000, но мое тестирование показало немного более высокое значение) И если в строке есть место для этой структуры, то будет от 1 до 5 прямых ссылок на страницы больших объектов, начиная с 24 байта для первой ссылки на первые 8000 байтов и увеличение на 12 байтов на каждую дополнительную ссылку для каждого дополнительного набора из 8000 байтов, максимум до 72 байтов.
    • Если данные превышают ок. 40 000 байт ИЛИ недостаточно места для хранения соответствующего количества прямых ссылок (например, в строке осталось только 40 байт, а для значения 20 000 байт требуется 3 ссылки, что составляет 24 байта для первых плюс 12 для двух дополнительных ссылок для 48 байтов всего необходимого пространства в строке), тогда будет просто 24-байтовый указатель на страницу текстового дерева, которая содержит ссылки на страницы больших объектов).
Соломон Руцкий
источник
7

Если по какой-либо причине данные должны храниться в SQL Server, я могу подумать о нескольких преимуществах их хранения в отдельной таблице. Некоторые из них более убедительны, чем другие.

  1. Помещение данных в отдельную таблицу означает, что вы можете хранить их в отдельной базе данных. Это может иметь преимущества для планового технического обслуживания. Например, вы можете работать DBCC CHECKDBтолько в той базе данных, которая содержит данные BLOB.

  2. Если вы не всегда помещаете более 8000 байтов в BLOB, возможно, он будет храниться в строке для некоторых строк. Вы можете не захотеть этого, потому что это замедлит запросы, которые обращаются к данным, используя кластеризованный индекс, даже если столбец не нужен для запроса. Помещение данных в отдельную таблицу устраняет этот риск.

  3. При хранении вне строки SQL Server использует указатель длиной до 24 байтов, чтобы указывать на новую страницу. Это занимает место и ограничивает общее количество столбцов BLOB, которые вы можете добавить в одну таблицу. Смотрите ответ srutzky для более подробной информации.

  4. Кластерный индекс columnstore не может быть определен для таблицы, содержащей столбец BLOB. Это ограничение было снято и будет удалено в SQL Server 2017.

  5. Если вы в конечном итоге решите, что данные должны быть перемещены за пределы SQL Server, это может быть проще сделать это изменение, если данные уже находятся в отдельной таблице.

Джо Оббиш
источник
1
Некоторые хорошие моменты здесь (+1). Но чтобы быть ясным относительно # 3 (re: 24-байтовый указатель для данных вне строки), это не всегда правильно. Я объясню (кратко) в нижней части моего ответа, как тип данных, размер значения и объем свободного места в строке определяют размер указателя.
Соломон Руцкий