Чому підзапит використовує паралелізм, а приєднання не робить?


16

Чому SQL-сервер використовує паралелізм під час запуску цього запиту, який використовує підзапит, але він не застосовується при використанні приєднання? Версія для з'єднання працює серійно і займає приблизно 30 разів більше часу.

Версія для приєднання: ~ 30 сек

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

Версія підпиту: <1 секунда

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

EDIT: Xml версії плану запитів:

ПРИЄДНАЙТЕ версію

СУБКВЕРСЬКА версія

Відповіді:


12

Як уже зазначалося в коментарях, схоже, вам потрібно оновити статистику.

Орієнтовна кількість рядків, що виходять із з'єднання між ними, locationі testrunsсильно відрізняється між двома планами.

Приєднайтесь до кошторису плану: 1

План 1

Оцінка плану підзапиту: 8,748

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

Фактична кількість рядків, що виходять із з'єднання, становить 14 276.

Звичайно, абсолютно не має ніякого інтуїтивного сенсу, що версія приєднання повинна оцінювати, що 3 рядки повинні надходити з locationодного рядка, що з'єднався, тоді як підзапит запитує, що один із цих рядків отримає 8 748 з того самого з'єднання, але, тим не менш, я зміг щоб відтворити це.

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

Кардинальність теструнів є 26244. Якщо припустити, що він заповнений трьома різними ідентифікаторами розташування, то наступний запит оцінює це8,748 рядки будуть повернуті ( 26244/3)

declare @i int

SELECT *
FROM   testruns AS tr
WHERE  tr.location_id = @i

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

Оскільки SQL Server отримує кількість рядків, що виходять із цього об'єднання, так неправильно, всі інші оцінки рядків у плані об’єднання масово занижуються. Окрім того, що ви отримуєте послідовний план, запит також отримує недостатню підтримку пам’яті, а сорти та хеш-об’єднання приєднуються доtempdb .

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

CREATE TABLE location
  (
     id       INT CONSTRAINT locationpk PRIMARY KEY,
     location VARCHAR(MAX) /*From the separate filter think you are using max?*/
  )

/*Temporary ids these will be updated later*/
INSERT INTO location
VALUES      (101, 'Coventry'),
            (102, 'Nottingham'),
            (103, 'Derby')

CREATE TABLE testruns
  (
     location_id INT
  )

CREATE CLUSTERED INDEX IX ON testruns(location_id)

/*Add in 26244 rows of data split over three ids*/
INSERT INTO testruns
SELECT TOP (5984) 1
FROM   master..spt_values v1, master..spt_values v2
UNION ALL
SELECT TOP (5984) 2
FROM   master..spt_values v1, master..spt_values v2
UNION ALL
SELECT TOP (14276) 3
FROM   master..spt_values v1, master..spt_values v2

/*Create statistics. The location_id histograms don't intersect at all*/
UPDATE STATISTICS location(locationpk) WITH FULLSCAN;    
UPDATE STATISTICS testruns(IX) WITH FULLSCAN;

/* UPDATE location.id. Three row update is below recompile threshold*/
UPDATE location
SET    id = id - 100

Потім виконання наступних запитів дає однакову оціночну та фактичну невідповідність

SELECT *
FROM   testruns AS tr
WHERE  tr.location_id = (SELECT id
                         FROM   location
                         WHERE  location = 'Derby')

SELECT *
FROM   testruns AS tr
       JOIN location loc
         ON tr.location_id = loc.id
WHERE  loc.location = ( 'Derby' ) 

Якщо в місці розташування буде додано унікальне обмеження, то стане очевидним, що "=" поверне рівно один рядок. Тоді у вашому прикладі плани запитів стають однаковими (сканує -> шукає): змінити таблицю розташування додавати обмеження U_Location_Location унікальний некластеризований (Location);
crokusek

@crokusek так. Зрозумів, що ви мали на увазі після цього, і видалив мій коментар! Чи збільшить це збільшення передбачуваної кількості рядків для версії приєднання таким же, як і підзапит? Наразі не на ПК для тестування?
Мартін Сміт

@crokusek Так. виглядає так само, як оцінені рядки поза з’єднанням, як і для підзапиту в цьому випадку.
Мартін Сміт

Так. Ідентичний план запитів, обидві оцінки 8748, обидві фактичні 14276. Btw, я думав, що попередньо обчисливши locationId, це рішення вирішить, але це не так.
crokusek

1
@crokusek - я також додам унікальне обмеження до місця розташування та інших подібних місць у моїй БД. Мушу визнати, що я не розумів, що це вплинуло на оптимізацію запитів. Я думав, що це просто забезпечити цілісність даних. Дякуємо за Ваш внесок у це питання.
Кріс Л
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.