假设您有以下代码(请忽略它很糟糕):
BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else
在我看来,这不是正确管理并发。仅仅因为您有一个事务并不意味着其他人不会读取与您在获取更新语句之前所做的相同的值。
现在,让代码保持原样(我意识到这最好作为单个语句处理,甚至更好地使用自动增量/标识列)有什么方法可以使它正确处理并发并防止允许两个客户端获得相同的竞争条件身份证价值?
我很确定WITH (UPDLOCK, HOLDLOCK)
在 SELECT 中添加 a 就可以了。SERIALIZABLE事务隔离级别似乎也可以工作,因为它拒绝任何其他人阅读您所做的事情,直到 tran 结束(更新:这是错误的。请参阅 Martin 的回答)。真的吗?他们会同样工作吗?一个比另一个更受欢迎吗?
想象一下做一些比 ID 更新更合法的事情——一些基于你需要更新的读取的计算。可能涉及许多表,其中一些您会写入,而另一些则不会。这里的最佳做法是什么?
写完这个问题后,我认为锁定提示更好,因为那样你只锁定你需要的表,但我很感激任何人的意见。
PS 不,我不知道最好的答案,真的很想得到更好的理解!:)
只是解决
SERIALIZABLE
隔离级别方面。是的,这将起作用,但存在死锁风险。两个事务都将能够同时读取该行。它们不会相互阻塞,因为它们会根据表结构获取对象
S
锁或索引RangeS-S
锁,并且这些锁是兼容的。但是它们在尝试获取更新所需的锁(分别为对象IX
锁或索引RangeS-U
)时会相互阻塞,从而导致死锁。相反,使用显式
UPDLOCK
提示将序列化读取,从而避免死锁风险。我认为对您来说最好的方法是实际将您的模块暴露给高并发并亲自查看。有时单独使用UPDLOCK就足够了,不需要HOLDLOCK。有时 sp_getapplock 效果很好。我不会在这里做任何笼统的陈述——有时再添加一个索引、触发器或索引视图会改变结果。我们需要对代码进行压力测试,并根据具体情况亲自查看。
我在这里写了几个压力测试的例子
编辑:为了更好地了解内部结构,您可以阅读 Kalen Delaney 的书籍。但是,书籍可能会像任何其他文档一样不同步。此外,还有太多的组合需要考虑:六个隔离级别、多种锁、聚集/非聚集索引以及谁知道还有什么。那是很多组合。最重要的是,SQL Server 是闭源的,所以我们不能下载源代码、调试它等等——这将是最终的知识来源。在下一个版本或服务包之后,其他任何内容都可能不完整或过时。
因此,您不应该在没有自己的压力测试的情况下决定什么对您的系统有效。无论你读过什么,它都可以帮助你理解正在发生的事情,但你必须证明你读过的建议对你有用。我认为没有人可以为你做这件事。
在这种特殊情况下,添加
UPDLOCK
锁SELECT
确实可以防止异常。没有必要添加,HOLDLOCK
因为在交易期间会持有更新锁,但我承认自己过去将其作为(可能是坏的)习惯包括在内。没有最佳实践。您选择的并发控制必须基于应用程序的要求。一些应用程序/事务需要像拥有数据库的独占所有权一样执行,不惜一切代价避免异常和不准确。其他应用程序/事务可以容忍彼此之间的某种程度的干扰。
编辑:@AlexKuznetsov 的评论促使我重新阅读了这个问题并删除了答案中非常明显的错误。在深夜发帖时提醒自己。