2009-01-30 12 views
34

Chciałbym zaktualizować zestaw wierszy na podstawie prostych kryteriów i uzyskać listę PK, które zostały zmienione. Myślałem, że mogę po prostu zrobić coś takiego, ale martwię się o możliwych problemach współbieżności:Czy istnieje sposób SELECT i UPDATE wierszy w tym samym czasie?

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

Jeśli to jest opakowane w transakcji są jakieś problemy współbieżności, które mogą wystąpić? Czy istnieje lepszy sposób to zrobić?

Odpowiedz

65

Rozważ oglądanie OUTPUT clause capability of UPDATE (również DELETE i INSERT). Przykład z połączonej stronie MSDN:

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

Dotyczy "UPDATE SET ... FROM ... WHERE ...."? – Kiquenet

8

Najpierw byłoby łatwiej wykonać aktualizację, a następnie uruchomić "SELECT ID FROM INSERTED".

Aby uzyskać więcej informacji i przykładów, zobacz SQL Tips.

0

czy to wewnątrz transakcji, system blokujący bazie zajmie się kwestiami współbieżności. oczywiście, jeśli użyjesz jednego (domyślnie mssql używa blokady, to znaczy, że tego nie zastąpisz)

0

Edycja: moja zła, chciałaś, żeby wybrane pokazywało wyniki po aktualizacji, a nie aktualizację z wyboru.

Czy próbowałeś dokonać sub-select?

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

Jednym ze sposobów, aby obsłużyć to jest to zrobić w transakcji, i zrobić kwerendę wybierającą wziąć blokady Update w wybranych wierszach aż zakończeniu transakcji.

BEGIN TRAN 

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

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

COMMIT TRAN 

Eliminuje to możliwość, że jednoczesne klient aktualizuje wiersze wybrane w tej chwili między swoją SELECT i aktualizację.

Po zatwierdzeniu transakcji blokady aktualizacji zostaną zwolnione.

Innym sposobem na obsłużenie tego jest zadeklarowanie kursora dla SELECT przy opcji FOR UPDATE. Następnie AKTUALIZUJ GDZIE AKTUALNY KURSOR. Poniższe elementy nie są testowane, ale powinny dać ci podstawowy pomysł:

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 
+3

+1 dla UPDLOCK, jest to prawidłowe rozwiązanie tego problemu, a w krótkich transakcjach nie doprowadzi do zakleszczeń. –

+0

Czy spowoduje to, że wszystkie wiersze uzyskają tę samą wartość datetime (jak w przypadku pojedynczego wywołania SQL)? Jeśli nie, powinieneś dostać czas przed pętlą do zmiennej i po prostu ustawić AltertDate na zmienną. Jeśli to jest problem, edytuj to w. – Thorsten

+1

+1 dla UPDLOCK i -1 dla CURSOR. – jsuddsjr

3

Być może coś bardziej podobnego do tego?

declare @UpdateTime datetime 

set @UpdateTime = getutcdate() 

update Table1 set AlertDate = @UpdateTime where AlertDate is null 

select ID from Table1 where AlertDate = @UpdateTime 
+1

Ma to tę zaletę, że nawet jeśli nie zrobisz tego w ramach transakcji, będzie to * bardzo * trudne, że uzyskasz wyniki innego procesu zmienionego. –

0

w SQL 2008 nowy rachunek TSQL „scalić” wprowadza która wykonuje wstawiania, aktualizacji lub usuwania operacji na tabeli docelowej na podstawie wyników sprzężenia z tabeli źródłowej. Możesz zsynchronizować dwie tabele, wstawiając, aktualizując lub usuwając wiersze w jednej tabeli na podstawie różnic znalezionych w drugiej tabeli.

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

6

Wiele lat później ...

Przyjęty odpowiedź użyciu klauzula wyjście jest dobre.Musiałem wykopać rzeczywiste składni, więc tutaj jest:

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

DODANO 14 września 2015: „Czy mogę użyć zmiennej skalarnej zamiast zmiennej tabeli”

można zapytać ... Przepraszam, ale nie, nie możesz. Jeśli potrzebujesz pojedynczego identyfikatora, musisz mieć SELECT @SomeID = ID from @UpdatedIDs.

1

Mam do czynienia z tym samym problemem; Muszę zaktualizować kwotę kredytu, i muszę uzyskać zmodyfikowany czas, wraz z danymi kredytowymi z DB. To jest w zasadzie

synchronicznie/atomowo wykonać (UPDATE następnie GET) w MySQL

Próbowałem wielu opcji i znaleźć taki, który rozwiązać mój problem.

1) OPTION_1 SELECT FOR UPDATE

ten utrzymuje blokadę aż aktualizacji (synchronicznie z dostać się do aktualizacji), ale muszę zablokować po aktualizacji aż do GET.

2) OPTION_2 składowana procedura

Procedura składowana nie wykona synchronicznie jak Redis Lua, więc tam też musimy kod synchronizacji do wykonywania tego.

3) OPTION_3 transakcja

Używałem WZP EntityManager jak poniżej, pomyślałem, że zanim zdecydują nikt nie może zaktualizować, a przed zatwierdzeniem i otrzyma zaktualizowaną obiektu wraz ze zmodyfikowanym czasie (db). Ale nie dostałem najnowszego obiektu. Dopuść tylko, że mam najnowsze.

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) OPCJA_4 LOCK rozwiązał problem, więc przed aktualizacją uzyskałem zamek; potem po GET zwolniłem blokadę.

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

*** MySQL ***? Pytanie jest oznaczone dla 'SQL SERVER' – Kiquenet

+0

OHH MY BAD, Odpowiedź dotyczy MySQL –

Powiązane problemy