数据库中的原子比较和交换

时间:2009-10-01 23:04:18

标签: sql sql-server database oracle

我正在研究工作排队解决方案。我想查询数据库中的给定行,其中状态列具有特定值,修改该值并返回行,我想以原子方式执行,以便其他任何查询都不会看到它:


begin transaction
select * from table where pk = x and status = y
update table set status = z where pk = x
commit transaction
--(the row would be returned)

两个或更多并发查询必须不能返回该行(一个查询执行会看到该行,而其状态= y) - 有点像互锁的CompareAndExchange操作。

我知道上面的代码运行(对于SQL服务器),但是交换总是原子的吗?

我需要一个适用于SQL Server和Oracle的解决方案

4 个答案:

答案 0 :(得分:7)

PK是主键吗?那么这是一个非问题,如果你已经知道主键没有运动。如果pk 主键,那么这就引出了一个显而易见的问题如何你知道要出列的项目的pk ......

问题是,如果你知道主键并想要将下一个'可用'(即状态= y)出列并将其标记为已出列(删除它或设置状态= Z)。

执行此操作的正确方法是使用单个语句。不幸的是,Oracle和SQL Server之间的语法不同。 SQL Server语法是:

update top (1) [<table>]
set status = z 
output DELETED.*
where  status = y;

我对Oracle的RETURNING子句不太熟悉,给出了一个类似于SQL的OUTPUT的例子。

其他SQL Server解决方案要求SELECT(使用UPDLOCK)上的锁定提示是正确的。 在Oracle中,优先使用的是使用FOR UPDATE,但这在SQL Server中不起作用,因为FOR UPDATE将与SQL中的游标一起使用。

在任何情况下,您在原始帖子中的行为都是不正确的。多个会话都可以选择相同的行,甚至全部更新它,将相同的出列项返回给多个读者。

答案 1 :(得分:2)

作为一般规则,要执行类似于此操作的操作,您需要确保在执行select时设置独占(或更新)锁定,以便在更新之前没有其他事务可以读取该行。 / p>

典型的语法类似于:

 select * from table where pk = x and status = y for update

但是你需要仔细查看。

答案 2 :(得分:1)

我有一些应用程序遵循类似的模式。像你这样的表代表了一个工作队列。该表有两个额外的列:thread_id和thread_date。当应用程序从队列中请求工作时,它会提交一个线程ID。然后,单个更新语句将使用提交的ID的线程标识列和具有当前时间的线程日期列更新所有适用的行。在更新之后,它选择具有该线程ID的所有行。这样您就不需要声明显式事务。 “锁定”发生在初始更新中。

thread_date列用于确保您不会以孤立的工作项结束。如果项目从队列中拉出然后您的应用程序崩溃会发生什么?您必须能够再次尝试这些工作项。因此,您可以从队列中获取尚未标记为已完成但已分配给具有远程过去的线程日期的线程的所有项目。由你来定义“遥远的”。

答案 3 :(得分:1)

试试这个。验证在UPDATE语句中。

代码

IF EXISTS (SELECT * FROM sys.tables WHERE name = 't1')
    DROP TABLE dbo.t1
GO
CREATE TABLE dbo.t1 (
    ColID       int         IDENTITY,
    [Status]    varchar(20)
)
GO

DECLARE @id             int
DECLARE @initialValue   varchar(20)
DECLARE @newValue       varchar(20)

SET @initialValue = 'Initial Value'

INSERT INTO dbo.t1 (Status) VALUES (@initialValue)
SELECT @id = SCOPE_IDENTITY()

SET @newValue = 'Updated Value'

BEGIN TRAN

UPDATE dbo.t1
SET
    @initialValue = [Status],
    [Status]      = @newValue
WHERE ColID    = @id
  AND [Status] = @initialValue

SELECT ColID, [Status] FROM dbo.t1

COMMIT TRAN

SELECT @initialValue AS '@initialValue', @newValue AS '@newValue'

结果

ColID Status
----- -------------
    1 Updated Value

@initialValue @newValue
------------- -------------
Initial Value Updated Value