Этот запрос должен занять от 1 до 3 секунд на вашем компьютере с Windows 10 8Gb RAM. Это займет 15 секунд, потому что SQL Server выбирает плохой план выполнения. В этом случае основной причиной плохого плана выполнения являются неверные оценки, некоторые операторы в плане показывают большую разницу между оценочными и фактическими строками. Например, SQL Server оценил, что ему нужно выполнить только один поиск в кластерном индексе pk_customer, но он выполнил 16 522 поиска. То же самое происходит с [ConversationHistory]. [IX_ConversationID_CreatedOn_Activity_ByWhom] и с [Message]. [IX_ConversationID_ID_ArrivalDt_From_RStatus_Type.
Вот несколько советов, которым можно следовать, чтобы повысить производительность запроса:
- Обновить статистику
- Попробуйте
OPTION (HASH JOIN)
в конце запроса. Это может улучшить
производительность или это может замедлить его, это может даже вызвать запрос
ошибка.
- Сохранять данные табличных переменных во временных таблицах и использовать их в запросе. (
SELECT * INTO #temp_table FROM @table_variable
). Табличные переменные не имеют статистики, вызывающей неверные оценки.
- Определите первый оператор, где разница между оценочными и фактическими строками достаточно велика. Сплит запрос. Запрос1:
SELECT * INTO #operator_result FROM (query equivalent to operator)
. Query2: напишите запрос, используя #operator_result
. Поскольку #operator_result
является временной таблицей, SQL Server вынужден пересматривать оценки. В этом случае оператор-нарушитель является хеш-соответствием (внутренним соединением)
Есть и другие способы повысить производительность этого запроса:
- Избегайте поиска ключей. В кластерный индекс
Conversation.PK_dbo.Conversation
имеется 16 522 ключевых поиска. Этого можно избежать, создав соответствующий индекс покрытия. В этом случае индекс покрытия следующий:
DROP INDEX [IX_MailboxID] ON [dbo].[Conversation]
GO
CREATE INDEX IX_MailboxID ON [dbo].[Conversation](MailboxID)
INCLUDE (ArrivalDate, Status, ClosureDate, CustomerID, ConversationType)
- Разделить ИЛИ предикат на
UNION
или UNION ALL
. Например:
вместо:
SELECT *
FROM table
WHERE <predicate1> OR <predicate2>
использование:
SELECT *
FROM table
WHERE <predicate1>
UNION
SELECT *
FROM table
WHERE <predicate2>
Иногда это улучшает производительность.
Применять каждый совет по отдельности и измерять производительность.
РЕДАКТИРОВАТЬ: Вы можете попробовать следующее и посмотреть, если это улучшает производительность:
SELECT
C.ID,
C.MailboxID,
C.Status,
C.CustomerID,
Cust.FName,
Cust.LName,
C.ArrivalDate as ConversationArrivalDate,
C.[ClosureDate],
C.[ConversationType],
M.[From],
M.ArrivalDate as MessageArrivalDate,
M.ID as MessageID
FROM
@ListConversationType AS convType
INNER JOIN (
@ListMailboxId AS mailboxIds
INNER JOIN
[Mailbox] AS Mb ON (Mb.ID = mailboxIds.MailboxID)
INNER JOIN
[Conversation] as C
ON C.ID = Mb.ID
) ON convType.ID = C.[ConversationType]
INNER HASH JOIN
[Customer] AS Cust ON (Cust.ID = C.CustomerID)
INNER HASH JOIN
[ConversationHistory] AS CHis ON (CHis.ConversationID = C.ID)
INNER HASH JOIN
[Message] AS M ON (M.ConversationID = C.ID)
WHERE
Mb.CompanyID = @CompanyID
AND ((CHis.CreatedOn > @fromDate
AND CHis.CreatedOn < @toDate
AND CHis.Activity = 1
AND CHis.TagData = '3')
OR (M.ArrivalDate > @fromDate
AND M.ArrivalDate < @toDate))
А это:
SELECT
C.ID,
C.MailboxID,
C.Status,
C.CustomerID,
Cust.FName,
Cust.LName,
C.ArrivalDate as ConversationArrivalDate,
C.[ClosureDate],
C.[ConversationType],
M.[From],
M.ArrivalDate as MessageArrivalDate,
M.ID as MessageID
FROM
@ListConversationType AS convType
INNER JOIN (
@ListMailboxId AS mailboxIds
INNER JOIN
[Mailbox] AS Mb ON (Mb.ID = mailboxIds.MailboxID)
INNER JOIN
[Conversation] as C
ON C.ID = Mb.ID
) ON convType.ID = C.[ConversationType]
INNER MERGE JOIN
[Customer] AS Cust ON (Cust.ID = C.CustomerID)
INNER MERGE JOIN
[ConversationHistory] AS CHis ON (CHis.ConversationID = C.ID)
INNER MERGE JOIN
[Message] AS M ON (M.ConversationID = C.ID)
WHERE
Mb.CompanyID = @CompanyID
AND ((CHis.CreatedOn > @fromDate
AND CHis.CreatedOn < @toDate
AND CHis.Activity = 1
AND CHis.TagData = '3')
OR (M.ArrivalDate > @fromDate
AND M.ArrivalDate < @toDate))