恼人的SQL死锁

时间:2014-01-23 09:11:33

标签: sql sql-server deadlock

考虑以下设置:

create table Tabl
    (
    ClustInd int,
    NonClustInd int,
    NonClustInd_Addon int,
    OtherCol int
    )
    create clustered index CLI on Tabl (ClustInd)
    create nonclustered index NCLI on Tabl (NonClustInd) include(NonClustInd_Addon)
    go

    --just generating & inserting 2^4*4 = 65536 records:
    ;with h(a) as (select 0 union all select 0), 
    h1(a) as (select h1.a from h h1, h h2, h h3, h h4),
    h2(a) as (select h1.a from h1, h1 h2, h1 h3, h1 h4),
    r(a) as (select row_number() over (order by a) from h2)
    insert into Tabl(ClustInd,  NonClustInd, NonClustInd_Addon, OtherCol)
    select a, a*10 - 10000, 60 + a*5, a*3 from r

现在如果你在一个会话中运行它:


    declare @g int
    while 1 = 1
    --Using "OtherCol" to force the NCLI -> CLI -> Tbl lookup
    select @g = OtherCol from Tabl where NonClustInd = 477210
这在另一个:

    while 1 = 1 
    update Tabl
    --Updating NonClustInd_Addon to force the updating of the NCLI and CLI indexes
    set NonClustInd_Addon = case when NonClustInd_Addon = 1 then 2 else 1 end
    where NonClustInd = 477210

“select”语句将使用update语句死锁。

它们死锁的原因是因为select语句首先锁定“NCLI”索引,然后锁定“CLI”索引。并且更新语句 - 首先是“CLI”索引,然后是“NCLI”。显然这最终会导致僵局。

有没有办法打败这个僵局?也许有一种方法可以控制索引被锁定的顺序(因为,例如,如果SQL锁定CLI,然后是NCLI进行更新和选择,则此死锁将永远不会出现)。 感谢。

PS:其他信息:

在更新期间,存在以下锁: Update

在选择期间:

Select

死锁前的秒数:

Deadlock

3 个答案:

答案 0 :(得分:3)

使用快照隔离或read committed snapshot。这通常很容易实施,而且负面后果往往是值得的。它通过将只读事务完全排除在外,简化了您的生活。它们不再锁定或阻塞。

如果你能做到这一点,问题就解决了。

答案 1 :(得分:1)

通过使用'NOLOCK',我们可以避免这种僵局......试试这样......

declare @g int

while 1 = 1

select @g = OtherCol from Tabl WITH(NOLOCK) where NonClustInd = 477210

答案 2 :(得分:0)

作为强力解决方案,您可以在表格中添加一列:lockColumn int not null default 0

在两次交易开始时,请致电update tab1 set lock = lock + 1 where NonClustInd = whatever

但是,我并不是特别喜欢这个,并且不认为它是一个永久的解决方案;当你弄清楚永久解决方案是什么时,这只是一个权宜之计。