有没有办法同时SELECT和UPDATE行?

时间:2009-01-30 22:12:39

标签: sql sql-server tsql sql-server-2008

我想根据一个简单的标准更新一组行,并获取已更改的PK列表。我以为我可以做这样的事情,但我担心可能的并发问题:

SELECT Id FROM Table1 WHERE AlertDate IS NULL;
UPDATE Table1 SET AlertDate = getutcdate() WHERE AlertDate IS NULL;

如果包含在事务中,是否存在可能发生的并发问题?或者有更好的方法吗?

9 个答案:

答案 0 :(得分:69)

考虑查看OUTPUT clause

USE AdventureWorks2012;  
GO  

DECLARE @MyTableVar table(  
    EmpID int NOT NULL,  
    OldVacationHours int,  
    NewVacationHours int,  
    ModifiedDate datetime);  

UPDATE TOP (10) HumanResources.Employee  
SET VacationHours = VacationHours * 1.25,  
    ModifiedDate = GETDATE()   
OUTPUT inserted.BusinessEntityID,  
       deleted.VacationHours,  
       inserted.VacationHours,  
       inserted.ModifiedDate  
INTO @MyTableVar;  

--Display the result set of the table variable.  
SELECT EmpID, OldVacationHours, NewVacationHours, ModifiedDate  
FROM @MyTableVar;  
GO  
--Display the result set of the table.  
SELECT TOP (10) BusinessEntityID, VacationHours, ModifiedDate  
FROM HumanResources.Employee;  
GO 

答案 1 :(得分:13)

处理此问题的一种方法是在事务中执行此操作,并使SELECT查询对所选行执行更新锁定,直到事务完成。

BEGIN TRAN

SELECT Id FROM Table1 WITH (UPDLOCK)
WHERE AlertDate IS NULL;

UPDATE Table1 SET AlertDate = getutcdate() 
WHERE AlertDate IS NULL;

COMMIT TRAN 

这消除了并发客户端更新SELECT和UPDATE之间选择的行的可能性。

提交事务时,将释放更新锁。

另一种处理方法是使用FOR UPDATE选项为SELECT声明一个游标。然后更新当前CURSOR的当前。以下内容未经过测试,但应该为您提供基本的想法:

DECLARE cur1 CURSOR FOR
  SELECT AlertDate FROM Table1 
  WHERE AlertDate IS NULL
  FOR UPDATE;

DECLARE @UpdateTime DATETIME

SET @UpdateTime = GETUTCDATE()

OPEN cur1;

FETCH NEXT FROM cur1;

WHILE @@FETCH_STATUS = 0
BEGIN

  UPDATE Table1 AlertDate = @UpdateTime
  WHERE CURRENT OF cur1;

  FETCH NEXT FROM cur1;

END

答案 2 :(得分:10)

许多年后......

使用OUTPUT子句的公认答案是好的。我不得不挖掘实际的语法,所以这里是:

DECLARE @UpdatedIDs table (ID int)
UPDATE 
    Table1 
SET 
    AlertDate = getutcdate() 
OUTPUT
    inserted.Id
INTO
    @UpdatedIDs
WHERE 
    AlertDate IS NULL;

ADDED 2015年9月14日:

"我可以使用标量变量而不是表变量吗?"有人可能会问...抱歉,但不,你不能。如果您需要一个ID,则必须SELECT @SomeID = ID from @UpdatedIDs

答案 3 :(得分:8)

首先执行UPDATE会更容易,然后运行'SELECT ID FROM INSERTED'。

请查看SQL Tips了解更多信息和示例。

答案 4 :(得分:4)

也许更像这样的事情?

declare @UpdateTime datetime

set @UpdateTime = getutcdate()

update Table1 set AlertDate = @UpdateTime where AlertDate is null

select ID from Table1 where AlertDate = @UpdateTime

答案 5 :(得分:1)

我遇到了同样的问题;我必须更新信用额度,并且必须修改时间以及来自数据库的信用额度。基本上是

在MYSQL中同步/原子执行(更新然后获取)

我尝试了很多选项,找到了一个解决了我的问题。

1)OPTION_1选择更新

这是维持锁定直到更新(从GET到UPDATE的SYNC),但我需要在更新后锁定直到GET。

2)OPTION_2存储过程

存储过程不会像redis lua那样同步执行,所以我们还需要同步代码才能执行。

3)OPTION_3交易

我在下面使用了JPA entityManager,认为在提交之前没有人可以更新,并且在提交之前我将获得更新的对象以及修改的时间(来自DB)。但我没有得到最新的对象。只提交我得到最新的。

client.DefaultRequestHeaders.Remove("X-RequestHeader");
client.DefaultRequestHeaders.Add("X-RequestHeader", $"request ServiceId=\"{serviceId}\"");

4)OPTION_4 LOCK解决了这个问题,所以在更新之前我获得了锁;然后在GET之后我释放了锁。

    try {
        entityManager.getTransaction().begin();
        //entityManager.persist(object);
        int upsert = entityManager.createNativeQuery(
        "update com.bill.Credit c set c.balance = c.balance - ?1
          where c.accountId = ?2 and c.balance >= ?1").executeUpdate(); 
             //c.balance >= ? for limit check
        Credit newCredit = entityManager.find(Credit.class, "id");
        entityManager.refresh(newCredit); //SHOULD GET LATEST BUT NOT
        entityManager.getTransaction().commit();
    } finally {     
        entityManager.unwrap(Session.class).close();
    } 

答案 6 :(得分:0)

如果它在事务内部,数据库锁定系统将处理并发问题。当然,如果你使用一个(mssql默认是它使用锁,所以如果你不重写那么它就说明了)

答案 7 :(得分:0)

编辑:我的不好,您希望select在更新后显示结果,而不是从select更新。

您是否尝试过子选择?

update mytable set mydate = sysdate 
where mydate in (select mydate from mytable where mydate is null);

答案 8 :(得分:0)

在SQL 2008中引入了新的TSQL语句“MERGE”,它根据与源表的连接结果对目标表执行插入,更新或删除操作。您可以根据另一个表中的差异在一个表中插入,更新或删除行来同步两个表。

http://blogs.msdn.com/ajaiman/archive/2008/06/25/tsql-merge-statement-sql-2008.aspx http://msdn.microsoft.com/en-us/library/bb510625.aspx