如何有效诊断 SQL Server 2012 数据库插入中的 'wait operation timeout'?

How do I effectively diagnose a 'wait operation timeout' on SQL Server 2012 database insert?

我有一个 C# windows 服务正在通过 exec sp_executesql 调用写入 SQL Server 2012 数据库。

exec sp_executesql N'Insert into [table name redacted] 
(trial.[field redacted],trial.[field redacted],trial.[Duration],trial.[Count],trial.[Step],trial.[Target],trial.[ResultData],trial.[SessionDateTime],trial.[field redacted],trial.[ModifiedOn],trial.[CreatedOn],trial.[field redacted],trial.[SerialNumber],trial.[Category],trial.[CreatedBy],trial.[ModifiedBy],trial.[Status],trial.[IsEnabled],trial.[IsImmutable],trial.[IsHidden],trial.[Ordinal],trial.[IconUrl],trial.[Url],trial.[DataName],trial.[DisplayName],trial.[Description]) 
values (@field redacted_0,@field redacted_0,@Duration_0,@Count_0,@Step_0,@Target_0,@ResultData_0,@SessionDateTime_0,@field redacted_0,@ModifiedOn_0,@CreatedOn_0,@field redacted_0,@SerialNumber_0,@Category_0,@CreatedBy_0,@ModifiedBy_0,@Status_0,@IsEnabled_0,@IsImmutable_0,@IsHidden_0,@Ordinal_0,@IconUrl_0,@Url_0,@DataName_0,@DisplayName_0,@Description_0)',
N'@field redacted_0 uniqueidentifier,@field redacted_0 nvarchar(4000),@Duration_0 bigint,@Count_0 int,@Step_0 nvarchar(4000),@Target_0 nvarchar(4000),@ResultData_0 nvarchar(4000),@SessionDateTime_0 datetime,@field redacted_0 uniqueidentifier,@ModifiedOn_0 datetime,@CreatedOn_0 datetime,@field redacted_0 uniqueidentifier,@SerialNumber_0 nvarchar(8),@Category_0 nvarchar(4000),@CreatedBy_0 nvarchar(11),@ModifiedBy_0 nvarchar(11),@Status_0 nvarchar(6),@IsEnabled_0 bit,@IsImmutable_0 bit,@IsHidden_0 bit,@Ordinal_0 int,@IconUrl_0 nvarchar(4000),@Url_0 nvarchar(4000),@DataName_0 nvarchar(4000),@DisplayName_0 nvarchar(12),@Description_0 nvarchar(4000)',@field redacted_0='BB52C791-28BC-EC11-BE10-E884A50CE990',@field redacted_0=NULL,@Duration_0=0,@Count_0=0,@Step_0=NULL,@Target_0=N'',
@ResultData_0=NULL,@SessionDateTime_0='2022-04-19 17:57:23',@field redacted_0='F626F234-0AC0-EC11-BE11-E884A50CE990',@ModifiedOn_0='2022-04-19 17:59:15.590',@CreatedOn_0='2022-04-19 17:59:15.590',@field redacted_0='EEFB196C-0AC0-EC11-BE11-E884A50CE990',@SerialNumber_0=N'00000057',@Category_0=NULL,@CreatedBy_0=N'John Stamos',@ModifiedBy_0=N'John Stamos',@Status_0=N'Normal',@IsEnabled_0=1,@IsImmutable_0=0,@IsHidden_0=0,@Ordinal_0=0,@IconUrl_0=NULL,@Url_0=NULL,@DataName_0=NULL,@DisplayName_0=N'Mobile Trial',@Description_0=NULL

通常情况下,插入几乎是瞬间完成的。有时,可能 1000 次中有 1 次会导致错误

Win32Exception: The wait operation timed out

我对 SQL 服务器数据库管理一点都不流利,我的搜索使我找到了许多读取修复程序,包括 exec sp_updatestatswith (NOLOCK) 修复程序,但我不确定在处理插入时如何解决这个问题,甚至不知道如何弄清楚我是如何进入这种状态的。我们目前根本没有(有意地)使用 t运行sactions,所以应该有最少的锁定。我们在 table 中插入了 480 万行,它确实有一个外键指向另一个只有 116K 行的 table。

这是 table 架构。我正在寻找有关如何确定导致此问题的原因的帮助。这只发生在生产中,当然是间歇性的,所以故障排除很困难。

提前致谢。

更新:根据@Charlieface 的建议,我运行 遇到超时时查询并得到这些结果。

如何使用此信息找出导致锁定的原因?我猜等待命令很重要,但这是我目前唯一的猜测。

更新 2:这是 SQL异常(与 Win32Exception 相对)

2022-04-20 16:00:47,861 [4] ERROR Net.ExceptionMarshallingErrorHandler - System.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action'1 wrapCloseInAction) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData() at System.Data.SqlClient.SqlDataReader.get_MetaData() at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted) at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest) at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry) at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method) at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method) at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)

从区块链脚本的结果可以看出,区块链的头部状态为AWAITING COMMAND。这意味着它可能有一个打开的事务在持有锁的同时被挂起。在服务器决定断开连接之前,所有其他事务都被锁定在那些行或对象上。这导致超时。解决方案是增加命令或锁定超时,因为这不能解决根本原因。

我建议您仔细查看触发该命令的代码。它可以通过两种方式使用事务:

  1. 在同一批次或过程中使用 server-side BEGIN TRANCOMMIT 语句。

    如果出现 batch-aborting 错误,事务将保持挂起状态,直到连接关闭或重置。
    但是由于 ADO.Net 管理连接池的方式,这不会发生,直到连接被重用或 4 分钟超时到期。 (这与在连接对象上使用 using 无关。)

  2. 使用 client-side 交易 SqlTransaction.BeginTransaction

    如果发生异常,using 语句通常会确保成功回滚和释放锁。
    但这有时不会成功发生,例如,如果客户端失去网络连接。

这两个的解决方法是:

  • 始终 在连接、事务、命令和reader 对象上使用using 语句。这会在客户端出现异常时提供 best-effort 回滚。
  • 确保 XACT_ABORT 设置为 ON。这意味着服务器将始终在发生错误时回滚。
    您可以将此作为批处理或过程的一部分,或者更好:

使用 server-side 事务通常优于 client-side 事务,因为在客户端失去网络连接的情况下,服务器将不知道回滚。