8

Ich habe eine gespeicherte Prozedur in mysql das ist eine Aufgabe, die so synchronisiert werden muss, dass, wenn zwei Anwendung ruft die gespeicherte Prozedur, nur einer kann auf einen Abschnitt des Codes zugreifen, um die Aufgabe auszuführen Der andere wird so lange blockiert, bis der erste die Aufgabe beendet hat.Synchronisierte gespeicherte Prozedur Ausführung in Mysql

DELIMITER $$ 
CREATE PROCEDURE SP_GEN_ID(IN NAME VARCHAR(20)) 

BEGIN 
    DECLARE maxLen int default 0; 
START TRANSACTION; 
    #the section of code that needs to be synchronized 
COMMIT 
END; 
$$ 

DELIMITER ; 

Also, wenn zwei Anwendungen die gespeicherte Prozedur gleichzeitig aufrufen, muss die Aufgabe synchronisiert werden.

a. Aber Starten TRANSACTION und COMMIT synchronisiert nicht die Ausführung.

b. Und LOCK TABLES tableA kann nicht in gespeicherten Prozedur verwendet werden, um die Synchronisierung zu gewährleisten.

c. Ich habe versucht, den Aufruf der gespeicherten Prozedur auf Anwendungsebene zu synchronisieren. Ich verwendete

boost_interprocess scoped_lock lock();

Es funktionierte völlig in Ordnung im Boost 1,41

Aber Mutex Inter Verriegelung wird nicht in der Boost-1,34-Bibliothek unterstützt, das ist das, was in meinem Fall zur Verfügung steht.

Gibt es eine Möglichkeit, den Codeabschnitt der gespeicherten Prozedur so zu synchronisieren, dass bei zwei gleichzeitig ausgeführten Anrufen blockiert wird, bevor der andere ausgeführt wird?

(hinzugefügt die folgenden) bearbeitet Code: um eine Idee zu geben, was ich im synchronisierten Block der gespeicherten Prozedur ausführen möchte.

Er erhält die zuletzt zugewiesene ID, erhöht sie um eins und prüft, ob sie nicht für einen anderen 'Name'-Datensatz verwendet wird. Wenn eine gültige ID gefunden wird, aktualisieren Sie die zuletzt zugewiesene ID-Datensatztabelle und ordnen Sie diese dann dem angegebenen 'Namen' zu.

DELIMITER $$ 
CREATE PROCEDURE SP_GEN_ID(IN NAME VARCHAR(20)) 

BEGIN 
    DECLARE maxLen int default 0; 
START TRANSACTION; 
    #the section of code that needs to be synchronized 
    SELECT lastid into lastgenerated FROM DB_last_id WHERE key = 'NAME_ID';  
findid_loop: 
    LOOP 
    set lastid = lastid + 1; 
    #this is to check whether it was assigned with someother name before. 
    IF not EXISTS (SELECT 1 FROM user_name_id WHERE name_id = lastgenerated) then 
        set nameid = lastgenerated; 
        set found = true; 
        LEAVE findid_loop; 
      END IF; 

      #for loop limit check 
      IF (counter < loopLimit) then 
        set counter = counter + 1; 
        ITERATE findid_loop; 
      ELSE 
        #reached the limit and not found. 
        LEAVE findid_loop; 
      END IF; 
    END LOOP findid_loop; 

    #if a valid id, update last id and assign to name. 
    IF (found) THEN 
      #update the id. 
      update DB_last_id set lastid = nameid where key = 'NAME_ID'; 
      insert into user_name_id values (nameid ,name); 
    ELSE 
      #return an empty string for the application to take action on the failure. 
      set nameid = ''; 
    END IF; 
#end transaction here. 
COMMIT 

END; 
$$ 

DELIMITER ; 
+0

Welche Speicher-Engine verwenden Ihre Tabellen?Sie müssen InnoDB sein, damit Transaktionen/Sperren funktionieren. – eggyal

+0

Die Speicher-Engine ist InnoDB für die Tabellen. Aber es scheint immer noch nicht zu funktionieren. Um dies zu überprüfen, habe ich geschlafen (15), um direkt nach START TRANSACTION auf 15 Sekunden zu warten. Wenn ich die gespeicherte Prozedur gleichzeitig anrufe, scheinen beide nach 15 Sekunden wieder herauszukommen. Wenn START TRANSACTION die Synchronisierung sicherstellt, sollte der zweite Aufruf der gespeicherten Prozedur nach 30 Sekunden oder so enden. Recht? (15 Sekunden für den ersten Anruf, um zu überholen, dann geht der zweite in die START TRANSACTION und schläft für 15 Sekunden) –

+0

Was genau ist der Code, den Sie synchronisieren möchten? Wenn Sie möchten, dass SQL-Operationen nur atomar sind, sollte dies durch Ausführen in einer Transaktion erreicht werden. Wenn Sie einen anderen Status wie Systemvariablen beeinflussen, müssen Sie eine Sperre für eine Tabelle/einen Datensatz erhalten, die diesem Zweck zugewiesen sind. Und das Fehlen einer Verzögerung bei Ihrem zweiten Anruf ist nicht notwendigerweise ein Hinweis darauf, dass die Sperre fehlgeschlagen ist (es könnten beispielsweise Cache-Probleme auftreten, die den zweiten Anruf schneller ausführen als der erste, oder der zweite Anruf könnte eine andere Ausführung annehmen Weg zum ersten). Bitte geben Sie den vollständigen Code an. – eggyal

Antwort

8

Das Starten einer Transaktion mit START TRANSACTION startet es eigentlich nicht. Der erste Tabellenzugriff nach START TRANSACTION funktioniert. Das Öffnen einer Transaktion ist auch kein Mittel zur Steuerung des gemeinsamen Zugriffs. Wenn Sie genau das brauchen, können Sie sich auf das Advisory Locks-System verlassen, das MySQL durch GET_LOCK(), RELEASE_LOCK() und einige andere verwandte Funktionen bietet.

Eine alternative Möglichkeit zur Implementierung der Steuerung des gemeinsamen Zugriffs durch Transaktionen dieses Mal wäre die Verwendung exklusiver Zeilensperren. Da SELECT Anweisungen in InnoDB nicht-locking sind, startet das Ausgeben einer solchen Abfrage eine Transaktion, setzt jedoch weder Sperren fest noch berücksichtigt sie bereits existierende. Wenn eine SELECT-Anweisung tatsächlich blockiert wird, wenn eine frühere Transaktion auf derselben Information (Zeile) ausgeführt wird, müssen Sie die FOR UPDATE-Klausel verwenden. Zum Beispiel:

START TRANSACTION; 
SELECT lastid into lastgenerated FROM DB_last_id WHERE key = 'NAME_ID' FOR UPDATE; 
... 

Mit dieser Konstruktion wird es nie zwei gleichzeitige Transaktionen auf derselben 'NAME_ID' vorbei an der SELECT Anweisung arbeiten, die explizit eine Sperrlese auszuführen wurde gesagt.

+0

das hat auch gut funktioniert! Vielen Dank. –

9

Wie oben in meinen Kommentaren erwähnt, Sie sollte finden, dass eine Transaktion für die meisten Bedürfnisse ausreicht; wenn Sie müssen jedoch explizit warten, bis der andere Anruf beendet hat, verwenden GET_LOCK(str,timeout):

Tries ein Schloss mit einem Namen durch den String str, mit einem Timeout von timeout gegeben zu erhalten Sekunden. Gibt 1 zurück, wenn die Sperre erfolgreich empfangen wurde, 0 wenn der Versuch fehlschlägt (z. B. weil ein anderer Client den Namen zuvor gesperrt hat) oder NULL, wenn ein Fehler aufgetreten ist (z. B. nicht genügend Arbeitsspeicher oder der Thread wurde mit mysqladmin kill beendet) . Wenn Sie eine Sperre mit GET_LOCK() erhalten haben, wird sie freigegeben, wenn Sie RELEASE_LOCK() ausführen, eine neue GET_LOCK() ausführen, oder Ihre Verbindung wird beendet (entweder normal oder abnormal). Sperren, die mit GET_LOCK() erhalten werden, interagieren nicht mit Transaktionen.Das heißt, das Festschreiben einer Transaktion gibt keine solchen Sperren frei, die während der Transaktion erhalten wurden.

Diese Funktion kann zum Implementieren von Anwendungssperren oder zum Simulieren von Datensatzsperren verwendet werden. Die Namen sind serverweit gesperrt. Wenn ein Name von einem Client gesperrt wurde, blockiert GET_LOCK() jede Anforderung eines anderen Clients für eine Sperre mit demselben Namen. Dadurch können Clients, die einem bestimmten Sperrnamen zustimmen, den Namen für kooperative Advisory-Sperren verwenden. Beachten Sie jedoch, dass damit auch ein Client, der nicht zu den kooperierenden Clients gehört, versehentlich oder absichtlich einen Namen sperren kann und somit verhindern kann, dass ein kooperierender Client diesen Namen sperrt. Eine Möglichkeit, die Wahrscheinlichkeit zu verringern, besteht darin, Sperrnamen zu verwenden, die datenbankspezifisch oder anwendungsspezifisch sind. Verwenden Sie zum Beispiel Lock Namen der Form db_name.str oder app_name.str.

mysql>SELECT GET_LOCK('lock1',10); 
     -> 1 
mysql>SELECT IS_FREE_LOCK('lock2'); 
     -> 1 
mysql>SELECT GET_LOCK('lock2',10); 
     -> 1 
mysql>SELECT RELEASE_LOCK('lock2'); 
     -> 1 
mysql>SELECT RELEASE_LOCK('lock1'); 
     -> NULL 

Der zweite RELEASE_LOCK() Aufruf gibt NULL weil die Sperre 'lock1' automatisch durch den zweiten GET_LOCK() Anruf freigegeben wurde.

Wenn mehrere Clients auf eine Sperre warten, ist die Reihenfolge, in der sie sie erfassen, nicht definiert und hängt von Faktoren wie der verwendeten Thread-Bibliothek ab. Insbesondere sollten Anwendungen nicht davon ausgehen, dass Clients die Sperre in derselben Reihenfolge erhalten, in der sie die Sperranforderungen ausgegeben haben.

Hinweis
Vor MySQL 5.5.3, wenn ein Client eine Sperre zu erhalten versucht, die bereits von einem anderen Client gehalten wird, blockiert es nach dem timeout Argumente. Wenn der blockierte Client beendet wird, stirbt sein Thread nicht, bis die Sperranforderung abgelaufen ist.

Diese Funktion ist für die anweisungsbasierte Replikation unsicher. Ab MySQL 5.5.1 wird eine Warnung protokolliert, wenn Sie diese Funktion verwenden, wenn binlog_format auf STATEMENT festgelegt ist. (Fehler # 47995)

+0

Ich lese über GET_LOCK von mysql-Dokumenten. Dann versuchte ich es zu testen, indem ich mysql> SELECT GET_LOCK ('lock2', 10) ausführte; -> 1 zweimal in der gleichen Sitzung und angenommen, dass der zweite Aufruf von GET_LOCK mit dem gleichen Namen (lock2) dint warten auf den ersten zu beenden. Ich habe das gerade in meiner gespeicherten Prozedur getestet, indem ich die gespeicherte Prozedur aus zwei verschiedenen Sitzungen aufgerufen habe. Es hat vollkommen gut funktioniert. Der zweite Aufruf von GET_LOCK wartete auf die Freigabe des ersten Aufrufs, bevor das Zeitlimit überschritten wurde. Danke, Eggyal! –

+0

Obwohl sowohl Ihre Lösung als auch Mushus Lösung meinen Fall lösen, konnte ich nur eine Antwort für dieses Fragenthema auswählen. Ich wählte seinen aus, weil das einfacher und für meinen Fall passender war. –

+0

Entschuldigung, ich konnte immer noch nicht 'Upvote', weil ich nicht genug Punkte dafür habe. Ich werde zurückkommen, wenn ich genug Punkte bekomme. Vielen Dank! –

Verwandte Themen