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 上缺少的索引会导致问题。