Я пытаюсь создать примерный план запроса, чтобы показать, почему UNIONing двух наборов результатов может быть лучше, чем использование OR в предложении 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) не равен NULL, используйте это, иначе используйте Expr1008 (LastEditorUserID)
- Expr1011: если Expr1006 (OwnerUserId) не равен NULL, используйте его, иначе используйте Expr1009 (LastEditorUserId)
- Expr1012: Если Expr1004 (62) является нулевым, используйте его, иначе используйте Expr1007 (62)
- В скаляре Compute: я не знаю, что делает амперсанд.
- Expr1013: 4 [и?] 62 (Expr1012) = 4 и OwnerUserId IS NULL (NULL = Expr1010)
- Expr1014: 4 [и?] 62 (Expr1012)
- Expr1015: 16 и 62 (Expr1012)
- В порядке сортировки по:
- Expr1013 Desc
- Expr1014 Asc
- Expr1010 Asc
- Expr1015 Desc
- В интервале слияния были удалены Expr1013 и Expr1015 (это входы, но не выходы)
- В поиске по индексу под объединением вложенных циклов он использует Expr1010 и Expr1011 в качестве предикатов поиска, но я не понимаю, каким образом он имеет к ним доступ, если не выполнил соединение с вложенным циклом из IX_NC_REPUTATION к поддереву, содержащему Expr1010 и Expr1011 ,
- Объединение Nested Loops возвращает только те Users.ID, которые соответствуют предыдущему поддереву. Из-за предиката pushdown возвращаются все строки, возвращаемые при поиске индекса по IX_NC_REPUTATION.
- Последнее объединение вложенных циклов: для каждой записи сообщения выведите Users.Id, где в указанном ниже наборе данных найдено совпадение.
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) ) ;
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) ) ;