Подолайте МЕРЕЖНЕ ПРИЄДНАННЯ (INDEX SCAN) з явним єдиним значенням KEY на ІНОЗЕМНОГО КЛЮЧА


9

Додано 7/11 . Проблема в тупикових ситуаціях виникає через сканування індексу під час MERGE JOIN. У цьому випадку транзакція намагається отримати блокування S на весь індекс у батьківській таблиці FK, але раніше інша транзакція ставить X lock на ключове значення індексу.

Дозвольте розпочати з невеликого прикладу (TSQL2012 DB із 70-461 курси, що використовується):

CREATE TABLE [Sales].[Orders](
[orderid] [int] IDENTITY(1,1) NOT NULL,
[custid] [int] NULL,
[empid] [int] NOT NULL,
[shipperid] [int] NOT NULL,
... )

Стовпці [custid], [empid], [shipperid]є основними параметрами [Sales].[Customers], [HR].[Employees], [Sales].[Shippers]відповідно. У кожному випадку у нас є кластерний індекс на згаданому стовпчику в батьківській таблиці.

ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([custid]) REFERENCES [Sales].[Customers] ([custid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([empid]) REFERENCES [HR].[Employees] ([empid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Shippers] FOREIGN KEY([shipperid])REFERENCES [Sales].[Shippers] ([shipperid])

Я намагаюся отримати INSERT [Sales].[Orders] SELECT ... FROMіншу таблицю під назвою, [Sales].[OrdersCache]яка має таку ж структуру, як і інші [Sales].[Orders]зовнішні ключі. Інша річ, можливо, важливо згадати таблицю [Sales].[OrdersCache]- це кластерний індекс.

CREATE CLUSTERED INDEX idx_c_OrdersCache ON Sales.OrdersCache ( custid, empid )

Як і очікувалося, коли я намагаюся вставити невеликі обсяги даних, LOOP JOIN прекрасно спрацьовує, щоб індекс шукати на зовнішніх клавішах.

З великими обсягами даних MERGE JOIN використовується оптимізатором запитів як найефективніший спосіб підтримати передбачуваний ключ у запиті.

І це не має нічого спільного, крім використання OPTION (LOOP JOIN) у нашому випадку із зовнішніми ключами або INNER LOOP JOIN у явному випадку JOIN.

Нижче наведено запит, який я намагаюся запустити у своєму оточенні:

INSERT Sales.Orders (
        custid, empid, shipperid, ... )
SELECT  custid, empid, 2, ...
FROM Sales.OrdersCache

Дивлячись на план, ми бачимо, що всі 3 ключі передбачення підтверджені MERGE JOIN. Для мене це не підходящий спосіб, оскільки він використовує INDEX SCAN з блокуванням цілого індексу. МЕРЕ ПРИЄДНАЙТЕ під час перевірки зовнішніх ключів

Використання OPTION (LOOP JOIN) не підходить, оскільки коштує майже на 15% дорожче, ніж МЕРГОВЕ ПРИЄДНАННЯ (я думаю, що регрес буде більшим із збільшенням обсягів даних).

У операторі SELECT ви можете бачити одне значення shipperidатрибута для всього вставленого набору. На мою думку, повинен бути спосіб зробити фазу перевірки для вставленого набору швидшою, принаймні, для атрибута, що не змінюється. Щось на зразок:

  • зробіть LOOP JOIN, MERGE JOIN, HASH JOIN, якщо у нас є невизначений підмножина для валідації JOIN
  • якщо є лише одне явне значення перевіреного стовпця, ми робимо перевірку лише один раз (INDEX SEEK).

Чи існує якась загальна модель, що перевершує ситуацію, що вища, використовуючи структуру коду, додаткові об'єкти DDL тощо?

Додано 20/07. Рішення. Оптимізатор запитів вже робить оптимізацію перевірки "єдиного ключа - зовнішнього ключа" за допомогою MERGE JOIN. І робить лише для Sales.Shippers таблиці, залишаючи LOOP JOIN для чергового приєднання до запиту одночасно. Оскільки в батьківській таблиці є декілька рядків Оптимізатор запитів використовує алгоритм сортування-об’єднання об'єднань і порівнює кожен рядок у внутрішній таблиці з батьківською таблицею лише один раз. Отже, це відповідь на моє запитання, чи є якийсь конкретний механізм для ефективної обробки окремих значень у наборі під час перевірки одного ключа. Це не дуже ідеальне рішення, але саме таким чином SQL Server оптимізує справу.

Дослідження про вплив на ефективність показало, що в моєму випадку оператор вставки MERGE JOIN та LOOP JOIN став приблизно рівним 750 одночасно вставлених рядків із наступною перевагою MERGE JOIN (у часовому ресурсі процесора). Тож використання OPTION (LOOP JOIN) - це відповідне рішення для мого бізнес-процесу.

Відповіді:


8

Використання OPTION (LOOP JOIN) не підходить, оскільки коштує майже на 15% дорожче, ніж MERGE JOIN

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

На мій досвід, оптимізатор перемикається з циклів приєднується до об'єднання об'єднань для перевірки зовнішнього ключа занадто рано. У всіх, крім найбільших операцій, я вважав, що з'єднання циклів є кращим. І під "великим" я маю на увазі принаймні десятки мільйонів рядків, звичайно, не тисячу або близько того, що ви вказуєте в коментарях до свого питання.

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

Це в принципі має сенс, але сьогодні немає такої логіки в рамках логіки побудови планів, яку використовує перевірка закордонних ключів. Поточна логіка навмисно дуже загальна і ортогональна для більш широкого запиту; конкретні оптимізації ускладнюють тестування та збільшують ймовірність виникнення помилок у крайньому випадку.

Чи існує якась загальна модель, що перевершує ситуацію, що вища, використовуючи структуру коду, додаткові об'єкти DDL тощо?

Не те, що мені відомо. Ризик тупикового з злиттям перевірка зовнішнього ключа добре відома , з найбільш широко використовуваним обхідним шляхом є OPTION (LOOP JOIN)підказкою. Неможливо уникнути прийняття загальних блокувань під час перевірки зовнішнього ключа, оскільки вони потрібні для коректності , навіть при рівнях ізоляції версійної версії.

Немає кращої загальної відповіді (ніж натяк на приєднання до циклу), якщо ви хочете зберегти можливість для кількох одночасних процесів додавати рядки до батьківської та дочірньої таблиць транзакційно. Але, якщо ви готові серіалізувати модифікації даних (не читає), використання sp_getapplock - це надійний і простий прийом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.