[英]SQL Server Query intermittent performance Issue
最近,我們在 SQL Server (2016) 上遇到了特定查詢的性能問題。 我看到的問題是性能問題非常不一致,我不確定如何改進。
表詳情:
CREATE TABLE ContactRecord
(
ContactSeq BIGINT NOT NULL
, ApplicationCd VARCHAR(2) NOT NULL
, StartDt DATETIME2 NOT NULL
, EndDt DATETIME2
, EndStateCd VARCHAR(3)
, UserId VARCHAR(10)
, UserTypeCd VARCHAR(2)
, LineId VARCHAR(3)
, CallingLineId VARCHAR(20)
, DialledLineId VARCHAR(20)
, ChannelCd VARCHAR(2)
, SubChannelCd VARCHAR(2)
, ServicingAgentCd VARCHAR(7)
, EucCopyTimestamp VARCHAR(30)
, PRIMARY KEY (ContactSeq)
, FOREIGN KEY (ApplicationCd) REFERENCES ApplicationType(ApplicationCd)
, FOREIGN KEY (EndStateCd) REFERENCES EndStateType(EndStateCd)
, FOREIGN KEY (UserTypeCd) REFERENCES UserType(UserTypeCd)
)
CREATE TABLE TransactionRecord
(
TransactionSeq BIGINT NOT NULL
, ContactSeq BIGINT NOT NULL
, TransactionTypeCd VARCHAR(3) NOT NULL
, TransactionDt DATETIME2 NOT NULL
, PolicyId VARCHAR(10)
, ProductId VARCHAR(7)
, EucCopyTimestamp VARCHAR(30)
, Detail VARCHAR(1000)
, PRIMARY KEY (TransactionSeq)
, FOREIGN KEY (ContactSeq) REFERENCES ContactRecord(ContactSeq)
, FOREIGN KEY (TransactionTypeCd) REFERENCES TransactionType(TransactionTypeCd)
)
當前記錄數:
ContactRecord
2000萬TransactionRecord
9000萬我的查詢是:
select
UserId,
max(StartDt) as LastLoginDate
from
ContactRecord
where
ContactSeq in
(
select
ContactSeq
from
TransactionRecord
where
ContactSeq in
(
select
ContactSeq
from
ContactRecord
where
UserId in
(
'1234567890',
'1234567891' -- Etc.
)
)
and TransactionRecord.TransactionTypeCd not in
(
'122'
)
)
and ApplicationCd not in
(
'1',
'4',
'5'
)
group by
UserId;
現在查詢不是很好,可以使用連接來改進,但是它從根本上是有效的。 我遇到的問題是我們的數據作業需要輸入大約 7100 個用戶 ID。 然后將它們分成 500 組。對於每 500 組,這些將在此查詢的IN
子句中使用。 此查詢的前 14 次執行(在IN
子句中有 500 個項目)執行良好。 每個結果大約在 15-20 秒內返回。
問題在於最后一次執行此查詢時剩余的 100 次給予或接受。 它似乎永遠不會完成。 它只是掛起。 在我們的數據工作中,它在 10 分鍾后超時。 我不知道為什么。 我不是 SQL Server 的專家,所以我不確定如何調試它。 我已經獨立執行了每個子查詢,然后用返回的數據替換了子查詢的內容。 為每個子查詢執行此操作工作正常。
在這里真的很感激任何幫助,因為我不知道它是如何在大量參數下如此一致地工作的,但僅對一小部分參數不起作用。
編輯
我這里有三個執行計划的例子。 請注意,這些都是在測試服務器上執行的,並且幾乎都是立即執行的,因為此測試等效項的數據很少。
這是 500 個參數的執行計划,在生產中執行良好,大約 15-20 秒后返回:
這是 119 個參數的執行計划,10 分鍾后在我們的數據作業中超時:
這是執行良好的 5 個參數的執行計划。 此查詢未在數據作業中明確執行,僅用於比較:
在所有情況下,SSMS 都給出了以下警告:
/*
Missing Index Details from SQLQuery2.sql
The Query Processor estimates that implementing the following index could improve the query cost by 26.3459%.
*/
/*
USE [CloasIvr]
GO
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[TransactionRecord] ([TransactionTypeCd])
INCLUDE ([ContactSeq])
GO
*/
這是這個問題的根本原因嗎?
如果沒有看到發生了什么,就很難確切地知道發生了什么——尤其是那些失敗的。 “良好”運行的執行計划可能會有所幫助,但我們只是猜測運行不良時出了什么問題。
我最初的猜測(類似於我的評論)是它所期望的估計非常錯誤,並且它創建了一個非常糟糕的計划。
特別是您的 TransactionRecord 表, detail
列是 1000 個字符,可能會出現意外大量嵌套循環的大問題。
索引
我建議的第一件事是建立索引 - 特別是 a) 僅包含這些數據所需的數據子集,以及 b) 以有用的方式對它們進行排序。
我建議以下兩個索引似乎有幫助
CREATE INDEX IX_ContactRecord_User ON ContactRecord
(UserId, ContactSeq)
INCLUDE (ApplicationCD, Startdt);
CREATE INDEX IX_TransactionRecord_ContactSeq ON TransactionRecord
(ContactSeq, TransactionTypeCd);
這些都是“覆蓋索引”,以及以可以提供幫助的方式進行排序。 或者,您可以用稍微修改的版本替換第一個版本(在 ContactSeq 上先排序),但我認為上面的版本會更有用。
CREATE INDEX IX_ContactRecord_User2 ON ContactRecord
(ContactSeq)
INCLUDE (ApplicationCD, Startdt, UserId);
此外,關於 TransactionRecord 上的索引 - 如果這是使用該索引的唯一查詢,您可以通過創建以下索引來改進它
CREATE INDEX IX_TransactionRecord_ContactSeq_Filtered ON TransactionRecord
(ContactSeq, TransactionTypeCd)
WHERE (TransactionTypeCD <> '122');
以上是一個過濾索引,它與您語句的 WHERE 子句中指定的內容相匹配。 最重要的是,它已經 a) 刪除了類型 <> '122' 的記錄,並且 b) 已經對 ContactSeq 上的記錄進行了排序,因此可以輕松查找它們。
順便說一句-鑒於您原則上詢問在外鍵上添加索引-這些的使用實際上取決於您讀取數據的方式。 如果您只引用被引用的表(例如,您有一個狀態表的 FK,並且只用它來報告,英語,狀態),那么原始表的 Status_ID 上的索引將無濟於事。 另一方面,如果您想找到 Status_ID = 4 的所有行,那么它會有所幫助。
為了幫助理解索引,我強烈推薦 Brent Ozar 的How to think like an SQL Server Engine - 它真的幫助我理解索引在實踐中是如何工作的。
使用排序的臨時表
這可能會有所幫助,但不太可能是主要解決方法。 如果您將相關的 UserID 預加載到臨時表中(在 UserID 上有一個主鍵),那么它可能有助於相關的 JOIN。 您還可以更輕松地修改每次運行,而不必修改查詢的中間部分。
CREATE TABLE #Users (UserId VARCHAR(10) PRIMARY KEY);
INSERT INTO #Users (UserID) VALUES
('1234567890'),
('1234567891');
然后將查詢的中間部分替換為
where
ContactSeq in
(
select
ContactSeq
from
ContactRecord CR
INNER JOIN #Users U ON CR.UserID = U.UserID
)
and TransactionRecord.TransactionTypeCd not in
(
'122'
)
簡化查詢
我嘗試了簡化查詢,並得到了這個:
select CR.UserId,
max(CR.StartDt) as LastLoginDate
from ContactRecord CR
INNER JOIN TransactionRecord TR ON CR.ContactSeq = TR.ContactSeq
where TR.TransactionTypeCd not in ('122')
AND CR.ApplicationCd not in ('1', '4', '5')
AND CR.UserId in ('1234567890', '1234567891') -- etc
group by UserId;
或者(使用臨時表)
select CR.UserId,
max(CR.StartDt) as LastLoginDate
from ContactRecord CR
INNER JOIN #Users U ON CR.UserID = U.UserID
INNER JOIN TransactionRecord TR ON CR.ContactSeq = TR.ContactSeq
where TR.TransactionTypeCd not in ('122')
AND CR.ApplicationCd not in ('1', '4', '5')
group by UserId;
簡化查詢的優點之一是它還可以幫助 SQL Server 獲得良好的估計; 這反過來又有助於它獲得良好的執行計划。
當然,您需要測試上述在您的情況下返回的記錄是否完全相同 - 我沒有要測試的數據集,所以我不能 100% 確定這些簡化版本與原始版本匹配。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.