2016-01-08 10 views
7

Gemäß der UPDATE documentation erwirbt eine UPDATE immer eine exklusive Sperre für die gesamte Tabelle. Ich frage mich jedoch, ob die exklusive Sperre erworben wird, bevor die zu aktualisierenden Zeilen bestimmt werden oder nur kurz vor dem eigentlichen Update.Ist eine Transaktion, die nur eine einzige isolierte Tabelle aktualisiert?

Mein konkretes Problem ist, dass ich eine verschachtelte SELECT in meinem UPDATE aussehen:

UPDATE Tasks 
SET Status = 'Active' 
WHERE Id = (SELECT TOP 1 Id 
      FROM Tasks 
      WHERE Type = 1 
       AND (SELECT COUNT(*) 
        FROM Tasks 
        WHERE Status = 'Active') = 0 
      ORDER BY Id) 

Jetzt frage ich mich, ob es wirklich ist garantiert, dass es genau eine Aufgabe ist mit Status = 'Active' danach, ob in parallel die gleiche Aussage kann mit einer anderen Art ausgeführt werden:

UPDATE Tasks 
SET Status = 'Active' 
WHERE Id = (SELECT TOP 1 Id 
      FROM Tasks 
      WHERE Type = 2   -- <== The only difference 
       AND (SELECT COUNT(*) 
        FROM Tasks 
        WHERE Status = 'Active') = 0 
      ORDER BY Id) 

Wenn für beide Aussagen die Reihen bestimmt würde sich ändern, bevor die Sperre erworben wird, könnte ich am Ende mit zwei aktiven Aufgaben, die ich verhindern muss.

Wenn dies der Fall ist, wie kann ich das verhindern? Kann ich das verhindern, ohne die Transaktionsstufe auf SERIALIZABLE zu setzen oder mit Sperrhinweisen zu verfahren?

Von der Antwort auf Is a single SQL Server statement atomic and consistent? habe ich gelernt, dass das Problem auftritt, wenn der verschachtelte SELECT auf eine andere Tabelle zugreift. Ich bin mir jedoch nicht sicher, ob ich mich um dieses Problem kümmern muss, wenn nur die aktualisierte Tabelle betroffen ist.

+1

dieser Dokumentation ist sowieso falsch. Updates sperren im Allgemeinen nicht die gesamte Tabelle. –

+0

Hm, ok, danke. Wo finde ich dann die richtige Dokumentation? – lex82

+0

Die Dokumentation sagt nicht, dass ein 'UPDATE' die gesamte Tabelle sperrt. Es besagt, dass es eine exklusive Sperre erwirbt, aber eine exklusive Sperre muss nicht auf der gesamten Tabelle sein. –

Antwort

0

Nein, mindestens die verschachtelte Select-Anweisung kann verarbeitet werden, bevor das Update gestartet wird und Sperren erworben werden. Um sicherzustellen, dass keine andere Abfrage dieses Update beeinträchtigt, muss die Transaktionsisolationsstufe auf SERIALIZABLE festgelegt werden.

diesem Artikel (und die Serie es ein Teil ist) erklärt sehr gut die Feinheiten der Parallelität in SQL Server:

http://sqlperformance.com/2014/02/t-sql-queries/confusion-caused-by-trusting-acid

1

Das ist keine Antwort auf Ihre Frage ... Aber Ihre Frage ist Schmerz für meine Augen :)

;WITH cte AS 
(
    SELECT *, RowNum = ROW_NUMBER() OVER (PARTITION BY [type] ORDER BY id) 
    FROM Tasks 
) 
UPDATE cte 
SET [Status] = 'Active' 
WHERE RowNum = 1 
    AND [type] = 1 
    AND NOT EXISTS(
      SELECT 1 
      FROM Tasks 
      WHERE [Status] = 'Active' 
     ) 
+1

Es ist möglich, dass die ursprüngliche Abfrage des OP effizienter ist als diese Version. Hinweis: Die Lösung des OP sollte 'NOT EXISTS' anstelle von' (SELECT COUNT (*)...) 'Verwenden. –

+0

Ich mag die RowNum, aber ich finde IsActive verwirrend, weil es nicht auf die bestimmte Zeile bezieht. Ich würde eine bedingte Anweisung verwenden, aber ich bin ziemlich sicher, dass dies genau die Probleme ergeben wird, die ich vermeiden möchte, weil die Tabelle nicht gesperrt wird. – lex82

+0

@Gordon Linoff danke für den Hinweis – Devart

6

Wenn Sie genau eine Aufgabe wollen mit statisch = aktiv, dann den Tisch, um dies zu gewährleisten, ist wahr. Verwenden Sie einen eindeutigen Index gefiltert:

create unique index unq_tasks_status_filter_active on tasks(status) 
    where status = 'Active'; 

Eine zweite gleichzeitige update scheitern könnte, aber Sie werden der Eindeutigkeit gewährleistet werden. Ihr Anwendungscode kann solche fehlgeschlagenen Updates verarbeiten und erneut versuchen.

Es kann gefährlich sein, sich auf die tatsächlichen Ausführungspläne der Aktualisierungen zu verlassen. Daher ist es sicherer, dass die Datenbank solche Validierungen durchführt. Die zugrunde liegenden Implementierungsdetails können je nach Umgebung und Version von SQL Server variieren. Was beispielsweise in einer Single-Thread-Umgebung mit einem einzelnen Prozessor funktioniert, funktioniert möglicherweise nicht in einer parallelen Umgebung. Was mit einer Isolationsstufe funktioniert, funktioniert möglicherweise nicht mit einer anderen.

EDIT:

Und ich kann nicht widerstehen. Aus Gründen der Effizienz, betrachten Sie die Abfrage als Schreiben:

UPDATE Tasks 
    SET Status = 'Active' 
    WHERE NOT EXISTS (SELECT 1 
         FROM Tasks 
         WHERE Status = 'Active' 
        ) AND 
      Id = (SELECT TOP 1 Id 
       FROM Tasks 
       WHERE Type = 2   -- <== The only difference 
       ORDER BY Id 
       ); 

Dann Platz Indizes auf Tasks(Status) und Tasks(Type, Id). In der Tat, mit der richtigen Abfrage, können Sie feststellen, dass die Abfrage so schnell ist (trotz der Aktualisierung des Index), dass Ihre Sorge um aktuelle Updates stark gemildert wird. Dies würde eine Race-Bedingung nicht lösen, aber es könnte es zumindest selten machen.

Und wenn Sie Fehler aufnimmst, dann mit dem einzigartigen gefilterten Index, könnten Sie einfach tun:

UPDATE Tasks 
    SET Status = 'Active' 
    WHERE Id = (SELECT TOP 1 Id 
       FROM Tasks 
       WHERE Type = 2   -- <== The only difference 
       ORDER BY Id 
       ); 

Dies wird einen Fehler zurück, wenn eine Zeile bereits aktiv ist.

Hinweis: Alle diese Abfragen und Konzepte können auf "eine aktive pro Gruppe" angewendet werden. Diese Antwort befasst sich mit der Frage, die Sie gestellt haben. Wenn Sie ein Problem mit "einer aktiven Person pro Gruppe" haben, sollten Sie eine andere Frage stellen.

+0

Vielen Dank für das darauf hin! Leider ist dies in der Umgebung, in der ich arbeite, nicht möglich. Meine Aussage wurde trotzdem vereinfacht, daher bin ich mir nicht sicher, ob ein solcher Index helfen würde, wenn ich ihn einrichten könnte. Ich würde gerne verstehen, wie das UPDATE verarbeitet wird. – lex82

+1

@ lex82 - wenn die Vereinfachung z.B. dass es nur einen aktiven pro (Kombination von einer oder mehreren anderen Spalten) geben kann, dann hätten Sie nur diese Spalten im Index und nicht im Status. –