簡體   English   中英

SQL Server 查詢間歇性性能問題

[英]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 秒后返回: 500 個參數的執行計划

這是 119 個參數的執行計划,10 分鍾后在我們的數據作業中超時: 119 個參數的執行計划

這是執行良好的 5 個參數的執行計划。 此查詢未在數據作業中明確執行,僅用於比較: 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM