Wie sperre Sie Tabellen in SQL Server 2005, und soll ich tun es auch?
-
09-06-2019 - |
Frage
Dieser wird nehmen Sie sich etwas zu erklären. Was ich getan habe, ist es, eine bestimmte benutzerdefinierte Nachrichten-Warteschlange erstellen in SQL Server 2005. Ich habe eine Tabelle mit Nachrichten, die Zeitstempel für beide Bestätigung und Abschluss enthält. Die gespeicherte Prozedur, die Anrufer auszuführen, um die nächste Nachricht in der Warteschlange zu erhalten, erkennt auch die Nachricht. So weit, ist es gut. Nun, wenn das System erfährt eine enorme Menge an Transaktionen (Tausend pro Minute), ist es nicht möglich, dass eine Nachricht von einer anderen Ausführung der gespeicherten Prozedur anerkannt werden, während ein anderer bereit ist, so sich selbst? Lassen Sie mich zeigen, meine SQL-Code in den gespeicherten proc helfen:
--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);
--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId
--Select the entire message
...
...
In dem obigen Code, konnte nicht eine andere gespeicherte Prozedur zur gleichen Zeit laufen die gleiche ID erhalten und versuchen, sie zugleich zu erkennen? Könnte ich (oder sollte ich) eine Art von Sperr eine andere gespeicherte Prozedur aus Quittieren von Meldungen, die eine andere gespeicherte Prozedur abfragt zu verhindern implementieren?
Wow, hat irgendetwas davon sogar Sinn machen? Es ist ein bisschen schwer zu Worte zu fassen ...
Lösung
So etwas
--Grab the next message id
begin tran
declare @MessageId uniqueidentifier
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands with(holdlock, updlock);
--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId
-- some error checking
commit tran
--Select the entire message
...
...
Andere Tipps
Dies scheint wie die Art von Situation, in der OUTPUT
nützlich sein kann, :
-- Acknowledge and grab the next message
declare @message table (
-- ...your `ActionMessages` columns here...
)
update ActionMessages
set AcknowledgedTime = getdate()
output INSERTED.* into @message
where ActionMessageId in (select top(1) ActionMessageId from UnacknowledgedDemands)
and AcknowledgedTime is null
-- Use the data in @message, which will have zero or one rows assuming
-- `ActionMessageId` uniquely identifies a row (strongly implied in your question)
...
...
, wir aktualisieren und die Zeile in der gleichen Operation greifen, die die Abfrage-Optimierer sagt genau , was wir tun, so dass sie die detaillierteste Sperre wählen es kann und halten es für die möglichst kurze Zeit. (Obwohl die Spaltenpräfix INSERTED
ist, wie OUTPUT
Triggern, ausgedrückt in Bezug auf die UPDATE
dergleichen ist die Zeile zu löschen und die neue eingefügt wird.)
würde ich mehr Informationen über Ihre ActionMessages
und UnacknowledgedDemands
Tabellen (views / TVFs / was auch immer) benötigen, kein größeres Wissen über SQL Server automatische Verriegelung zu erwähnen, zu sagen, ob die and AcknowledgedTime is null
Klausel erforderlich. Es ist dort gegen eine Racebedingung zwischen dem Unter auswählen und das Update zu verteidigen. Ich bin sicher, dass es nicht notwendig wäre, wenn wir von ActionMessages
Auswahl selbst (zum Beispiel where AcknowledgedTime is null
mit einem top
auf dem update
anstelle des Sub-select auf UnacknowledgedDemands
). Ich erwarte, dass selbst wenn es nicht notwendig ist, ist es harmlos.
Beachten Sie, dass OUTPUT
in SQL Server 2005 und höher. Das ist, was Sie sagten, Sie verwendet haben, aber wenn die Kompatibilität mit geriatrischen SQL Server 2000 installiert erforderlich waren, würden Sie wollen einen anderen Weg gehen.
@Kilhoffer:
Das ganze SQL-Batch wird vor der Ausführung analysiert, so SQL weiß, dass Sie ein Update auf die Tabelle sowie wählen Sie aus der es tun werden.
Edit: Auch wird SQL nicht notwendigerweise die gesamte Tabelle sperren - es könnte nur die erforderlichen Zeilen sperren. Siehe hier für einen Überblick über Sperren in SQL Server.
Statt expliziter Sperren, die oft von SQL Server auf höhere Granularität eskalieren als gewünscht ist, warum nicht versuchen, nur diesen Ansatz:
declare @MessageId uniqueidentifier
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId and AcknowledgedTime is null
if @@rowcount > 0
/* acknoweldge succeeded */
else
/* concurrent query acknowledged message before us,
go back and try another one */
Je weniger Sie sperren - die höhere Parallelität Sie haben
.Wenn Sie wirklich die Verarbeitung werden die Dinge one-by-one? Sollten Sie nicht SQL Server nur haben alle unbestätigten Meldungen mit heutigem Datum bestätigen und sie zurückkehren? (Alle auch in einer Transaktion natürlich)
Sie wollen Ihren Code in einer Transaktion wickeln, dann SQL Server behandelt die entsprechenden Zeilen oder Tabellen sperren.
begin transaction
--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);
--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId
commit transaction
--Select the entire message
...