Почему использование условия OR вместо Union вызвало проблему с производительностью

Привет, у меня есть запрос ниже в SP

@CrmContactId — это параметр SP.

Select distinct A.PolicyBusinessId, A.PolicyDetailId 
from TPolicyBusiness A
inner join TPolicyOwner B on a.PolicyDetailId=b.PolicyDetailId
Left Join TAdditionalOwner C on c.PolicyBusinessId=A.PolicyBusinessId
where (b.CRMContactId = @CRMContactId)  

мы внесли новое изменение и ввели условие ИЛИ

Select distinct A.PolicyBusinessId, A.PolicyDetailId 
from TPolicyBusiness A
inner join TPolicyOwner B on a.PolicyDetailId=b.PolicyDetailId
Left Join TAdditionalOwner C on c.PolicyBusinessId=A.PolicyBusinessId
where (b.CRMContactId = @CRMContactId OR C.CRMContactId = @CRMContactId) 

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

Приложение : План выполнения

но это изменение вызвало огромную проблему с производительностью на реальном сервере. TPolicyBusiness и TPolicyOwner — тяжелые таблицы с миллионами записей. Таблица TAdditionalOwner — это легкая таблица с небольшим количеством записей.

Чтобы решить эту проблему, вместо условия OR было Union all.

Select distinct A.PolicyBusinessId, A.PolicyDetailId 
        From
        (
            Select A.PolicyBusinessId, A.PolicyDetailId 
            from TPolicyBusiness A
            inner join TPolicyOwner B on a.PolicyDetailId=b.PolicyDetailId
            where b.CRMContactId = @CRMContactId
            union all
            Select A.PolicyBusinessId, A.PolicyDetailId 
            from TPolicyBusiness A
            Join TAdditionalOwner C on c.PolicyBusinessId=A.PolicyBusinessId
            where C.CRMContactId = @CRMContactId
        ) as A

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

Приложение План выполнения

Может кто-нибудь объяснить, почему введение OR вызвало проблему и почему использование Union лучше, чем OR в этом случае?


person Lamps    schedule 27.08.2014    source источник
comment
Вы смотрели на план выполнения? Изменяет ли это использование индексов, например?   -  person Raphaël Althaus    schedule 27.08.2014
comment
Не видя планов выполнения и вашего DDL, трудно сказать точно. Я предполагаю, что оптимизатор использует сканирование индекса при использовании ИЛИ, однако более узкая кардинальность каждого запроса при разделении означает, что при использовании UNION оптимизатор использует поиск индекса по одной или обеим таблицам в каждом запросе.   -  person GarethD    schedule 27.08.2014
comment
Хорошо, спасибо @RaphaëlAlthaus и GarethD. Я рассмотрю план выполнения и обновлю вопрос.   -  person Lamps    schedule 27.08.2014
comment
Я обновил вопрос с планами выполнения   -  person Lamps    schedule 27.08.2014
comment
Нет индекса, охватывающего TAdditionalOwner.PolicyBusinessId.   -  person simo.3792    schedule 28.08.2014


Ответы (4)


Использование UNION ALL для замены OR на самом деле является одним из хорошо известных приемов оптимизации. Лучшая ссылка и объяснение приведены в этой статье: Index Союз.

Суть в том, что OR предикаты, которые могут удовлетворяться двумя поисками по индексу, не могут быть надежно обнаружены оптимизатором запросов (причина в невозможности предсказать непересекающиеся наборы с двух сторон ИЛИ) . Таким образом, при выражении того же условия, что и UNION ALL, у оптимизатора не возникнет проблем с созданием плана, который выполняет два коротких поиска и объединяет результаты. Важно понимать, что a=1 or b=2 может отличаться от a=1 union all b=2, поскольку первый запрос возвращает строки, удовлетворяющие обоим условиям один раз, а второй — дважды. Когда вы пишете запрос как UNION ALL, вы сообщаете компилятору, что понимаете это и у вас нет с этим проблем.

Дополнительные сведения см. в разделе Как анализировать производительность SQL Server.

person Remus Rusanu    schedule 27.08.2014
comment
Разместите фактические планы, прикрепите файл XML .sqlplan, а не их изображение. - person Remus Rusanu; 27.08.2014

Query1, левое соединение и предложение where объединяются, чтобы означать, что таблица C эффективно игнорируется (также не упоминается в списке выбора), поэтому у вас в основном есть запрос на внутреннее соединение с двумя таблицами.

Запрос 2, левое соединение теперь является почти внутренним соединением, потому что на эту таблицу ссылаются в предложении where, не допуская также NULL из этой таблицы, но из-за ИЛИ все условия учитываются для результата. Следовательно, запрос на внутреннее соединение с тремя таблицами более или менее.

Запрос 3, вы упростили внутренний запрос UNION ALL до 2 простых внутренних соединений.

Но я подозреваю, что это будет больше, потому что and A.IndigoClientId=@TenantId указывает, что вы все равно не раскрыли полные запросы.

person Paul Maxwell    schedule 27.08.2014
comment
извините, я удалил это условие, и A.IndigoClientId=@TenantId подумал, что это бесполезно. Обновил мой вопрос. - person Lamps; 27.08.2014
comment
слишком поздно :) Я знаю, что он существовал сейчас. Основная проблема в том, что мы можем делать обоснованные предположения, но это все, что мы можем сделать. Вам нужно будет изучить фактические планы выполнения каждого запуска, чтобы действительно понять различия. - person Paul Maxwell; 27.08.2014
comment
Хорошо :) Я вернусь с планами выполнения. - person Lamps; 27.08.2014

JOIN to TAdditionalOwner использует

TPolicyBusiness.PolicyBusinessId = TAdditionalOwner.PolicyBusinessId 

где JOIN to TPolicyOwner использует

TPolicyBusiness.PolicyDetailId = TPolicyOwner.PolicyDetailId

Убедитесь, что существует соответствующий индекс для PolicyBusinessId.

В 2-way JOIN, который является частью UNION, меньшая таблица TAdditionalOwner будет оптимизирована, если в TPolicyBusiness нет индекса, на который она могла бы ссылаться, из-за небольшого размера. Сервер по-прежнему будет выполнять сканирование таблицы, но использовать значения из меньшей таблицы и проверять, находятся ли они где-нибудь в большой таблице. Если индекса нет, эта оптимизация довольно быстро исчезнет по мере роста маленькой таблицы.

Учитывая, что вы не имеете в виду ни B, ни C в SELECT, вы можете просто перейти к этому

SELECT DISTINCT A.PolicyBusinessId, A.PolicyDetailId 
FROM TPolicyBusiness A
LEFT JOIN TPolicyOwner B ON a.PolicyDetailId = b.PolicyDetailId AND b.CRMContactId = @CRMContactId 
LEFT JOIN TAdditionalOwner C on c.PolicyBusinessId = A.PolicyBusinessId AND C.CRMContactId = @CRMContactId

Таким образом, он будет ПРИСОЕДИНЯТЬСЯ к любой таблице, как и в вашем ОБЪЕДИНЕНИИ, но без выбора ВНЕШНЕГО выбора.

В любом случае убедитесь, что используемые поля проиндексированы.

person simo.3792    schedule 27.08.2014

Оператор объединения, это займет много времени, если данные огромны. Старайтесь избегать оператора объединения.

Пожалуйста, попробуйте ниже попробовать отфильтровать данные при присоединении. Это будет намного лучше.

Select distinct A.PolicyBusinessId, A.PolicyDetailId 
from TPolicyBusiness A
inner join TPolicyOwner B on a.PolicyDetailId=b.PolicyDetailId
and b.CRMContactId = @CRMContactId 
Left Join TAdditionalOwner C on c.PolicyBusinessId=A.PolicyBusinessId
where  (1=1 OR C.CRMContactId = @CRMContactId) 
person Adi    schedule 27.08.2014
comment
Это не совсем правильно. Если контакт только от TAdditionalOwner, то он не будет ПРИСОЕДИНЯТЬСЯ к TPolicyOwner.... и, следовательно, никакого результата. - person simo.3792; 27.08.2014