在更新或插入语句上没有事务 (?) 的单个表上的死锁

Posted

技术标签:

【中文标题】在更新或插入语句上没有事务 (?) 的单个表上的死锁【英文标题】:Deadlocks on single table with no transaction (?) on update or insert statements 【发布时间】:2015-04-16 13:43:08 【问题描述】:

所以,我们一直有这些死锁问题,我试图在一个简单的代码 sn-p 中复制这个问题。基本上,我们在一些处理之前登录我们的数据库,并在之后更新结果。

数据库是 Sybase ASE 15.7,使用的驱动程序是 Adaptive Server Enterprise ODBC 驱动程序 (v15.05)。代码是 C# 4.0

我用来复制问题的代码如下:

数据库:

create  table dbo.test_deadlock_t(
id   int  identity,
col1   int  not null,
col2   varchar(255)  not null,
constraint test_deadl_id_pk primary key clustered ( id ))
alter table test_deadlock_t lock allpages

C#(忽略一些声明性代码)

using System.Data.Odbc;

public const string cmdInsert = "INSERT INTO test_deadlock_t (col1, col2) VALUES (1, 'test') SELECT @@identity";
public const string cmdUpdate = "UPDATE test_deadlock_t SET col1 = 3, col2 = 'aaaaaaaa' WHERE id = 0";

public static void Test()

    Task[] tasks = new Task[threadCount];
    for (int ii = 0; ii < threadCount; ii++)
    
        tasks[ii] = Task.Factory.StartNew(() => InsertThenUpdate());
    
    Task.WaitAll(tasks);


public static void Test2()

    Task[] tasks = new Task[threadCount];
    for (int ii = 0; ii < threadCount; ii++)
    
        int ii_copy = ii;
        tasks[ii_copy] = Task.Factory.StartNew(() => Update(ii_copy));
    
    Task.WaitAll(tasks);


public static void InsertThenUpdate()

    using (OdbcConnection connection = new OdbcConnection(connectionString))
    
        connection.Open();
        OdbcCommand command = new OdbcCommand(cmdInsert, connection);
        int result = (int)(command.ExecuteScalar() as decimal?).Value;
        Update(result);
    


public static void Update(int id)

    using (OdbcConnection connection = new OdbcConnection(connectionString))
    
        connection.Open();
        OdbcCommand command = new OdbcCommand(String.Format(cmdUpdate, id), connection);
        int result = command.ExecuteNonQuery();
        Console.WriteLine("Update result : " + result);
    

Test 和 Test2 都抛出随机死锁异常。我没有明确地启动事务,表和查询很简单,谁能解释发生了什么以及如何避免这个问题?

谢谢!

编辑:这个测试用例实际上并没有真正重现问题,我正在编辑这个,因为我感觉当更新设置更大的 varchar 列时会发生死锁。

正如 Jason 提到的,死锁可能来自 PK 索引的更新。一个可能的原因可能是查询正在锁定表,意识到页面不够用,将行移动到新页面,然后尝试锁定索引以更新,同时保持页面上的锁定,当另一个查询从查询索引开始,然后请求锁定页面。

【问题讨论】:

为了测试的完整性,如果您进行显式事务处理会发生什么? 我的猜测是它与此有关:infocenter.sybase.com/help/index.jsp?topic=/…。请参阅最后一段“在许多情况下,由所有页锁定导致的并发问题是由索引页锁定引起的,而不是数据页本身的锁定。” @Ralf :我在之前的测试中失败了(对于垃圾邮件感到抱歉),但行为与显式事务声明基本相同。 【参考方案1】:

我不确定这对将来的任何人是否有帮助,但未回答的问题很糟糕,所以这是我发现的:

仅当更新使行变长时才会出现此问题。 Jason 认为锁可能来自索引是正确的。经过对日志的进一步分析,我们得到了这个:

Deadlock Id 29756: Process (Familyid 0, Spid 282) was waiting for a 'exclusive page' lock on page 26700113 of table 'test_deadlock_t' in database 'xxx' but process (Familyid 0, Spid 1051) already held a 'exclusive page' lock on it.
Deadlock Id 29756: Process (Familyid 0, Spid 1051) was waiting for a 'exclusive page' lock on page 29892374 of table 'test_deadlock_t' , indid 1 in database 'xxx' but process (Familyid 0, Spid 282) already held a 'exclusive page' lock on it.

“indid 1”指的是主键。

我的理解是 sybase 会锁定它想要更新的页面。与此同时,一个 select 以某种方式请求锁定索引。然后更新发现页面对于更新的行来说太小了,所以它尝试移动页面,并请求锁定索引。但是select已经有锁了,select要访问同一个页面……

如果有人对正在发生的事情有更好的了解,我会很高兴知道。在我们的例子中,使用固定长度的字段解决了这个问题。

【讨论】:

以上是关于在更新或插入语句上没有事务 (?) 的单个表上的死锁的主要内容,如果未能解决你的问题,请参考以下文章

同一张表上的两个“SELECT FOR UPDATE”语句会导致死锁吗?

使用自引用外键的死锁

MySQL online DDL的两个坑

MySQL online DDL的两个坑

PetaPoco 在带触发器的表上插入失败

从存储过程结果集中在表上插入/更新数据