T-SQL存储过程执行'原子'吗?

时间:2008-11-03 20:18:24

标签: sql-server-2005 tsql sql-server-2008 stored-procedures

假设我有一个看起来像这样的简单存储过程(注意:这只是一个例子,不是一个实际的过程):

CREATE PROCEDURE incrementCounter AS

DECLARE @current int
SET @current = (select CounterColumn from MyTable) + 1

UPDATE
    MyTable
SET
    CounterColumn = current
GO

我们假设我有一个名为'myTable'的表,其中包含一行,'CounterColumn'包含我们当前的计数。

这个存储过程可以同时执行多次吗?

即。这是可能的:

我两次调用'incrementCounter'。调用A到达设置'当前'变量的位置(假设它是5)。调用B到达设置“当前”变量(也将是5)的点。呼叫A完成执行,然后呼叫B结束。最后,该表应包含值6,但由于执行重叠

而包含5

5 个答案:

答案 0 :(得分:12)

这适用于SQL Server。

每个语句都是原子的,但是如果你希望存储过程是原子的(或者一般的任何语句序列),你需要用

明确地包围语句。

开始交易
声明......
声明......
提交交易

(通常使用BEGIN TRAN和END TRAN。)

当然,有很多方法可以解决锁定问题,这取决于同时发生的其他问题,因此您可能需要一种策略来处理失败的事务。 (完全讨论可能导致锁定的所有情况,无论你如何设计这个特定的SP,都超出了问题的范围。)但是由于原子性,它们仍然会被重新提交。根据我的经验,您可能会很好,不知道您的交易量和数据库上的其他活动。对不起,请说明一点。

与流行的误解相反,这将适用于您的默认事务级别设置。

答案 1 :(得分:12)

除了将代码放在BEGIN TRANSACTIONEND TRANSACTION之间之外,您还需要确保正确设置事务隔离级别。

例如,SERIALIZABLE隔离级别将防止代码同时运行时丢失更新,但READ COMMITTED(SQL Server Management Studio中的默认值)不会。

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

正如其他人已经提到的那样,在确保一致性的同时,这可能导致阻塞和死锁,因此可能不是实践中的最佳解决方案。

答案 2 :(得分:1)

我使用这种方法

CREATE PROCEDURE incrementCounter
AS

DECLARE @current int

UPDATE MyTable
SET
  @current = CounterColumn = CounterColumn + 1

Return @current

此过程一次执行所有两个命令,它与其他事务隔离。

答案 3 :(得分:0)

也许我在你的例子中读得太多了(你的真实情况可能要复杂得多),但为什么你不能在一个声明中这样做呢?

CREATE PROCEDURE incrementCounter AS

UPDATE
    MyTable
SET
    CounterColumn = CounterColumn + 1

GO

这样,它是自动原子的,如果同时执行两个更新,它们将始终由SQL Server排序,以避免您描述的冲突。但是,如果您的实际情况要复杂得多,那么将其包装在事务中是最好的方法。

但是,如果另一个进程启用了“安全性较低”的隔离级别(如允许脏读或不可重复读取的那个),那么我认为事务不会对此进行保护,因为另一个进程可以看到部分更新的数据,如果它被选为允许不安全的读取。

答案 4 :(得分:0)

对你的问题的简短回答是,它可以而且会很短。如果要阻止并发执行存储过程,请在继续执行该过程中的任何工作之前启动事务并在每次执行存储过程时更新相同的数据。

CREATE PROCEDURE ..
BEGIN TRANSACTION
UPDATE mylock SET ref = ref + 1
...

这将迫使其他并发执行等待轮到他们,因为他们将无法更改' ref'值,直到其他事务完成并且相关的更新锁定被解除。

一般来说,假设任何和所有SELECT查询的结果在之前都是陈旧的是一个好主意。使用" heavy"隔离级别解决这个不幸的现实严重限制了可扩展性。更好地构建更改的方式可以对更新期间存在的系统状态做出乐观的假设,因此当您的假设失败时,您可以稍后重试并希望获得更好的结果。例如:

UPDATE
    MyTable
SET
    CounterColumn = current 
WHERE CounterColumn = current - 1

使用添加了WHERE子句的示例,如果对其当前状态的假设失败,则此更新不会影响任何行。检查@@ ROWCOUNT以测试行数和回滚或其他适当的操作,但它与预期结果不同。