为什么在中等负载下这些过程中只有一个版本会被阻塞?

sea*_*ean 7 sql-server query-performance

桌子:

CREATE TABLE [dbo].[Session] (
    [SessionId]          UNIQUEIDENTIFIER NOT NULL,
    [CID]                INT              NULL,
    [DEST]               VARCHAR (50)     NOT NULL,
    [EndUser]            VARCHAR (50)     NULL,
    [Platform]           VARCHAR (5)      CONSTRAINT [DF_Session_Platform] DEFAULT ('WEB') NOT NULL,
    [SessionState]       VARCHAR (50)     NOT NULL,
    [ServerName]         VARCHAR (180)    NOT NULL,
    [StartDate]          DATETIME         CONSTRAINT [DF_Session_StartDate] DEFAULT (getutcdate()) NOT NULL,
    [UpdateDate]         DATETIME         NULL,
    [EndDate]            DATETIME         NULL,
    [SessionData]        XML              NULL,
    [StartMinute]        AS               (dateadd(minute,datediff(minute,(0),[StartDate]),(0))) PERSISTED,
    [ActivityBreadcrumb] NVARCHAR (MAX)   NULL,
    CONSTRAINT [PK_Session] PRIMARY KEY CLUSTERED ([StartDate] ASC, [SessionId] ASC)
);
GO

CREATE NONCLUSTERED INDEX [IX_Session_StartDate_EndDate]
    ON [dbo].[Session]([StartDate] ASC, [EndDate] ASC);
GO

CREATE NONCLUSTERED INDEX [IX_Session_CID_DEST]
    ON [dbo].[Session]([CID] ASC, [DEST] ASC, [StartDate] ASC, [SessionId] ASC)
    INCLUDE([Platform]);
GO

CREATE PRIMARY XML INDEX [IX_Session_SessionData]
    ON [dbo].[Session]([SessionData])
    WITH (PAD_INDEX = OFF);
GO

CREATE XML INDEX [IX_Session_SessionData_PROPERTY]
    ON [dbo].[Session]([SessionData])
    USING XML INDEX [IX_Session_SessionData] FOR PROPERTY
    WITH (PAD_INDEX = OFF);
GO

CREATE NONCLUSTERED INDEX [IX_StartMinute]
    ON [dbo].[Session]([StartMinute] DESC, [EndDate] ASC);
GO

CREATE NONCLUSTERED INDEX [IX_Session_CID_SessionId]
    ON [dbo].[Session]([CID] ASC, [SessionId] ASC);
GO

CREATE STATISTICS [ST_Session_StartDate_SessionId_CID]
    ON [dbo].[Session]([StartDate], [SessionId], [CID]);
GO

CREATE STATISTICS [ST_Session_SessionId_CID_DEST]
    ON [dbo].[Session]([SessionId], [CID], [DEST]);
Run Code Online (Sandbox Code Playgroud)

导致问题的程序:

CREATE PROCEDURE [dbo].[SessionUpdate] 
    @SessionId uniqueidentifier, 
    @CID int,
    @DEST varchar(50),
    @EndUser varchar(50) = NULL,
    @Platform varchar(5) = NULL,
    @SessionState varchar(50),
    @ServerName varchar(180) ,
    @StatusDtm datetime,
    @EndDtm datetime,
    @Data xml,
    @ActivityBreadcrumb nvarchar(max) = NULL
AS
BEGIN

    SET NOCOUNT ON;

    MERGE AppLog.dbo.[Session] as target
    USING (Select @SessionId, @CID, @DEST, @EndUser, @Platform, @SessionState, @ServerName, @StatusDtm, @EndDtm, @Data, @ActivityBreadcrumb) as source (SessionId, CID, DEST, EndUser, [Platform], SessionState, ServerName, StatusDtm, EndDtm, Data, ActivityBreadcrumb)
    ON (target.SessionID = source.SessionId)
    WHEN MATCHED THEN
        Update Set target.UpdateDate = Source.StatusDtm,
            target.CID = coalesce(Source.CID, target.CID),
            target.Platform = coalesce(Source.Platform, target.Platform),
            target.SessionState = coalesce(Source.SessionState,Target.SessionState),
            target.SessionData = source.Data,
            target.ActivityBreadcrumb = source.ActivityBreadcrumb
    WHEN NOT MATCHED THEN
        Insert Values(Source.SessionId, Source.CID, Source.DEST, Source.EndUser, Source.[Platform], Source.SessionState, Source.ServerName, Source.StatusDtm, null, Source.EndDtm, source.Data, source.ActivityBreadcrumb);

END
Run Code Online (Sandbox Code Playgroud)

多年来,上述程序似乎运行良好。预定查询每晚删除一年前的数据。该应用程序在几天内经历了大量的处理,在此期间我们开始看到一些阻塞。在音量回落到正常水平甚至低于正常水平后,问题仍然存在。

10 到 20 分钟内没有将行插入表中。这是我们在那段时间查看阻塞查询时看到的: 阻塞查询

我们更改了索引、程序并更改了应用程序更新表的方式。问题不再发生。proc 现在只插入一行而不更新:

CREATE PROCEDURE [dbo].[SessionUpdate] 
    @SessionId uniqueidentifier, 
    @CID int,
    @DEST varchar(50),
    @EndUser varchar(50) = NULL,
    @Platform varchar(5) = NULL,
    @SessionState varchar(50),
    @ServerName varchar(180) ,
    @StatusDtm datetime,
    @EndDtm datetime,
    @Data xml,
    @ActivityBreadcrumb nvarchar(max) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO Session (SessionId, CID, DEST, EndUser, [Platform], SessionState, ServerName, StartDate, EndDate, SessionData, ActivityBreadcrumb)
    VALUES (@SessionId, @CID, @DEST, @EndUser, @Platform, @SessionState, @ServerName, @StatusDtm, @EndDtm, @Data, @ActivityBreadcrumb)
END
Run Code Online (Sandbox Code Playgroud)

我们怀疑发生阻塞的原因是由于统计数据变旧和程序重新编译。看起来所有的过程调用都在等待一个过程完成编译。这是锁数据:

<Database name="AppLog">
<Locks>
   <Lock request_mode="S" request_status="GRANT" request_count="1" />
</Locks>
<Objects>
   <Object name="SessionUpdate" schema_name="dbo">
      <Locks>
         <Lock resource_type="OBJECT" request_mode="Sch-S" request_status="GRANT" request_count="1" />
         <Lock resource_type="OBJECT.COMPILE" request_mode="X" request_status="WAIT" request_count="1" />
      </Locks>
   </Object>
</Objects>
</Database>
Run Code Online (Sandbox Code Playgroud)

这听起来正确吗?

我想更好地了解发生了什么,以避免再次出现这种情况。

例如,新过程是否不再重新编译,或者如果重新编译,为什么不再阻塞其他调用?

如果对新过程的调用确实导致它重新编译,并且需要 10-20 分钟才能完成,那么在应用程序由于超时关闭连接后插入是否仍然发生?

我们是否应该担心使用此表的其他查询?他们会因为更新统计数据而超时吗?

小智 0

不是完整的答案,但根据所提供的信息,不可能给出明确的答案。

我怀疑这是统计更新和重新编译的问题。至少这不是我的第一个猜测。我注意到的第一件事是 MERGE 连接target.SessionID = source.SessionId这意味着您需要在 SessionID 上有一个索引才能进行有效的查找。提供的 DDL 不包含第一列是 SessionID 的索引。在这种情况下,SQL Server 能做的最好的事情就是扫描索引。索引扫描对于并发来说是可怕的。它将阻止其他人访问该表。如果索引足够大,它也可以并行。在这种情况下,我们可以看到:大量的长时间锁定等待,并且阻塞会话具有闩锁 ACCESS_METHODS_DATASET_PARENT 指示并行工作。现在,统计信息更新也可以并行进行,但仅限于 SQL Server 2016 以及兼容级别 130 的数据库。来源

从 SQL Server 2016 (13.x) 开始,使用兼容性级别 130 时,将并行进行数据采样以构建统计信息,以提高统计信息收集的性能。每当表大小超过特定阈值时,查询优化器将使用并行样本统计信息。

您应该明确检查您的数据库处于什么兼容性级别。其次:默认情况下会对统计数据进行采样,除非您强制进行全面扫描。鉴于 MERGE 可能已经进行了索引扫描,如果额外的表采样会显着增加查询运行时间,我会感到惊讶。没有说这不可能发生。仅根据给定的信息,我预计 SessionID 上缺少的索引会导致问题。