Дуже дивна продуктивність з індексом XML


32

Моє запитання засноване на цьому: https://stackoverflow.com/q/35575990/5089204

Щоб дати відповідь там, я зробив наступний тестовий сценарій.

Тестовий сценарій

Спочатку я створюю тестову таблицю і заповнюю її 100 000 рядками. Випадкове число (0 до 1000) повинно вести до ~ 100 рядків для кожного випадкового числа. Це число вводиться у варшарський колон і як значення у ваш XML.

Тоді я виконую дзвінок, як ОП, там він потребує. Насправді я здійснюю дзвінки двічі: другий раз у зміненому порядку та з трохи зміненими парами пошуку та з "// item" замість повного шляху, щоб уникнути помилкових позитивних результатів через кешовані результати чи плани.

Потім я створюю індекс XML і роблю ті ж дзвінки

Тепер - що насправді мене здивувало! - .nodesз повним шляхом набагато повільніше , ніж раніше (9 секунд) , але .exist()це до половини секунди, з повним шляхом навіть приблизно до 0,10 сек. (хоча .nodes()з коротким шляхом краще, але все ще далеко позаду .exist())

Запитання:

Мої власні тести підсумовуються коротко: індекси XML можуть надзвичайно підірвати базу даних. Вони можуть надзвичайно прискорити роботи (наприклад, редагувати 2), але також можуть уповільнити ваші запити. Я хотів би зрозуміти, як вони працюють ... Коли потрібно створити індекс XML? Чому .nodes()з індексом може бути гірше, ніж без? Як можна уникнути негативного впливу?

CREATE TABLE #testTbl(ID INT IDENTITY PRIMARY KEY, SomeData VARCHAR(100),XmlColumn XML);
GO

DECLARE @RndNumber VARCHAR(100)=(SELECT CAST(CAST(RAND()*1000 AS INT) AS VARCHAR(100)));

INSERT INTO #testTbl VALUES('Data_' + @RndNumber,
'<error application="application" host="host" type="exception" message="message" >
  <serverVariables>
    <item name="name1">
      <value string="text" />
    </item>
    <item name="name2">
      <value string="text2" />
    </item>
    <item name="name3">
      <value string="text3" />
    </item>
    <item name="name4">
      <value string="text4" />
    </item>
    <item name="name5">
      <value string="My test ' +  @RndNumber + '" />
    </item>
    <item name="name6">
      <value string="text6" />
    </item>
    <item name="name7">
      <value string="text7" />
    </item>
  </serverVariables>
</error>');

GO 100000

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesFullPath_no_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistFullPath_no_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('//item[@name="name5" and value/@string="My test 500"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistShortPath_no_index;
GO

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('//item[@name="name5" and value/@string="My test 500"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesShortPath_no_index;
GO

CREATE PRIMARY XML INDEX PXML_test_XmlColum1 ON #testTbl(XmlColumn);
CREATE XML INDEX IXML_test_XmlColumn2 ON #testTbl(XmlColumn) USING XML INDEX PXML_test_XmlColum1 FOR PATH;
GO

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesFullPath_with_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistFullPath_with_index;
GO

DECLARE @d DATETIME=GETDATE();
SELECT * 
FROM #testTbl
WHERE XmlColumn.exist('//item[@name="name5" and value/@string="My test 500"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistShortPath_with_index;
GO

DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('//item[@name="name5" and value/@string="My test 500"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesShortPath_with_index;
GO

DROP TABLE #testTbl;

EDIT 1 - Результати

Це один результат, коли SQL Server 2012 локально встановлений на середньому ноутбуці. У цьому тесті я не зміг відтворити надзвичайно негативний вплив на NodesFullPath_with_index, хоча це повільніше, ніж без індексу ...

NodesFullPath_no_index    6.067
ExistFullPath_no_index    6.223
ExistShortPath_no_index   8.373
NodesShortPath_no_index   6.733

NodesFullPath_with_index  7.247
ExistFullPath_with_index  0.217
ExistShortPath_with_index 0.500
NodesShortPath_with_index 2.410

EDIT 2 Тест з більшим XML

Згідно з пропозицією TT, я використав XML вище, але скопіював item-узли, щоб досягти близько 450 елементів. Я дозволяю, щоб вузол звернення був дуже високо в XML (тому що я думаю, що .exist()він зупиниться при першому зверненні, тоді як .nodes()продовжиться)

Створення XML-індексу підірвало mdf-файл до ~ 21GB, ~ 18GB, схоже, належать до індексу (!!!)

NodesFullPath_no_index    3min44
ExistFullPath_no_index    3min39
ExistShortPath_no_index   3min49
NodesShortPath_no_index   4min00

NodesFullPath_with_index  8min20
ExistFullPath_with_index  8,5 seconds !!!
ExistShortPath_with_index 1min21
NodesShortPath_with_index 13min41 !!!

Відповіді:


33

Тут впевнене, що багато чого відбувається, тому нам просто доведеться побачити, куди це веде.

По-перше, різниця в часі між SQL Server 2012 та SQL Server 2014 пов'язана з новим оцінником кардинальності в SQL Server 2014. Ви можете використовувати прапор трасування в SQL Server 2014, щоб примусити старий оцінювач, і тоді ви побачите ті ж терміни характеристики в SQL Server 2014, як і в SQL Server 2012.

Порівняння з nodes()vs exist()не є справедливим, оскільки вони не повернуть однаковий результат, якщо в XML буде більше одного відповідного елемента для одного рядка. exist()поверне один рядок із базової таблиці незалежно, тоді як nodes()потенційно може дати вам більше одного повернутого для кожного рядка базової таблиці.
Ми знаємо дані, але SQL Server не має і повинен будувати план запитів, який враховує це.

Щоб зробити nodes()запит еквівалентним exist()запиту, ви можете зробити щось подібне.

SELECT testTbl.*
FROM testTbl
WHERE EXISTS (
             SELECT *
             FROM XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b)
             )

З таким запитом немає різниці між використанням nodes()або exist()тим, що SQL Server будує майже однаковий план для двох версій, що не використовують індекс, і точно такий же план, коли використовується індекс. Це справедливо як для SQL Server 2012, так і для SQL Server 2014.

Для мене в SQL Server 2012 запити без індексу XML займають 6 секунд, використовуючи модифіковану версію nodes()запиту вище. Немає різниці між використанням повного шляху або короткого шляху. З індексом XML повна версія шляху є найшвидшою і займає 5 мс, а використання короткого шляху займає близько 500 мс. Вивчення планів запитів скаже вам, чому є різниця, але коротка версія полягає в тому, що коли ви використовуєте короткий шлях, SQL Server шукає в індексі короткий шлях (діапазон шукає використання like) і повертає 700000 рядків, перш ніж відміняти рядки, які не відповідають за значенням. Використовуючи повний шлях, SQL Server може використовувати вираз шляху безпосередньо разом зі значенням вузла, щоб здійснити пошук і повернути лише 105 рядків з нуля для роботи.

Використовуючи SQL Server 2014 та новий оцінювач кардинальності, немає різниці в цих запитах при використанні індексу XML. Без використання індексу запити все ще займають стільки ж часу, але це 15 секунд. Очевидно, що тут немає покращення при використанні нових речей.

Не впевнений, чи я повністю втратив інформацію про те, про що йдеться у вашому питанні насправді, оскільки я змінив запити на еквівалент, але ось, на що я вважаю, це зараз.

Чому nodes()запит (оригінальна версія) з індексом XML розміщується значно повільніше, ніж коли індекс не використовується?

Ну, відповідь полягає в тому, що оптимізатор плану запитів SQL Server робить щось погано, і це вводить оператор шпулі. Я не знаю чому, але гарна новина полягає в тому, що його вже немає з новим оцінником кардинальності в SQL Server 2014.
Без індексів запит займає близько 7 секунд, незалежно від того, який оцінювач кардинальності використовується. З індексом потрібно 15 секунд зі старим оцінювачем (SQL Server 2012) і приблизно 2 секунди з новим оцінювачем (SQL Server 2014).

Примітка. Вищенаведені результати справедливі для ваших даних тесту. Можна змінити різницю, змінивши розмір, форму чи форму XML. Жоден спосіб точно не знати без тестування даних, які ви насправді є у таблицях.

Як працюють індекси XML

Індекси XML в SQL Server реалізовані як внутрішні таблиці. Первинний індекс XML створює таблицю з первинним ключем базової таблиці плюс стовпчик ідентифікатора вузла, загалом 12 стовпців. Він матиме один рядок на кожен, element/node/attribute etc.щоб таблиця, звичайно, була дійсно великою залежно від розміру збереженого XML. З первинним індексом XML на місці SQL Server може використовувати первинний ключ внутрішньої таблиці для пошуку XML-вузлів та значень для кожного рядка в базовій таблиці.

Вторинні індекси XML бувають трьох типів. Коли ви створюєте вторинний індекс XML, у внутрішній таблиці створюється некластеризований індекс, і залежно від того, який тип вторинного індексу ви створюєте, він матиме різні стовпці та порядки стовпців.

Від CREATE XML INDEX (Transact-SQL) :

VALUE
Створює вторинний індекс XML у стовпцях, де стовпчики ключів (значення вузла та шлях) основного індексу XML.

PATH
Створює вторинний індекс XML для стовпців, побудованих на значеннях шляхів та значеннях вузлів у первинному індексі XML. У вторинному індексі PATH значення шляху та вузла є ключовими стовпцями, які дозволяють ефективно шукати під час пошуку шляхів.

ВЛАСНІСТЬ
Створює вторинний індекс XML на стовпцях (PK, шлях та значення вузла) первинного індексу XML, де PK є первинним ключем базової таблиці.

Отже, коли ви створюєте індекс PATH, перший стовпець у цьому індексі є виразом шляху, а другий стовпець - значенням у цьому вузлі. Насправді шлях зберігається у певному стисненому форматі та перевертається. Те, що він зберігається зворотним, це робить його корисним у пошуку, використовуючи короткі вирази шляху. У вашому короткому випадку шляху ви шукали //item/value/@string, //item/@nameі //item. Оскільки шлях зберігається у зворотному стовпці, SQL Server може використовувати пошук діапазону з тим, like = '€€€€€€%де €€€€€€повернений шлях. Коли ви використовуєте повний шлях, немає ніяких причин використовувати, likeоскільки весь шлях закодований у стовпчик, і значення також може бути використане в предикаті пошуку.

Ваші запитання :

Коли слід створити індекс XML?

В крайньому випадку, якщо будь-коли. Краще розробити базу даних, щоб вам не довелося використовувати значення всередині XML для фільтрування в пункті де. Якщо ви заздалегідь знаєте, що вам потрібно це зробити, ви можете використовувати просування властивостей, щоб створити обчислений стовпець, який ви можете проіндексувати, якщо це потрібно. Оскільки SQL Server 2012 SP1, у вас також доступні вибіркові індекси XML. Робота поза сценою майже однакова як із звичайними індексами XML, лише ви вказуєте вираз шляху у визначенні індексу і індексуються лише ті вузли, які відповідають. Таким чином ви зможете зекономити багато місця.

Чому .nodes () з індексом може бути гіршим, ніж без?

Коли в таблиці створений індекс XML, SQL Server завжди буде використовувати цей індекс (внутрішні таблиці) для отримання даних. Це рішення робиться до того, як оптимізатор скаже, що швидко, а що не швидко. Вхід до оптимізатора перезаписується, тому він використовує внутрішні таблиці, після чого оптимізатор повинен зробити все можливе, як при звичайному запиті. Якщо не використовується індекс, натомість використовується пара функцій, що оцінюються за таблицею. Суть полягає в тому, що ви не можете сказати, що буде швидше без тестування.

Як можна уникнути негативного впливу?

Тестування


2
Ваші уявлення про різницю .nodes()та .exist()переконливі. Також той факт, що індекс full path searchшвидше, здається зрозумілим легко. Це означатиме: Якщо ви створюєте індекс XML, ви завжди повинні знати про негативний вплив будь-якого загального XPath ( //або *або ..або [filter]або що-небудь не просто звичайне Xpath ...). Насправді, ви повинні використовувати лише повний шлях - досить чудовий нічия ...
Шнуго,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.