【问题标题】:Is there a way to SELECT and UPDATE rows at the same time?有没有办法同时选择和更新行?
【发布时间】:2010-10-04 14:11:14
【问题描述】:

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

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

如果它包含在事务中,是否会出现任何并发问题?或者有更好的方法吗?

【问题讨论】:

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


【解决方案1】:

先执行 UPDATE 然后运行“SELECT ID FROM INSERTED”会更容易。

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

【讨论】:

  • 这对于简单查询来说是个好主意!
  • 这可以在触发器中工作,但它可以在存储过程或其他执行上下文中工作吗?
  • @aheho,通过在更新语句中使用输出子句,可以在 proc 中使用相同的基本方法。
【解决方案2】:

考虑查看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 

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      编辑:我的错,您希望选择在更新后显示结果,而不是从选择中更新。

      您是否尝试过子选择?

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

      【讨论】:

        【解决方案5】:

        处理此问题的一种方法是在事务中执行此操作,并使您的 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 声明一个游标。然后更新光标的当前位置。以下内容未经测试,但应该可以为您提供基本思路:

        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 
          SET AlertDate = @UpdateTime  --set value
          WHERE CURRENT OF cur1;
        
          FETCH NEXT FROM cur1;
          
        END
        

        【讨论】:

        • +1 用于 UPDLOCK,这是解决此问题的正确方法,简而言之,事务不会导致死锁
        • 这是否会导致所有行都获得相同的日期时间值(就像在单个 SQL 调用中一样)?如果没有,您应该将循环之前的时间放入变量中,并将 AltertDate 设置为变量。如果有问题,请在其中进行编辑。
        【解决方案6】:

        也许更像这样?

        declare @UpdateTime datetime
        
        set @UpdateTime = getutcdate()
        
        update Table1 set AlertDate = @UpdateTime where AlertDate is null
        
        select ID from Table1 where AlertDate = @UpdateTime
        

        【讨论】:

        • 这有一个很好的优势,即使您不在事务中执行此操作,您也很难非常获得更改其他过程的结果。
        【解决方案7】:

        在 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

        【讨论】:

          【解决方案8】:

          多年后...

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

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

          添加 2015 年 9 月 14 日:

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

          【讨论】:

            【解决方案9】:

            我也遇到过同样的问题;我必须更新信用额度,并且必须从 DB 获取修改时间以及信用详细信息。基本上是

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

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

            1) OPTION_1 选择更新

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

            2) OPTION_2 存储过程

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

            3) OPTION_3 交易

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

                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();
                } 
            

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

            private Object getLock(final EntityManager entityManager, final String Id){
            
                entityManager.getTransaction().begin();
                Object obj_acquire = entityManager.createNativeQuery("SELECT GET_LOCK('" + Id + "', 10)").getSingleResult();
                entityManager.getTransaction().commit();
                return obj_acquire;
            }
            
            
            private Object releaseLock(final EntityManager entityManager, final String Id){
            
                entityManager.getTransaction().begin();
                Object obj_release = entityManager.createNativeQuery("SELECT RELEASE_LOCK('" + Id + "')").getSingleResult();
                entityManager.getTransaction().commit();
                return obj_release;
            }
            

            【讨论】:

            • MySQL ?问题标记为SQL SERVER
            • 哦,我的错,答案是 MySQL
            猜你喜欢
            • 2021-12-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-10-12
            相关资源
            最近更新 更多