18

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

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

4

5 回答 5

13

这是针对 SQL Server 的。

每个语句都是原子的,但如果您希望存储过程是原子的(或一般的任何语句序列),则需要用显式包围语句

BEGIN TRANSACTION
语句 ...
语句 ...
COMMIT TRANSACTION

(通常简称为 BEGIN TRAN 和 END TRAN。)

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

与流行的误解相反,这将在您使用默认事务级别设置的情况下起作用。

于 2008-11-03T20:27:07.843 回答
12

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

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

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

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

于 2008-11-03T20:58:19.090 回答
1

我用这个方法

CREATE PROCEDURE incrementCounter
AS

DECLARE @current int

UPDATE MyTable
SET
  @current = CounterColumn = CounterColumn + 1

Return @current

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

于 2013-09-15T04:59:00.233 回答
0

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

CREATE PROCEDURE incrementCounter AS

UPDATE
    MyTable
SET
    CounterColumn = CounterColumn + 1

GO

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

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

于 2010-01-19T03:52:15.337 回答
0

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

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

这将迫使其他并发执行等待轮到它们,因为在其他事务完成并解除关联的更新锁之前,它们将无法更改“ref”值。

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

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

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

于 2015-02-08T10:02:24.453 回答