单个 table 死锁,更新或插入语句没有事务(?)
Deadlocks on single table with no transaction (?) on update or insert statements
所以,我们一直遇到这些死锁问题,我试图在一个简单的代码片段中复制它。基本上,我们在一些处理之前登录我们的数据库,并在之后更新结果。
数据库是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 都抛出随机死锁异常。我没有明确地启动交易,table 和查询非常简单,任何人都可以解释发生了什么以及如何避免这个问题吗?
谢谢!
编辑:这个测试用例实际上并没有真正重现问题,我正在编辑这个,因为我觉得当更新设置更大的 varchar 列时会发生死锁。
正如 Jason 提到的,死锁可能来自 PK 索引的更新。一个可能的原因可能是查询正在锁定 table,意识到页面不够用,将行移动到新页面,然后尝试锁定索引以更新,同时在页面上保持锁定, 当另一个查询开始查询索引然后要求锁定页面时。
我不确定这对以后的任何人都有帮助,但未回答的问题很糟糕,所以这是我的发现:
只有当更新使行变长时才会出现此问题。 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想访问同一个页面...
如果有人对正在发生的事情有更好的了解,我会很高兴知道。在我们的例子中,使用固定长度的字段解决了这个问题。
所以,我们一直遇到这些死锁问题,我试图在一个简单的代码片段中复制它。基本上,我们在一些处理之前登录我们的数据库,并在之后更新结果。
数据库是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 都抛出随机死锁异常。我没有明确地启动交易,table 和查询非常简单,任何人都可以解释发生了什么以及如何避免这个问题吗?
谢谢!
编辑:这个测试用例实际上并没有真正重现问题,我正在编辑这个,因为我觉得当更新设置更大的 varchar 列时会发生死锁。
正如 Jason 提到的,死锁可能来自 PK 索引的更新。一个可能的原因可能是查询正在锁定 table,意识到页面不够用,将行移动到新页面,然后尝试锁定索引以更新,同时在页面上保持锁定, 当另一个查询开始查询索引然后要求锁定页面时。
我不确定这对以后的任何人都有帮助,但未回答的问题很糟糕,所以这是我的发现:
只有当更新使行变长时才会出现此问题。 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想访问同一个页面...
如果有人对正在发生的事情有更好的了解,我会很高兴知道。在我们的例子中,使用固定长度的字段解决了这个问题。