Дивний план запитів при використанні АБО в пункті JOIN - Постійне сканування для кожного рядка таблиці


10

Я намагаюся скласти зразок плану запитів, щоб показати, чому UNIONing двох наборів результатів може бути кращим, ніж використання АБО в пункті JOIN. План запитів, який я написав, мене наткнув. Я використовую базу даних StackOverflow з некластеризованим індексом на Users.Reputation.

Зображення плану запиту Запит є

CREATE NONCLUSTERED INDEX IX_NC_REPUTATION ON dbo.USERS(Reputation)
SELECT DISTINCT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts  
    ON Users.Id = Posts.OwnerUserId
    OR Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

План запитів знаходиться за адресою https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE , тривалість запиту для мене становить 4:37 хв., 26612 рядків повернуто.

Раніше я не бачив, щоб цей стиль постійного сканування створювався з існуючої таблиці - я не знайомий з тим, чому існує постійне сканування для кожного окремого ряду, коли постійне сканування зазвичай використовується для одного рядка, введеного користувачем наприклад SELECT GETDATE (). Чому він використовується тут? Я дуже вдячний, щоб прочитати цей план запитів.

Якщо я розділяю це АБО на UNION, він створює стандартний план, який працює за 12 секунд з тими ж поверненими 26612 рядками.

SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON Users.Id = Posts.OwnerUserId
WHERE Users.Reputation = 5
UNION 
SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON  Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

Я трактую цей план так:

  • Отримайте всі 41782500 рядків із публікацій (фактична кількість рядків відповідає скануванню CI у повідомленнях)
  • Кожні 41782500 рядків у повідомленнях:
    • Виробляють скаляри:
    • Expr1005: OwnerUserId
    • Expr1006: OwnerUserId
    • Expr1004: Статичне значення 62
    • Expr1008: LastEditorUserId
    • Expr1009: LastEditorUserId
    • Expr1007: Статичне значення 62
  • У конкатенаті:
    • Exp1010: Якщо Expr1005 (OwnerUserId) не є нульовим, використовуйте інше використання Expr1008 (LastEditorUserID)
    • Expr1011: Якщо Expr1006 (OwnerUserId) не є нульовим, використовуйте це, інакше використовуйте Expr1009 (LastEditorUserId)
    • Expr1012: Якщо значення Expr1004 (62) недійсне, використовуйте Expr1007 (62)
  • У скалярі обчислень: Я не знаю, що робить амперсанд.
    • Expr1013: 4 [і?] 62 (Expr1012) = 4, а OwnerUserId IS NULL (NULL = Expr1010)
    • Expr1014: 4 [і?] 62 (Expr1012)
    • Expr1015: 16 та 62 (Expr1012)
  • У порядку замовлення за сортуванням:
    • Expr1013 Опис
    • Expr1014 Asc
    • Expr1010 Вих
    • Expr1015 Опис
  • У інтервалі злиття було видалено Expr1013 та Expr1015 (це входи, але не виходи)
  • В індексі пошуку нижче вкладених циклів приєднуйтесь, він використовує Expr1010 та Expr1011 як предикати для пошуку, але я не розумію, яким чином він має доступ до них, коли він не зробив вкладений цикл з IX_NC_REPUTATION до піддерева, що містить Expr1010 та Expr1011 .
  • Приєднання вкладених циклів повертає лише Users.ID, які мають відповідність у попередньому піддереві. Через натискання присудка повертаються всі рядки, повернені з індексу, шукані в IX_NC_REPUTATION.
  • Останній приєднаний цикл приєднання: для кожного запису повідомлень виведіть Users.Id, де відповідність знайдена в наборі даних нижче.

Чи спробували ви з підзапросом EXITS або підзапитом? SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND ( EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.OwnerUserId) OR EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ

один підзапит:SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id IN (Posts.OwnerUserId, Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ

Відповіді:


10

План схожий на той, про який я детальніше розглядаю тут .

PostsТаблиця сканується.

Для кожного ряду він витягує OwnerUserIdі LastEditorUserId. Це аналогічно тому, як UNPIVOTпрацює. На плані нижче ви бачите одного оператора постійного сканування, який створює два вихідні рядки для кожного вхідного рядка.

SELECT *
FROM dbo.Posts
UNPIVOT (X FOR U IN (OwnerUserId,LastEditorUserId)) Unpvt

У цьому випадку план є дещо складнішим, оскільки семантика orполягає в тому, що якщо обидва значення стовпців однакові, то з об'єднання слід випускати один ряд Users(а не два)

Потім вони ставляться через інтервал злиття, так що у випадку, якщо значення є однаковими, діапазон згортається і Usersпроти нього виконується лише одне прагнення - інакше проти нього виконуються два пошуки .

Значення 62- це прапор, що означає, що пошук повинен бути рівним.

Що стосується

Я не розумію, яким чином він має доступ до них, коли він не зробив вкладений цикл з'єднання з IX_NC_REPUTATION в піддерево, що містить Expr1010 і Expr1011

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

введіть тут опис зображення

Перепишіть, що дає аналогічний план (хоча з інтервалом злиття, заміненим об'єднанням злиття), наведено нижче, якщо це допоможе.

SELECT DISTINCT D2.UserId
FROM   dbo.Posts p
       CROSS APPLY (SELECT Users.Id AS UserId
                    FROM   (SELECT p.OwnerUserId
                            UNION /*collapse duplicate to single row*/
                            SELECT p.LastEditorUserId) D1(UserId)
                           JOIN Users
                             ON Users.Id = D1.UserId) D2
OPTION (FORCE ORDER) 

введіть тут опис зображення

Залежно від того, які індекси доступні в Postsтаблиці, варіант цього запиту може бути більш ефективним, ніж пропоноване UNION ALLрішення. (копія бази даних у мене не має корисного індексу для цього, і пропоноване рішення робить два повних сканування Posts. Нижче це робиться в одному скануванні)

WITH Unpivoted AS
(
SELECT UserId
FROM dbo.Posts
UNPIVOT (UserId FOR U IN (OwnerUserId,LastEditorUserId)) Unpivoted
)
SELECT DISTINCT Users.Id
FROM dbo.Users INNER HASH JOIN Unpivoted
       ON  Users.Id = Unpivoted.UserId
WHERE Users.Reputation = 5

введіть тут опис зображення

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