Есть ли способ ВЫБРАТЬ и ОБНОВИТЬ строки одновременно? - PullRequest
40 голосов
/ 31 января 2009

Я хотел бы обновить набор строк на основе простых критериев и получить список PK, которые были изменены. Я думал, что смогу сделать что-то подобное, но беспокоюсь о возможных проблемах параллелизма:

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

Если это включено в транзакцию, могут ли возникнуть проблемы с параллелизмом? Или есть лучший способ сделать это?

Ответы [ 9 ]

70 голосов
/ 31 января 2009

Подумайте о рассмотрении предложения OUTPUT :

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 
13 голосов
/ 31 января 2009

Один из способов справиться с этим - сделать это в транзакции и заставить ваш запрос 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 и вашим ОБНОВЛЕНИЕМ.

Когда вы фиксируете транзакцию, блокировка обновления будет снята.

Другой способ справиться с этим - объявить курсор для вашего SELECT с опцией FOR UPDATE. Затем ОБНОВИТЬ ГДЕ ТОК КУРСОРА. Следующее не проверено, но должно дать вам основную идею:

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
10 голосов
/ 09 сентября 2015

Много лет спустя ...

Хорошо принят ответ об использовании предложения OUTPUT. Я должен был выкопать фактический синтаксис, так что вот оно:

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

ДОБАВЛЕНО 14 СЕНТЯБРЯ 2015 ГОДА:

"Могу ли я использовать скалярную переменную вместо табличной переменной?" можно спросить ... Извините, но нет, вы не можете. Вам нужно будет SELECT @SomeID = ID from @UpdatedIDs, если вам нужен один идентификатор.

8 голосов
/ 31 января 2009

Было бы проще сначала выполнить ОБНОВЛЕНИЕ, а затем запустить «ВЫБОР ИД ИЗ ВСТАВЛЕНО».

Посмотрите Советы по SQL для получения дополнительной информации и примеров.

4 голосов
/ 31 января 2009

Может быть, что-то еще, как это?

declare @UpdateTime datetime

set @UpdateTime = getutcdate()

update Table1 set AlertDate = @UpdateTime where AlertDate is null

select ID from Table1 where AlertDate = @UpdateTime
1 голос
/ 12 июля 2017

Я столкнулся с той же проблемой; Я должен обновить сумму кредита, и должен получить измененное время, вместе с деталями кредита из БД. Это в основном

СИНХРОННО / АТОМНО выполнить (ОБНОВИТЬ, затем ПОЛУЧИТЬ) в MYSQL

Я перепробовал много вариантов и нашел один, который решил мою проблему.

1) OPTION_1 ВЫБРАТЬ ОБНОВЛЕНИЕ

Это сохранение блокировки до обновления (SYNC от GET до UPDATE), но мне нужна блокировка после обновления до GET.

2) OPTION_2 Хранимая процедура

Хранимая процедура не будет выполняться синхронно, как redis lua, поэтому для этого также необходим код синхронизации.

3) OPTION_3 Транзакция

Я использовал JPA entityManager, как показано ниже, думал, что до коммита никто не сможет обновить, а до коммита я получу обновленный объект вместе с измененным временем (из БД). Но я не получил последний объект. Только коммит я получил последний.

    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;
}
0 голосов
/ 02 февраля 2009

в 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

0 голосов
/ 31 января 2009

Изменить: мой плохой, вы хотели, чтобы выбор показывал результаты после обновления, а не обновление из выбора.

Вы пробовали суб-выбор?

update mytable set mydate = sysdate 
where mydate in (select mydate from mytable where mydate is null);
0 голосов
/ 31 января 2009

, если она находится внутри транзакции, система блокировки базы данных позаботится о проблемах параллелизма. конечно, если вы используете один из них (по умолчанию mssql использует блокировку, он сообщает, что если вы не переопределите это)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...