Лучший способ измельчить данные XML в столбцы базы данных SQL Server - PullRequest
25 голосов
/ 14 сентября 2008

Каков наилучший способ разбить данные XML на различные столбцы базы данных? До сих пор я в основном использовал узлы и функции значений следующим образом:

INSERT INTO some_table (column1, column2, column3)
SELECT
Rows.n.value('(@column1)[1]', 'varchar(20)'),
Rows.n.value('(@column2)[1]', 'nvarchar(100)'),
Rows.n.value('(@column3)[1]', 'int'),
FROM @xml.nodes('//Rows') Rows(n)

Однако я считаю, что это происходит очень медленно даже для XML-данных среднего размера.

Ответы [ 8 ]

48 голосов
/ 12 января 2011

Наткнулся на этот вопрос, хотя у меня была очень похожая проблема, я выполнял запрос, обрабатывающий XML-файл объемом 7,5 МБ (~ около 10 000 узлов), в течение 3,5-4 часов, прежде чем окончательно сдаться.

Однако после небольшого исследования я обнаружил, что после ввода XML-кода с использованием схемы и создания XML-индекса (я бы вставил его в таблицу) тот же запрос был выполнен за ~ 0,04 мс.

Как это для улучшения производительности!

Код для создания схемы:

IF EXISTS ( SELECT * FROM sys.xml_schema_collections where [name] = 'MyXmlSchema')
DROP XML SCHEMA COLLECTION [MyXmlSchema]
GO

DECLARE @MySchema XML
SET @MySchema = 
(
    SELECT * FROM OPENROWSET
    (
        BULK 'C:\Path\To\Schema\MySchema.xsd', SINGLE_CLOB 
    ) AS xmlData
)

CREATE XML SCHEMA COLLECTION [MyXmlSchema] AS @MySchema 
GO

Код для создания таблицы с типизированным столбцом XML:

CREATE TABLE [dbo].[XmlFiles] (
    [Id] [uniqueidentifier] NOT NULL,

    -- Data from CV element 
    [Data] xml(CONTENT dbo.[MyXmlSchema]) NOT NULL,

CONSTRAINT [PK_XmlFiles] PRIMARY KEY NONCLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Код для создания индекса

CREATE PRIMARY XML INDEX PXML_Data
ON [dbo].[XmlFiles] (Data)

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

Также я получил бы ошибку:

XQuery [dbo.XmlFiles.Data.value()]: Cannot implicitly atomize or apply 'fn:data()' to complex content elements, found type 'xs:anyType' within inferred type 'element({http://www.mynamespace.fake/schemas}:SequenceNumber,xs:anyType) ?'.

если я попытался перейти выше узла, выбранного с помощью функции узлов. Э.Г.

SELECT
    ,C.value('CVElementId[1]', 'INT') AS [CVElementId]
    ,C.value('../SequenceNumber[1]', 'INT') AS [Level]
FROM 
    [dbo].[XmlFiles]
CROSS APPLY
    [Data].nodes('/CVSet/Level/CVElement') AS T(C)

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

SELECT
    ,C.value('CVElementId[1]', 'INT') AS [CVElementId]
    ,B.value('SequenceNumber[1]', 'INT') AS [Level]
FROM 
    [dbo].[XmlFiles]
CROSS APPLY
    [Data].nodes('/CVSet/Level') AS T(B)
OUTER APPLY
    B.nodes ('CVElement') AS S(C)

Надеюсь, это кому-нибудь поможет, ведь это был мой день.

5 голосов
/ 16 августа 2013

в моем случае я использую SQL 2005 SP2 (9.0).

Единственное, что помогло, это добавление OPTION (OPTIMIZE FOR (@your_xml_var = NULL)). Объяснение по ссылке ниже.

Пример:

INSERT INTO @tbl (Tbl_ID, Name, Value, ParamData)
SELECT     1,
    tbl.cols.value('name[1]', 'nvarchar(255)'),
    tbl.cols.value('value[1]', 'nvarchar(255)'),
    tbl.cols.query('./paramdata[1]')
FROM @xml.nodes('//root') as tbl(cols) OPTION ( OPTIMIZE FOR ( @xml = NULL ) )

https://connect.microsoft.com/SQLServer/feedback/details/562092/an-insert-statement-using-xml-nodes-is-very-very-very-slow-in-sql2008-sp1

3 голосов
/ 21 марта 2012

У нас была похожая проблема здесь. Наш DBA (SP, вы человек) посмотрел на мой код, немного изменил синтаксис, и мы получили ожидаемую скорость Это было необычно, потому что мой выбор из XML был достаточно быстрым, но вставка была слишком медленной. Поэтому попробуйте этот синтаксис вместо:

INSERT INTO some_table (column1, column2, column3)
    SELECT 
        Rows.n.value(N'(@column1/text())[1]', 'varchar(20)'), 
        Rows.n.value(N'(@column2/text())[1]', 'nvarchar(100)'), 
        Rows.n.value(N'(@column3/text())[1]', 'int')
    FROM @xml.nodes('//Rows') Rows(n) 

Таким образом, указание параметра text () действительно влияет на производительность. Взял нашу вставку из 2К строк из «Я, наверное, написал неправильно - позвольте мне остановить это» примерно до 3 секунд. Это в 2 раза быстрее, чем необработанные операторы вставки, которые мы выполняли через соединение.

3 голосов
/ 14 сентября 2008

Я не уверен, какой метод самый лучший. Я использовал OPENXML конструкцию:

INSERT INTO Test
SELECT Id, Data 
FROM OPENXML (@XmlDocument, '/Root/blah',2)
WITH (Id   int         '@ID',
      Data varchar(10) '@DATA')

Чтобы ускорить его, вы можете создавать XML-индексы. Вы можете установить индекс специально для значения оптимизации производительности функции. Также вы можете использовать типизированные столбцы XML, которые работают лучше.

2 голосов
/ 20 июня 2011

Я бы не стал утверждать, что это «лучшее» решение, но для этой цели я написал общую процедуру SQL CLR - она ​​принимает «табличную» структуру XML (такую, как возвращаемая FOR XML RAW) выводит набор результатов.

Это не требует какой-либо настройки / знания структуры «таблицы» в Xml и оказывается чрезвычайно быстрым / эффективным (хотя это не было целью проектирования). Я просто уничтожил 25-мегабайтную (нетипизированную) переменную XML менее чем за 20 секунд, получив 25 000 строк довольно широкой таблицы.

Надеюсь, это кому-нибудь поможет: http://architectshack.com/ClrXmlShredder.ashx

0 голосов
/ 10 марта 2009

Мое текущее решение для больших наборов XML (> 500 узлов) состоит в том, чтобы использовать групповое копирование SQL (System.Data.SqlClient.SqlBulkCopy), используя DataSet для загрузки XML в память, а затем передать таблицу в SqlBulkCopy (определяя Помогает схема XML).

Очевидно, что есть подводные камни, такие как ненужное использование DataSet и загрузка всего документа в память. Я хотел бы пойти дальше и реализовать свой собственный IDataReader для обхода метода DataSet, но в настоящее время DataSet «достаточно хорош» для этой работы.

По сути, я никогда не находил решения моего первоначального вопроса относительно низкой производительности для такого типа измельчения XML. Он может быть медленным из-за того, что типизированные XML-запросы по своей сути медлительны или связаны с транзакциями и журналом SQL Server. Я предполагаю, что типизированные функции xml никогда не были предназначены для работы с нетривиальными размерами узлов.

Массовая загрузка XML: я попробовал это, и оно было быстрым, но у меня были проблемы с установкой COM-библиотеки DLL для работы в 64-битных средах, и я обычно стараюсь избегать COM-библиотеки, которые больше не поддерживаются.

sp_xml_preparedocument / OPENXML: я никогда не шел по этому пути, поэтому мне было бы интересно посмотреть, как он работает.

0 голосов
/ 10 марта 2009

Существует Массовая загрузка XML COM-объект ( .NET Пример )

С MSDN :

Вы можете вставить данные XML в SQL База данных сервера с использованием INSERT оператор и функция OPENXML; однако утилита Bulk Load обеспечивает лучшую производительность, когда вы нужно вставить большое количество XML данные.

0 голосов
/ 10 марта 2009

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

Мой тест содержит xml, в результате чего вставляется 244 записи, то есть 244 узла.

Код, который я переписываю, выполняется в среднем за 0,4 секунды. (10 тестов выполняются, разброс от .56 секунд до .344 секунд) Производительность не является основной причиной, по которой код переписывается, но новый код должен выступать так же или лучше. Этот старый код зацикливает узлы xml, вызывая sp для вставки один раз за цикл

Новый код - это всего лишь один sp; передать XML в; измельчите его.

Тесты с включенным новым кодом показывают, что новый sp занимает в среднем 3,7 секунды - почти в 10 раз медленнее.

Мой запрос находится в форме, размещенной в этом вопросе;

INSERT INTO some_table (column1, column2, column3)
SELECT
Rows.n.value('(@column1)[1]', 'varchar(20)'),
Rows.n.value('(@column2)[1]', 'nvarchar(100)'),
Rows.n.value('(@column3)[1]', 'int'),
FROM @xml.nodes('//Rows') Rows(n)

План выполнения, по-видимому, показывает, что для каждого столбца сервер sql выполняет отдельную «Табличную функцию [XMLReader]», возвращая все 244 строки, объединяя все обратно с помощью Nested Loops (Inner Join). Так что в моем случае, когда я делаю / вставляю примерно в 30 столбцов, это происходит по отдельности 30 раз.

Мне придётся сбросить этот код, я не думаю, что какая-либо оптимизация обойдёт этот метод по своей сути медленно. Я собираюсь попробовать метод sp_xml_preparedocument / OPENXML и посмотреть, будет ли производительность лучше для этого. Если кто-то сталкивается с этим вопросом из веб-поиска (как я это сделал), я настоятельно рекомендую вам провести некоторое тестирование производительности перед использованием такого типа измельчения в SQL Server

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...