代码,注意值的顺序是不同的。因此它在锁定行之间交替:
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" );
}
} );
}
表格def:
CREATE TABLE [dbo].[EDescriptionsCategories](
[CategoryId] [int] NOT NULL,
[Id] [int] NOT NULL,
CONSTRAINT [PK_EDescriptionsCategories] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
例外:
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)提示。有可能不是仅仅为了并行更新那两行来锁定整个表吗?
答案 0 :(得分:1)
您的两个语句以不同的顺序获取行锁。这是死锁的经典案例。您可以通过确保所采取的锁定顺序始终处于某种全局顺序(例如,按ID排序)来解决此问题。您应该将两个UPDATE
语句合并为一个,并在将其发送到SQL Server之前对客户端上的ID列表进行排序。对于许多典型的UPDATE
计划,这实际上工作正常(但不保证)。
或者,如果检测到死锁(SqlException.Number == 1205
),则添加重试逻辑。这更优雅,因为它不需要更深入的代码更改。但死锁具有性能影响,所以只能在低死锁率下执行此操作。
如果您的并行处理产生了大量更新,您可以INSERT
将所有这些更新放入临时表(可以同时完成),并在完成后执行一个大的UPDATE
复制所有个人更新记录到主表。您只需更改示例查询中的联接源。
答案 1 :(得分:0)
代码,注意值的顺序是不同的。所以它在锁定行之间交替
不,它不会交替。它以两种不同的顺序获取锁。死锁是保证。
是否有可能不......并行更新这两行?
不喜欢它不是。您要求的是死锁的定义。需要付出一些代价。解决方案必须来自您的业务逻辑,不应尝试从不同的事务处理同一组ID。这意味着整个业务的具体情况。如果你无法做到这一点,那么基本上你只是在乞求僵局。有一些事情你可以做,但没有一个是防弹的,所有费用都很高。链条上的问题越来越严重。
答案 2 :(得分:0)
同意关于锁定的其他答案。
更迫切的问题是你希望从中获得什么?这些命令只有一根电缆向下移动。
这可能会让整体表现变得更糟。更好地并行执行计算,但序列化(并可能批量)更新。