У меня есть довольно большая таблица с одним из столбцов, представляющих собой данные XML, со средним размером записи XML ~ 15 килобайт. Все остальные столбцы - это обычные числа, большие буквы, идентификаторы GUID и т. Д. Чтобы получить конкретные числа, скажем, таблица имеет миллион строк и размер ~ 15 ГБ.
Я заметил, что эта таблица очень медленно выбирает данные, если я хочу выбрать все столбцы. Когда я делаю
SELECT TOP 1000 * FROM TABLE
считывание данных с диска занимает около 20-25 секунд, хотя я не определяю порядок результатов. Я запускаю запрос с холодным кешем (т.е. после DBCC DROPCLEANBUFFERS
). Вот результаты статистики IO:
Сканирование 1, логическое чтение 364, физическое чтение 24, чтение с опережением 7191, логическое чтение lob 7924, физическое чтение за 1690, чтение с опережением чтение 3968.
Захватывает ~ 15 МБ данных. План выполнения показывает Clustered Index Scan, как я ожидал.
На диске нет ввода-вывода, кроме моих запросов; Я также проверил, что фрагментация кластерного индекса близка к 0%. Это SATA-накопитель потребительского уровня, однако я все же думаю, что SQL Server сможет сканировать таблицу быстрее, чем ~ 100-150 МБ / мин.
Наличие поля XML приводит к тому, что большая часть данных таблицы располагается на страницах LOB_DATA (на самом деле ~ 90% страниц таблицы составляют LOB_DATA).
Я предполагаю, что мой вопрос - правильно ли я считаю, что страницы LOB_DATA могут вызывать медленное сканирование не только из-за их размера, но и потому, что SQL Server не может эффективно сканировать кластерный индекс, когда в таблице много страниц LOB_DATA?
Еще более широко - разумно ли иметь такую структуру таблицы / шаблон данных? В рекомендациях по использованию Filestream обычно указываются гораздо большие размеры полей, поэтому я не хочу идти по этому пути. Я действительно не нашел никакой хорошей информации об этом конкретном сценарии.
Я думал о сжатии XML, но это должно быть сделано на клиенте или с SQLCLR и потребовало бы довольно много работы для реализации в системе.
Я попробовал сжатие, и поскольку XML-файлы сильно избыточны, я могу (в приложении ac #) сжать XML с 20 КБ до ~ 2,5 КБ и сохранить его в столбце VARBINARY, предотвращая использование страниц данных больших объектов. Это ускоряет SELECT в 20 раз в моих тестах.
источник
SELECT *
это не проблема, если вам нужны данные XML. Это проблема только в том случае, если вам не нужны данные XML. В таком случае зачем замедлять запрос, чтобы получить данные, которые вы не используете? Я спросил об обновлениях в XML, задаваясь вопросом, не сообщалось ли точно о фрагментации на страницах больших объектов. Вот почему я спросил в своем ответе, как именно вы определили, что кластерный индекс не был фрагментирован? Можете ли вы предоставить команду, которую вы выполнили? И вы сделали полный REBUILD по кластерному индексу? (продолжение)Ответы:
Простое наличие столбца XML в таблице не имеет такого эффекта. Это наличие XML - данных , которые, при определенных условиях , вызывает некоторую часть данных подряд, чтобы хранить от строки, на страницах LOB_DATA. И хотя один (или, может быть, несколько ;-) может утверждать, что да, в
XML
столбце подразумевается, что действительно будут данные XML, не гарантируется, что данные XML нужно будет хранить вне строки: если строка в значительной степени уже не заполнена вне каких-либо данных XML небольшие документы (до 8000 байт) могут помещаться в строку и никогда не переходить на страницу LOB_DATA.Сканирование относится к просмотру всех строк. Конечно, когда читается страница данных, считываются все данные в строке , даже если вы выбрали подмножество столбцов. Разница с данными больших объектов заключается в том, что если вы не выберете этот столбец, данные вне строки не будут прочитаны. Следовательно, не совсем справедливо делать вывод о том, насколько эффективно SQL Server может сканировать этот кластеризованный индекс, поскольку вы точно не тестировали его (или тестировали половину его). Вы выбрали все столбцы, включая столбец XML, и, как вы упомянули, именно там находится большая часть данных.
Итак, мы уже знаем, что
SELECT TOP 1000 *
тест не просто читал серию 8k страниц данных, все подряд, а вместо этого переходил в другие места в каждой строке . Точная структура этих больших данных может варьироваться в зависимости от их размера. Основываясь на исследованиях, показанных здесь ( каков размер указателя большого объекта для (MAX) типов, таких как Varchar, Varbinary, Etc? ), Существует два типа размещения больших объектов вне строки:Одна из этих двух ситуаций возникает каждый раз, когда вы извлекаете данные больших объектов размером более 8000 байт или просто не помещаются в строку. Я разместил тестовый скрипт на PasteBin.com (скрипт T-SQL для проверки выделения и чтения больших объектов ), который показывает 3 типа выделения больших объектов (в зависимости от размера данных), а также влияние каждого из них на логические и физическое чтение. В вашем случае, если данные XML на самом деле меньше 42 000 байт на строку, то ни один из них (или их очень мало) не должен быть в наименее эффективной структуре TEXT_TREE.
Если вы хотите проверить, насколько быстро SQL Server может сканировать этот кластеризованный индекс, сделайте,
SELECT TOP 1000
но укажите один или несколько столбцов, не включая этот столбец XML. Как это влияет на ваши результаты? Это должно быть немного быстрее.Учитывая, что у нас есть неполное описание фактической структуры таблицы и шаблона данных, любой ответ может быть неоптимальным в зависимости от того, что это за недостающие детали. Имея это в виду, я бы сказал, что нет ничего явно необоснованного в вашей структуре таблицы или шаблоне данных.
Это ускорило выбор всех столбцов или даже только данных XML (теперь они есть
VARBINARY
), но на самом деле это повреждает запросы, которые не выбирают данные «XML». Предполагая, что у вас есть около 50 байтов в других столбцах и значениеFILLFACTOR
100, тогда:Сжатие
XML
не требуется: для 15 КБ данных требуется 2 страницы LOB_DATA, для чего требуется 2 указателя для встроенного корня. Первый указатель имеет длину 24 байта, а второй - 12, что составляет 36 байтов, хранимых в строке для данных XML. Общий размер строки составляет 86 байт, и вы можете разместить около 93 из этих строк на странице данных размером 8060 байт. Следовательно, 1 миллион строк требует 10 753 страниц данных.Пользовательское сжатие: 2,5 тыс.
VARBINARY
Данных помещается в строку. Общий размер строки составляет 2610 (2,5 * 1024 = 2560) байт, и вы можете разместить только 3 из этих строк на странице данных размером 8060 байт. Следовательно, 1 миллион строк требует 333,334 страниц данных.Следовательно, реализация пользовательского сжатия приводит к увеличению в 30 раз количества страниц данных для кластерного индекса. Значение, все запросы с использованием индекса кластерного сканирования теперь около 322,500 больше страниц данных для чтения. Пожалуйста, ознакомьтесь с подробным разделом ниже, чтобы узнать о дополнительных последствиях такого типа сжатия.
Я бы предостерег от проведения любого рефакторинга, основанного на производительности
SELECT TOP 1000 *
. Это вряд ли будет запрос, который приложение даже выдаст, и его не следует использовать в качестве единственной основы для потенциально ненужной оптимизации.Для получения более подробной информации и дополнительных тестов см. Раздел ниже.
На этот Вопрос нельзя дать однозначного ответа, но мы можем, по крайней мере, добиться определенного прогресса и предложить дополнительные исследования, которые помогут приблизить нас к выяснению точной проблемы (в идеале на основе фактических данных).
Что мы знаем:
XML
колонку и несколько других столбцов типов:INT
,BIGINT
,UNIQUEIDENTIFIER
, « и т.д.»XML
«размер» столбца составляет в среднем около 15 тыс.DBCC DROPCLEANBUFFERS
для выполнения следующего запроса требуется 20-25 секунд:SELECT TOP 1000 * FROM TABLE
Что мы думаем, мы знаем:
Сжатие XML может помочь. Как именно вы будете делать сжатие в .NET? Через классы GZipStream или DeflateStream ? Это не вариант с нулевой стоимостью. Это, безусловно, будет сжимать некоторые данные на большой процент, но это также потребует больше ЦП, поскольку вам потребуется дополнительный процесс для сжатия / распаковки данных каждый раз. Этот план также полностью лишит вас возможности:
.nodes
,.value
,.query
и.modify
функции XML.индексировать данные XML.
Помните (поскольку вы упомянули, что XML «сильно избыточен»),
XML
тип данных уже оптимизирован, поскольку он сохраняет имена элементов и атрибутов в словаре, присваивая целочисленный идентификатор индекса каждому элементу, а затем использует этот целочисленный идентификатор. во всем документе (следовательно, он не повторяет полное имя для каждого использования и не повторяет его снова как закрывающий тег для элементов). В реальных данных также удалены посторонние пробелы. Вот почему извлеченные XML-документы не сохраняют свою первоначальную структуру и почему извлекаются пустые элементы,<element />
даже если они были введены как<element></element>
, Таким образом, любой выигрыш от сжатия с помощью GZip (или чего-либо еще) будет найден только путем сжатия значений элемента и / или атрибута, который представляет собой гораздо меньшую площадь поверхности, которую можно улучшить, чем многие ожидают, и, скорее всего, не стоит потери Возможности, как указано выше.Также имейте в виду, что сжатие данных XML и сохранение
VARBINARY(MAX)
результата не исключит доступ к LOB, а только уменьшит его. В зависимости от размера оставшихся данных в строке сжатое значение может помещаться в строку или для него все еще могут потребоваться страницы больших объектов.Этой информации, хотя и полезной, недостаточно. Есть много факторов, которые влияют на производительность запросов, поэтому нам нужно гораздо более детальное представление о том, что происходит.
Что мы не знаем, но нужно:
SELECT *
значение? Это шаблон, который вы используете в коде. Если так, то почему?SELECT TOP 1000 XmlColumn FROM TABLE;
:?Сколько из 20-25 секунд, необходимых для возврата этих 1000 строк, связано с сетевыми факторами (получение данных по сети), а также с клиентскими факторами (что составляет примерно 15 МБ плюс остальные XML-данные в сетку в SSMS или, возможно, сохранение на диск)?
Выделяя эти два аспекта операции, иногда можно просто не возвращать данные. Теперь можно подумать, что нужно выбрать временную таблицу или переменную таблицы, но это будет просто ввести несколько новых переменных (например, дисковый ввод-вывод для
tempdb
, запись журнала транзакций, возможный автоматический рост данных tempdb и / или файла журнала, необходимость пространство в буферном пуле и т. д.). Все эти новые факторы могут фактически увеличить время запроса. Вместо этого я обычно сохраняю столбцы в переменные (соответствующего типа данных; нетSQL_VARIANT
), которые перезаписываются каждой новой строкой (т.е.SELECT @Column1 = tab.Column1,...
).ОДНАКО , как было отмечено @PaulWhite в этом DBA.StackExchange Q & A, логическое чтение отличается при доступе к одним и тем же данным большого объекта, с дополнительным исследованием моего собственного, опубликованного на PasteBin ( сценарий T-SQL для проверки различных сценариев для чтения большого объекта ) , LOBs не обращались последовательно между
SELECT
,SELECT INTO
,SELECT @XmlVariable = XmlColumn
,SELECT @XmlVariable = XmlColumn.query(N'/')
, иSELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Таким образом, наши возможности здесь немного более ограничены, но вот что можно сделать:Кроме того , можно выполнить запрос через Sqlcmd.exe и направить выход идти в никуда через:
-o NUL:
.Каков фактический размер данных для возвращаемых
XML
столбцов ? Средний размер этого столбца по всей таблице на самом деле не имеет значения, если строки «TOP 1000» содержат непропорционально большую часть общих данных. Если вы хотите узнать о ТОП 1000 строк, посмотрите на эти строки. Пожалуйста, запустите следующее:XML
CREATE TABLE
отчет, включая все индексы.Каковы точные результаты следующего запроса:
ОБНОВИТЬ
Мне пришло в голову, что я должен попытаться воспроизвести этот сценарий, чтобы увидеть, испытываю ли я подобное поведение. Итак, я создал таблицу с несколькими столбцами (аналогично смутному описанию в вопросе), а затем заполнил ее 1 миллионом строк, и столбец XML содержит приблизительно 15 тыс. Данных на строку (см. Код ниже).
Я обнаружил, что выполнение
SELECT TOP 1000 * FROM TABLE
завершается за 8 секунд в первый раз, а затем через 2–4 секунды каждый раз (да, выполняетсяDBCC DROPCLEANBUFFERS
перед каждым запускомSELECT *
запроса). Мой ноутбук, которому несколько лет, не быстр: SQL Server 2012 SP2 Developer Edition, 64-битная, 6 ГБ оперативной памяти, двухъядерный 2,5 ГГц Core i5 и диск SATA 5400 об / мин. Я также использую SSMS 2014, SQL Server Express 2014, Chrome и некоторые другие.Основываясь на времени отклика моей системы, я повторю, что нам нужно больше информации (т.е. подробностей о таблице и данных, результатах предлагаемых тестов и т. Д.), Чтобы помочь сузить причину времени отклика в 20-25 секунд. что ты видишь.
И, поскольку мы хотим учесть время, затрачиваемое на чтение страниц без LOB, я выполнил следующий запрос, чтобы выбрать все, кроме столбца XML (один из тестов, которые я предложил выше). Это возвращается через 1,5 секунды довольно последовательно.
Заключение (на данный момент)
Исходя из моей попытки воссоздать ваш сценарий, я не думаю, что мы можем указать ни на диск SATA, ни на непоследовательный ввод / вывод в качестве основной причины 20 - 25 секунд, особенно потому, что мы все еще не знаю, как быстро возвращается запрос, не включая столбец XML. И я не смог воспроизвести большое количество логических операций чтения (не больших объектов), которые вы показываете, но у меня есть ощущение, что мне нужно добавить больше данных в каждую строку в свете этого и утверждения:
Моя таблица содержит 1 миллион строк, каждая из которых содержит более 15 000 XML-данных, и
sys.dm_db_index_physical_stats
показывает, что существует 2 миллиона страниц LOB_DATA. Тогда оставшиеся 10% будут 222 тыс. Страниц данных IN_ROW, но у меня их всего 11 630. Итак, еще раз, нам нужно больше информации относительно фактической схемы таблицы и фактических данных.источник
Да, чтение данных LOB, не сохраненных в строке, приводит к случайному вводу-выводу вместо последовательного ввода-вывода. Метрика производительности диска, используемая здесь, чтобы понять, почему она быстрая или медленная, это IOPS с произвольным чтением.
Данные больших объектов хранятся в древовидной структуре, где страница данных в кластерном индексе указывает на страницу данных больших объектов с корневой структурой больших объектов, которая, в свою очередь, указывает на фактические данные больших объектов. При обходе корневых узлов в кластерном индексе SQL Server может получать данные в строке только при последовательном чтении. Чтобы получить данные больших объектов, SQL Server должен пойти куда-нибудь на диск.
Я полагаю, что если вы перейдете на SSD-диск, вы не сильно пострадаете от этого, поскольку случайный IOPS для SSD намного выше, чем для вращающегося диска.
Да, это может быть. Зависит от того, что этот стол делает для вас.
Обычно проблемы с производительностью XML в SQL Server возникают, когда вы хотите использовать T-SQL для запросов в XML, и даже больше, когда вы хотите использовать значения из XML в предикате в предложении where или объединении. Если это так, вы можете вместо этого взглянуть на продвижение свойств или выборочные индексы XML или перепроектировать структуры таблиц, вместо этого разбивая XML на таблицы.
Я сделал это однажды в продукте чуть более 10 лет назад и с тех пор сожалею об этом. Я действительно упустил возможность работать с данными с помощью T-SQL, поэтому я не рекомендовал бы это никому, если этого можно избежать.
источник