Почему подзапрос использует параллелизм, а объединение - нет?

16

Почему сервер SQL использует параллелизм при выполнении этого запроса, который использует подзапрос, но не при использовании соединения? Объединенная версия запускается последовательно и занимает около 30 раз больше времени.

Регистрация версии: ~ 30 сек

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

Версия подзапроса: <1секунда

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

РЕДАКТИРОВАТЬ: XML-версии плана запроса:

JOIN версия

Версия SUBQUERY

Крис Л
источник

Ответы:

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' ) 
Мартин Смит
источник
Если к местоположению добавлено уникальное ограничение, становится очевидным, что «=» вернет ровно одну строку. Тогда в вашем примере планы запросов становятся идентичными (scans -> seeks): изменить таблицу Location добавить ограничение U_Location_Location unique nonclustered (Location);
Crokusek
@crokusek да. Понял, что вы имели в виду потом и удалил мой комментарий! Увеличивает ли это приблизительное число строк для версии соединения, чтобы оно совпадало с подзапросом? Не на ПК в данный момент, чтобы проверить?
Мартин Смит
@crokusek Да. выглядит как те же оценочные строки из соединения, что и для подзапроса в этом случае синглтона.
Мартин Смит
Да. Идентичный план запроса, обе оценки 8748, обе фактические 14276. Кстати, я думал, что предварительное вычисление locationId разрешит эту разницу, но это не так.
crokusek
1
@crokusek - я также добавлю уникальное ограничение на местоположение и другие подобные места в моей БД. Должен признать, я не осознавал, что это повлияло на оптимизацию запросов. Я думал, что это просто для обеспечения целостности данных. Спасибо за ваш вклад в этот вопрос.
Крис Л