我有大型存储过程来处理用户操作。
它们由多个select语句组成。这些被过滤,大多数时候只获得一行。选择被复制到临时表或以其他方式评估。 最后,merge-Statement执行DB中所需的更改。 所有都封装在一个交易中。
我有来自用户的并发输入,并且应该锁定select语句的选定行以保持数据完整性。
如何锁定所有select语句的选定行,以便在当前事务处理过程中不通过其他事务更新?
ROWLOCK和HOLDLOCK的表提示组合是否以只锁定所选行的方式工作,或者由于HOLDLOCK而锁定整个表?
SELECT *
FROM dbo.Test
WITH (ROWLOCK HOLDLOCK )
WHERE id = @testId
我可以改为使用
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
交易开始后?或者这会锁定整个表格吗? 我正在使用SQL2008 R2,但如果SQL2012中的工作方式不同,也会感兴趣。
PS:我刚看过表提示UPDLOCK和SERIALIZE。 UPDLOCK似乎是一个只锁定一行的解决方案,似乎UPDLOCK总是锁定而不是ROWLOCK,它只指定锁是应用基于行的IF锁。我仍然对解决这个问题的最佳方法感到困惑......
答案 0 :(得分:0)
更改隔离级别修复了问题(并锁定在行级别):
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
以下是我测试它的方式。 我在SQL Management Studio的空白页面中创建了一个语句:
begin tran
select
*
into #message
from dbo.MessageBody
where MessageBody.headerId = 28
WAITFOR DELAY '0:00:05'
update dbo.MessageBody set [message] = 'message1'
where headerId = (select headerId from #message)
select * from dbo.MessageBody where headerId = (select headerId from #message)
drop table #message
commit tran
执行此语句(由于延迟而在最后5秒内),我在另一个窗口中调用了第二个查询:
begin tran
select
*
into #message
from dbo.MessageBody
where MessageBody.headerId = 28
update dbo.MessageBody set [message] = 'message2'
where headerId = (select headerId from #message)
select * from dbo.MessageBody where headerId = (select headerId from #message)
drop table #message
commit tran
我很惊讶它立刻执行了。这是由于默认的SQL Server事务级别" Read Commited" http://technet.microsoft.com/en-us/library/ms173763.aspx。由于第一个脚本的更新是在延迟之后完成的,因此在第二个脚本期间还没有任何umcommited更改,因此读取并更新了第28行。
将隔离级别更改为Serialization阻止了这种情况,但它也阻止了并发性 - 两个scip都是连续执行的。
没关系,因为两个脚本都读取并更改了同一行(通过headerId = 28)。将headerId更改为第二个脚本中的另一个值,这些语句是并行执行的。所以来自SERIALIZATION的锁似乎在行级别上。
添加表提示
WITH ( SERIALIZABLE)
在第一个语句的第一个选择中也会阻止对所选行的进一步读取。