代码,注意值的顺序是不同的。所以它在锁定行之间交替:
static void Main( string[] args )
{
List<int> list = new List<int>();
for(int i = 0; i < 1000; i++ )
list.Add( i );
Parallel.ForEach( list, i =>
{
using( NamePressDataContext db = new NamePressDataContext() )
{
db.ExecuteCommand( @"update EBayDescriptionsCategories set CategoryId = Ids.CategoryId from EBayDescriptionsCategories
join (values (7276, 20870),(240, 20870)) as Ids(Id,CategoryId) on Ids.Id = EBayDescriptionsCategories.Id" );
db.ExecuteCommand( @"update EBayDescriptionsCategories set CategoryId = Ids.CategoryId from EBayDescriptionsCategories
join (values (240, 20870),(7276, 20870)) as Ids(Id,CategoryId) on Ids.Id = EBayDescriptionsCategories.Id" );
}
} );
}
表定义:
CREATE TABLE [dbo].[EDescriptionsCategories](
[CategoryId] [int] NOT NULL,
[Id] [int] NOT NULL,
CONSTRAINT [PK_EDescriptionsCategories] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
异常(exception):
Transaction (Process ID 80) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
该代码仅适用于 WITH (TABLOCK) 提示。是否可以不锁定整个表只是为了并行更新这两行?
最佳答案
您的两个语句以不同的顺序获取行锁。这是死锁的经典案例。您可以通过确保获取锁的顺序始终处于某种全局顺序(例如,按 ID 排序)来解决此问题。您可能应该将两个 UPDATE
语句合并为一个语句,并在将其发送到 SQL Server 之前对客户端上的 ID 列表进行排序。对于许多典型的 UPDATE
计划,这实际上工作正常(但不能保证)。
或者,您添加重试逻辑以防检测到死锁 (SqlException.Number == 1205
)。这更优雅,因为它不需要更深入的代码更改。但死锁会对性能产生影响,因此只在死锁率较低时才这样做。
如果您的并行处理生成大量更新,您可以将所有这些更新INSERT
到一个临时表中(可以同时完成),完成后执行一个大的UPDATE
将所有单独的更新记录复制到主表。您只需更改示例查询中的连接源。
关于c# - 这个简单的代码产生了死锁。包括简单的示例程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20239143/