C# SQL多个查询同时创建死锁(UPDLOCK)

C# SQL多个查询同时创建死锁(UPDLOCK),c#,sql,sql-server,C#,Sql,Sql Server,因此,我的数据库出现了一个死锁问题 System.Data.SqlClient.SqlException:事务(进程ID 57)在另一个进程的锁资源上被死锁,并被选为死锁牺牲品。重新运行事务 我有一个项目开始运行,然后分成x个并行运行的实例(通常在4到8个之间)。这些实例中的每一个都运行安装程序查询,其中一小部分如下所示: public void AddNewUserAndAssignPermission( string userid, string username,

因此,我的数据库出现了一个死锁问题

System.Data.SqlClient.SqlException:事务(进程ID 57)在另一个进程的锁资源上被死锁,并被选为死锁牺牲品。重新运行事务

我有一个项目开始运行,然后分成x个并行运行的实例(通常在4到8个之间)。这些实例中的每一个都运行安装程序查询,其中一小部分如下所示:

    public void AddNewUserAndAssignPermission(
        string userid, string username, string normalizedUsername, string password, string security, string concurrency, string rolename)
    {
        InsertRecordIntoUserTable(userid,username,normalizedUsername,password,security,concurrency);
        InsertRecordIntoPersonTable(username, userid);
        //If the new user's role ID has not been specified, then skip over the assignment of role permission queries
        if(rolename!= "")
        {
            InsertRecordIntoUserRolesTable(userid, rolename);
        }
    }

    public void InsertRecordIntoUserTable(
        string userid, string username, string normalizedUsername, string password, string security, string concurrency)
    {
        var query = @"IF NOT EXISTS(
                        SELECT * FROM AspNetUsers WHERE UserName = @username)
                        BEGIN
                            INSERT INTO AspNetUsers(Id,UserName,NormalizedUserName,Email,NormalizedEmail,EmailConfirmed,PasswordHash,SecurityStamp,ConcurrencyStamp,PhoneNumber,PhoneNumberConfirmed,TwoFactorEnabled,LockoutEnd,LockoutEnabled,AccessFailedCount)
                            VALUES (@userid, @username, @normalizedUsername, NULL, NULL, 0, @password, @security, @concurrency, NULL, 0, 0, NULL, 0, 0)
                        END";
        var param_id = NewParam("@userid", userid);
        var param_username = NewParam("@username", username);
        var param_normalizedUsername = NewParam("@normalizedUsername", normalizedUsername);
        var param_password = NewParam("@password", password);
        var param_security = NewParam("@security", security);
        var param_concurrency = NewParam("@concurrency", concurrency);
        SqlDataLib sql = new SqlDataLib();
        sql.ExecuteNonQuery(Settings.AppConnectionString, query, param_id, param_username, param_normalizedUsername, param_password, param_security, param_concurrency);
    }

    public void InsertRecordIntoPersonTable( string lastname, string userid)
    {
        var query = @"IF NOT EXISTS(SELECT * FROM crm.Person WHERE IdentityUserId = @userid)
                        BEGIN
                            INSERT INTO crm.Person (FirstName, LastName, IdentityUserId, Inactive) VALUES ('Testing', @lastname, @userid, 0)
                        END";
        var param_userid = NewParam("@userid", userid);
        var param_lastname = NewParam("@lastname", lastname);
        SqlDataLib sql = new SqlDataLib();
        sql.ExecuteNonQuery(Settings.AppConnectionString, query, param_userid, param_lastname);
    }

    public void InsertRecordIntoUserRolesTable(string userid, string rolename)
    {
        var query = @"DECLARE @roleId nvarchar(450); SET @roleId = (SELECT Id from AspNetRoles WHERE Name = @rolename)
                        IF NOT EXISTS(SELECT * FROM AspNetUserRoles WHERE UserId = @userid AND RoleId = @roleid)
                        BEGIN
                            INSERT INTO AspNetUserRoles (UserId, RoleId) VALUES (@userid, @roleid)
                        END";
        var param_userid = NewParam("@userid", userid);
        var param_rolename = NewParam("@rolename", rolename);
        SqlDataLib sql = new SqlDataLib();
        sql.ExecuteNonQuery(Settings.AppConnectionString, query, param_userid, param_rolename);
    }
INSERT INTO AspNetUsers(Id,UserName,NormalizedUserName,Email,NormalizedEmail,EmailConfirmed,PasswordHash,SecurityStamp,ConcurrencyStamp,PhoneNumber,PhoneNumberConfirmed,TwoFactorEnabled,LockoutEnd,LockoutEnabled,AccessFailedCount)
                      Select TOP 1 @userid, @username, @normalizedUsername, NULL, NULL, 0, @password, @security, @concurrency, NULL, 0, 0, NULL, 0, 0 FROM AspNetUsers
                      WHERE not exists ( SELECT * FROM AspNetUsers WITH (UPDLOCK)
                                         WHERE UserName = @username )
 
 INSERT INTO crm.Person (FirstName, LastName, IdentityUserId, Inactive)
                      SELECT TOP 1 'Testing', @lastname, @userid, 0 FROM crm.Person
                      WHERE not exists ( SELECT * FROM crm.Person WITH (UPDLOCK)
                                         WHERE IdentityUserId = @userid )

 DECLARE @roleId nvarchar(450); SET @roleId = (SELECT Id from dbo.AspNetRoles WHERE Name = @rolename)
                      INSERT INTO dbo.AspNetUserRoles (UserId, RoleId)
                      SELECT TOP 1 @userid, @roleid FROM dbo.AspNetUserRoles
                      WHERE not exists ( SELECT * FROM dbo.AspNetUserRoles WITH (UPDLOCK)
                                         WHERE UserId = @userid AND RoleId = @roleid )
但是,这段代码失败,给了我有关尝试插入重复密钥的错误-我对此进行了调查,并相信这是因为在执行一个实际添加到记录中的代码时,这些密钥不会锁定数据库,而另一个已经检查到它不存在,然后还尝试添加它。因此,我使用
UPDLOCK
重新编写了这些查询来解决这个问题。新查询如下所示:

    public void AddNewUserAndAssignPermission(
        string userid, string username, string normalizedUsername, string password, string security, string concurrency, string rolename)
    {
        InsertRecordIntoUserTable(userid,username,normalizedUsername,password,security,concurrency);
        InsertRecordIntoPersonTable(username, userid);
        //If the new user's role ID has not been specified, then skip over the assignment of role permission queries
        if(rolename!= "")
        {
            InsertRecordIntoUserRolesTable(userid, rolename);
        }
    }

    public void InsertRecordIntoUserTable(
        string userid, string username, string normalizedUsername, string password, string security, string concurrency)
    {
        var query = @"IF NOT EXISTS(
                        SELECT * FROM AspNetUsers WHERE UserName = @username)
                        BEGIN
                            INSERT INTO AspNetUsers(Id,UserName,NormalizedUserName,Email,NormalizedEmail,EmailConfirmed,PasswordHash,SecurityStamp,ConcurrencyStamp,PhoneNumber,PhoneNumberConfirmed,TwoFactorEnabled,LockoutEnd,LockoutEnabled,AccessFailedCount)
                            VALUES (@userid, @username, @normalizedUsername, NULL, NULL, 0, @password, @security, @concurrency, NULL, 0, 0, NULL, 0, 0)
                        END";
        var param_id = NewParam("@userid", userid);
        var param_username = NewParam("@username", username);
        var param_normalizedUsername = NewParam("@normalizedUsername", normalizedUsername);
        var param_password = NewParam("@password", password);
        var param_security = NewParam("@security", security);
        var param_concurrency = NewParam("@concurrency", concurrency);
        SqlDataLib sql = new SqlDataLib();
        sql.ExecuteNonQuery(Settings.AppConnectionString, query, param_id, param_username, param_normalizedUsername, param_password, param_security, param_concurrency);
    }

    public void InsertRecordIntoPersonTable( string lastname, string userid)
    {
        var query = @"IF NOT EXISTS(SELECT * FROM crm.Person WHERE IdentityUserId = @userid)
                        BEGIN
                            INSERT INTO crm.Person (FirstName, LastName, IdentityUserId, Inactive) VALUES ('Testing', @lastname, @userid, 0)
                        END";
        var param_userid = NewParam("@userid", userid);
        var param_lastname = NewParam("@lastname", lastname);
        SqlDataLib sql = new SqlDataLib();
        sql.ExecuteNonQuery(Settings.AppConnectionString, query, param_userid, param_lastname);
    }

    public void InsertRecordIntoUserRolesTable(string userid, string rolename)
    {
        var query = @"DECLARE @roleId nvarchar(450); SET @roleId = (SELECT Id from AspNetRoles WHERE Name = @rolename)
                        IF NOT EXISTS(SELECT * FROM AspNetUserRoles WHERE UserId = @userid AND RoleId = @roleid)
                        BEGIN
                            INSERT INTO AspNetUserRoles (UserId, RoleId) VALUES (@userid, @roleid)
                        END";
        var param_userid = NewParam("@userid", userid);
        var param_rolename = NewParam("@rolename", rolename);
        SqlDataLib sql = new SqlDataLib();
        sql.ExecuteNonQuery(Settings.AppConnectionString, query, param_userid, param_rolename);
    }
INSERT INTO AspNetUsers(Id,UserName,NormalizedUserName,Email,NormalizedEmail,EmailConfirmed,PasswordHash,SecurityStamp,ConcurrencyStamp,PhoneNumber,PhoneNumberConfirmed,TwoFactorEnabled,LockoutEnd,LockoutEnabled,AccessFailedCount)
                      Select TOP 1 @userid, @username, @normalizedUsername, NULL, NULL, 0, @password, @security, @concurrency, NULL, 0, 0, NULL, 0, 0 FROM AspNetUsers
                      WHERE not exists ( SELECT * FROM AspNetUsers WITH (UPDLOCK)
                                         WHERE UserName = @username )
 
 INSERT INTO crm.Person (FirstName, LastName, IdentityUserId, Inactive)
                      SELECT TOP 1 'Testing', @lastname, @userid, 0 FROM crm.Person
                      WHERE not exists ( SELECT * FROM crm.Person WITH (UPDLOCK)
                                         WHERE IdentityUserId = @userid )

 DECLARE @roleId nvarchar(450); SET @roleId = (SELECT Id from dbo.AspNetRoles WHERE Name = @rolename)
                      INSERT INTO dbo.AspNetUserRoles (UserId, RoleId)
                      SELECT TOP 1 @userid, @roleid FROM dbo.AspNetUserRoles
                      WHERE not exists ( SELECT * FROM dbo.AspNetUserRoles WITH (UPDLOCK)
                                         WHERE UserId = @userid AND RoleId = @roleid )
然而,这使得他们在尝试插入重复密钥时有时仍然失败,而在这方面主要失败:

System.Data.SqlClient.SqlException:事务(进程ID 57)在另一个进程的锁资源上被死锁,并被选为死锁受害者

所以我再次重写了它们(我相信这不是它们应该看起来的样子,但这是我在做的实验)。注意主语句上的
UPDLOCK

INSERT INTO AspNetUsers(Id,UserName,NormalizedUserName,Email,NormalizedEmail,EmailConfirmed,PasswordHash,SecurityStamp,ConcurrencyStamp,PhoneNumber,PhoneNumberConfirmed,TwoFactorEnabled,LockoutEnd,LockoutEnabled,AccessFailedCount)
                      Select TOP 1 @userid, @username, @normalizedUsername, NULL, NULL, 0, @password, @security, @concurrency, NULL, 0, 0, NULL, 0, 0 FROM AspNetUsers WITH (UPDLOCK)
                      WHERE not exists ( SELECT * FROM AspNetUsers 
                                         WHERE UserName = @username )

 INSERT INTO crm.Person (FirstName, LastName, IdentityUserId, Inactive)
                      SELECT TOP 1 'Testing', @lastname, @userid, 0 FROM crm.Person WITH (UPDLOCK)
                      WHERE not exists ( SELECT * FROM crm.Person
                                         WHERE IdentityUserId = @userid )

 DECLARE @roleId nvarchar(450); SET @roleId = (SELECT Id from dbo.AspNetRoles WHERE Name = @rolename)
                      INSERT INTO dbo.AspNetUserRoles (UserId, RoleId)
                      SELECT TOP 1 @userid, @roleid FROM dbo.AspNetUserRoles WITH (UPDLOCK)
                      WHERE not exists ( SELECT * FROM dbo.AspNetUserRoles
                                         WHERE UserId = @userid AND RoleId = @roleid )
这工作得很好,但是锁定数据库的时间太长,这通常会导致多个实例中的一个在SQL超时时失败,表示其他实例通过时数据库没有响应。(不再有重复的关键问题)

我还想补充一点,如果我单独运行这些语句,或者只运行一个C#代码实例,我可以随时运行它们,如果行不存在,它们将始终创建该行,否则不会按预期执行任何操作。它们只有在基本上由我的C#代码同时运行时才会开始出错


我猜我使用的
UPDLOCK
有点错误,但我已经尝试了我所知道的一切。任何帮助都将不胜感激。

此模式的问题:

WHERE not exists ( SELECT * FROM AspNetUsers WITH (UPDLOCK)
                 WHERE UserName = @username )
如果在子查询中找不到行,则不会锁定任何内容。在默认并发模型下,锁总是行/键/页/表锁。它不会锁定空的密钥范围。但是,SERIALIZABLE可以。因此,将锁定提示更改为:

 WHERE not exists ( SELECT * FROM AspNetUsers WITH (UPDLOCK, SERIALIZABLE)
                     WHERE UserName = @username )

这种模式的问题是:

WHERE not exists ( SELECT * FROM AspNetUsers WITH (UPDLOCK)
                 WHERE UserName = @username )
如果在子查询中找不到行,则不会锁定任何内容。在默认并发模型下,锁总是行/键/页/表锁。它不会锁定空的密钥范围。但是,SERIALIZABLE可以。因此,将锁定提示更改为:

 WHERE not exists ( SELECT * FROM AspNetUsers WITH (UPDLOCK, SERIALIZABLE)
                     WHERE UserName = @username )

您不应该让所有并行线程都运行相同的安装插入-只在序列化部分执行

否则,您不必尝试锁定,只需捕获重复键错误并继续—您当前的逻辑表示,如果已经有一行具有相同的名称(假设该行是唯一键),则无需执行任何操作。此演示来自

开始尝试
插入等
结束尝试
开始捕捉
如果出现错误_编号()2627
错误等
端接

您不应该让所有并行线程都运行相同的安装插入-只在序列化部分执行

否则,您不必尝试锁定,只需捕获重复键错误并继续—您当前的逻辑表示,如果已经有一行具有相同的名称(假设该行是唯一键),则无需执行任何操作。此演示来自

开始尝试
插入等
结束尝试
开始捕捉
如果出现错误_编号()2627
错误等
端接

您的代码目前的意义不大,我想知道这是否会加剧锁定问题。

你的每一句话都是

从不存在的某个表中选择top 1@param1、@param2。。。
这是荒谬的我们不关心“选择”部分中已经存在的内容,只关心“不存在”部分中的内容。所以应该是这样

在不存在的地方选择@param1、@param2。。。
另外,正如其他人所提到的,您需要使其可序列化(可选语法为
HOLDLOCK
)@DavidBrowne对此有些不清楚。关键是,它既可以取出一个范围锁以防止插入,又可以将该锁保持到事务结束。它可以使用一个表锁来满足这一点,它只需要保持它直到结束

因此,我们需要的是:

插入到AspNetUsers中
(Id、用户名、NormalizedUserName、电子邮件、NormalizedMail、EmailConfirmed、PasswordHash、SecurityStamp、ConcurrencyStamp、PhoneNumber、PhoneNumber确认、双因素编码、锁定输出、锁定输出、访问失败计数)
选择@userid、@username、@normalizedUsername、NULL、NULL、0、@password、@security、@concurrency、NULL、0、0、NULL、0、0
不存在的位置(选择1
来自具有(UPDLOCK、HOLDLOCK)的AspNetUsers
其中UserName=@UserName);
插入crm.Person
(FirstName、LastName、IdentityUserId、非活动)
选择“测试”,@lastname,@userid,0
不存在的位置(选择1
来自crm。具有(UPDLOCK、HOLDLOCK)的人员
其中IdentityUserId=@userid);
声明@roleId nvarchar(450)=(从dbo.AspNetRoles中选择Id,其中Name=@rolename);
插入到dbo.AspNetUserRoles中
(UserId,RoleId)
选择@userid、@roleId
不存在的位置(选择1
来自dbo.AspNetUserRoles和(UPDLOCK、HOLDLOCK)
其中UserId=@UserId和RoleId=@RoleId);

如果执行此操作后仍然出现死锁,我建议您共享死锁图以及完整的表和索引定义。


还有许多其他问题可能导致死锁,只有在看到图表后才能给出明确的答案,因为您的代码实际上没有意义,