2010-09-03 9 views
6

Ich muss eine MsSql-Datenbanktabelle und weitere 8 (identische) Prozesse haben, die parallel auf die gleiche Tabelle zugreifen - eine Auswahl top n machen, diese n Zeilen verarbeiten und a aktualisieren Spalte dieser Zeilen. Das Problem ist, dass ich jede Zeile nur einmal auswählen und verarbeiten muss. Das heißt, wenn ein Prozess in die Datenbank gelangt und die obersten n Zeilen ausgewählt hat, sollte er beim Blockieren des zweiten Prozesses diese Zeilen finden und die Zeilen von n bis 2 * n Zeilen auswählen, und so weiter ...Entsperrte Zeilen in einer "select top n" -Abfrage zurückgeben

Ist es möglich, einige Zeilen zu sperren, wenn Sie sie auswählen, und wenn jemand oben n Zeilen anfordert, die gesperrt sind, um die nächsten Zeilen zurückzugeben und nicht auf die gesperrten zu warten? Scheint wie eine Totale, aber ...

Eine andere Sache, die ich dachte - vielleicht nicht so elegant, aber klingt einfach und sicher, ist in der Datenbank einen Zähler für die Instanzen zu haben, die Selects auf diesem Tisch gemacht. Die erste Instanz, die kommt, inkrementiert den Zähler und wählt top n aus, der nächste inkrementiert den Zähler und wählt Zeilen von n * (i-1) bis n * i und so weiter ...

Hört sich an Wie eine gute Idee? Hast du bessere Vorschläge? Jeder Gedanke wird sehr geschätzt!

Danke für Ihre Zeit.

+3

Es klingt wie Sie eine Tabelle als Warteschlange verwenden? Sie könnten [diesen Artikel] finden (http: // rusanu.com/2010/03/26/using-tables-as-queues /) von [Remus Rusanu] (http://stackoverflow.com/users/105929/remus-rusanu) nützlich. –

+0

Danke für den Link. Es war interessant darüber zu wissen, aber da ich die Zeilen behalten muss, nachdem ich sie ausgewählt habe (das Löschen ist keine Option), ist das für meinen Fall nicht wirklich anwendbar. – Diana

+1

Sie könnten eine "verarbeitete" Bitspalte haben und ein "UPDATE" für ein "DELETE" möglicherweise ersetzen. Wenn Sie sich entscheiden, eine Gegenlösung [diese Antwort] (http://stackoverflow.com/questions/3453411/sql-server-auto-incrementation-that-allows-update-statements/3462957#3462957) zu verwenden, könnte helfen. –

Antwort

6

Hier ist eine Probe I blogged about a while ago:

Der READPAST Hinweis ist, was mehrere Prozesse sorgt für einander nicht blockieren, wenn die Abfrage von Datensätzen zu verarbeiten. Außerdem habe ich in diesem Beispiel ein Bitfeld, um einen Datensatz physisch zu "sperren" - könnte bei Bedarf eine Datetime sein.

DECLARE @NextId INTEGER 
BEGIN TRANSACTION 

-- Find next available item available 
SELECT TOP 1 @NextId = ID 
FROM QueueTable WITH (UPDLOCK, READPAST) 
WHERE IsBeingProcessed = 0 
ORDER BY ID ASC 

-- If found, flag it to prevent being picked up again 
IF (@NextId IS NOT NULL) 
    BEGIN 
     UPDATE QueueTable 
     SET IsBeingProcessed = 1 
     WHERE ID = @NextId 
    END 

COMMIT TRANSACTION 

-- Now return the queue item, if we have one 
IF (@NextId IS NOT NULL) 
    SELECT * FROM QueueTable WHERE ID = @NextId 
+0

Danke für Ihre Antwort, READPAST klingt wie die MsSql Antwort, nach der ich gesucht habe. Wenn SELECT TOP 1 @NextId = ID ein Top 3000 über mehrere Tabellen von Millionen von Datensätzen aus wählt, kann es einige Zeit dauern. Ist es durch den READPAST "garantiert", dass, wenn ich einen anderen Thread habe, der die selbe Auswahl trifft, der zweite die nächsten nicht blockierten Zeilen aufnimmt? – Diana

+0

Ich testete Ihren Ansatz auf einem einfachen Testtisch, wirkt wie ein Charme. Aber auf meiner realen Tabelle (preatty groß, viele Indizes und Statistiken), es funktioniert nur manchmal ... Andere Zeiten, überspringt nicht die gesperrten Zeilen, es "hängt" nur, bis die vorherige select Sperren der Zeilen vorbei ist (Um das zu testen, habe ich einen "ait for delay '00: 00: 10 '" hinzugefügt). Irgendwelche Ideen, warum das passiert? – Diana

+0

@Diana - Wie viele Zeilen versuchen Sie pro Prozess zu sperren? z.B. vielleicht wird eine Tabellensperre gesetzt (Rowlocks werden über einen bestimmten Schwellenwert auf Table Locks aufgerüstet). Wenn du ein Beispielskript schreiben könntest, das zeigt, wie du es machst (wie obv. Mein Beispiel nur für einen einzelnen Datensatz war), wäre das großartig - vielleicht lohnt es sich in einer verknüpften Frage für maximale Sichtbarkeit – AdaTheDev

2

Die einfachste Methode ist row locking zu verwenden:

BEGIN TRAN 

SELECT * 
FROM authors 
WITH (HOLDLOCK, ROWLOCK) 
WHERE au_id = '274-80-9391' 

/* Do all your stuff here while the record is locked */ 

COMMIT TRAN 

Aber wenn Sie Ihre Daten zugreifen und dann das Schließen der Verbindung, werden Sie nicht in der Lage sein, diese Methode zu verwenden.

Wie lange müssen Sie die Zeilen sperren? Der beste Weg könnte tatsächlich sein, wie Sie sagen, um einen Zähler auf die Zeilen setzen, die Sie auswählen (am besten mit OUTPUT Klausel innerhalb einer UPDATE).

+0

Ich muss die Verbindung nach der Auswahl schließen, und die Verarbeitung des Zeilenstapels nimmt einige Zeit in Anspruch (jede Zeile bestimmt das Senden einer E-Mail mit einem 500-KB-Anhang). – Diana

+0

Ich würde sagen, Sie müssen Ihre eigene Methode zum Sperren der Zeilen implementieren (z. B. den Zähler), da Zeilensperren Anforderungen lange genug blockieren können, dass Sie an anderer Stelle Timeout-Fehler haben. Martins Kommentar sieht wie ein Weg vorwärts http://stackoverflow.com/questions/3636950/return-unlocked-rows-in-a-select-top-n-query#comment-3822218 – Codesleuth

0

Die beste Idee, wenn Sie Datensätze auf diese Weise auswählen möchten, wäre ein Zähler in einer separaten Tabelle zu verwenden.

Sie möchten wirklich keine Reihen in einer Produktionsdatenbank ausschließlich für längere Zeit sperren, deshalb würde ich empfehlen, einen Zähler zu verwenden. Auf diese Weise ist nur einer Ihrer Prozesse in der Lage, diese Counter-Nummer zu einem bestimmten Zeitpunkt zu übernehmen (da sie während der Aktualisierung gesperrt wird), wodurch Sie die von Ihnen benötigte Parallelität erhalten.

Wenn Sie eine Hand brauchen, die Tabellen und Prozeduren schreibt, die das tun (einfach und sicher, wie Sie es sagen!), Fragen Sie einfach.

+0

Ok, froh, dass die Idee zu sehen die Theke hört sich gut an und auf den ersten Blick sind keine größeren Strömungen zu sehen. Ich werde für einen Tag oder so mehr darüber nachdenken. Danke für deine Antwort und Hilfe! – Diana

0

EDIT: ahh, vergiss, du arbeitest in einem getrennten Stil. Wie wäre es damit:

UPDATE TOP (@n) QueueTable SET Locked = 1 
OUTPUT INSERTED.Col1, INSERTED.Col2 INTO @this 
WHERE Locked = 0 

<do your stuff> 

Vielleicht suchen Sie für den READPAST Hinweis?

<begin or save transaction> 

INSERT INTO @this (Col1, Col2) 
SELECT TOP (@n) Col1, Col2 
FROM Table1 WITH (ROWLOCK, HOLDLOCK, READPAST) 

<do your stuff> 

<commit or rollback> 
Verwandte Themen