Mysql
 sql >> Datenbank >  >> RDS >> Mysql

MySQL-Transaktion:AUSWÄHLEN + EINFÜGEN

Was Sie brauchen, ist Sperren . Transaktionen sind in der Tat "nicht unbedingt erforderlich".

Sie können zwischen „pessimistischem Sperren“ und „optimistischem Sperren“ wählen. Die Entscheidung, welche dieser beiden Möglichkeiten Sie wählen, liegt bei Ihnen und muss grundsätzlich unter folgenden Gesichtspunkten bewertet werden:

  • das Maß an Parallelität, das Sie haben
  • die Dauer der unteilbaren Operationen in der Datenbank
  • die Komplexität des gesamten Vorgangs

Ich werde empfehlen, diese beiden zu lesen, um sich eine Vorstellung von den beteiligten Dingen zu machen:

Ein Beispiel zur besseren Erklärung

Dies ist vielleicht nicht so elegant, aber nur ein Beispiel, das zeigt, wie es möglich ist, alles ohne Transaktion (und sogar ohne die UNIQUE-Einschränkungen) zu tun. Was getan werden muss, ist die folgende kombinierte INSERT + SELECT-Anweisung zu verwenden und nach ihrer Ausführung um die Anzahl der betroffenen Zeilen zu überprüfen. Wenn die Anzahl der betroffenen Zeilen 1 ist, dann war es erfolgreich, andernfalls (wenn es 0 ist) gab es eine Kollision und die andere Partei hat gewonnen.

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= @endTime AND `end` >= @startTime
    AND `devices_id` = @deviceId)
GROUP BY (1);

Dies ist ein Beispiel für optimistisches Sperren, das ohne Transaktionen und mit einer einzigen SQL-Operation erhalten wird.

Wie es geschrieben steht, hat es das Problem, dass mindestens eine Zeile bereits im slot vorhanden sein muss Tabelle, damit es funktioniert (andernfalls gibt die SELECT-Klausel immer ein leeres Recordset zurück und in diesem Fall wird nichts eingefügt, auch wenn es keine Kollisionen gibt. Es gibt zwei Möglichkeiten, damit es funktioniert:

  • Fügen Sie eine Dummy-Zeile in die Tabelle ein, vielleicht mit dem Datum in der Vergangenheit
  • so umschreiben, dass sich die FROM-Hauptklausel auf jede Tabelle bezieht, die mindestens eine Zeile hat, oder besser eine kleine Tabelle erstellen (vielleicht mit dem Namen dummy). ) mit nur einer Spalte und nur einem Datensatz darin und schreiben Sie wie folgt um (beachten Sie, dass die GROUP BY-Klausel nicht mehr benötigt wird)

    INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
    SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
    FROM `dummy`
    WHERE NOT EXISTS (
        SELECT `id` FROM `slot`
        WHERE `start` <= @endTime AND `end` >= @startTime
        AND `devices_id` = @deviceId);
    

Hier folgt eine Reihe von Anweisungen, die beim einfachen Kopieren / Einfügen die Idee in Aktion zeigen. Ich bin davon ausgegangen, dass Sie Datum/Uhrzeit in int-Feldern als Zahl mit verketteten Datums- und Uhrzeitziffern codieren.

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
VALUES (1008141200, 1008141210, 11, 2, 'Dummy Record', 14)

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141206, 1408141210, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= 1408141210 AND `end` >= 1408141206
    AND `devices_id` = 14)
GROUP BY (1);

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141208, 1408141214, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= 1408141214 AND `end` >= 1408141208
    AND `devices_id` = 14)
GROUP BY (1);

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT 1408141216, 1408141220, 11, 2, 'Hello', 14
FROM `slot`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= 1408141220 AND `end` >= 1408141216
    AND `devices_id` = 14)
GROUP BY (1);

SELECT * FROM `slot`;

Dies ist eindeutig ein extremes Beispiel für optimistisches Sperren, ist aber am Ende sehr effizient, da alles mit nur einer SQL-Anweisung und mit geringer Interaktion (Datenaustausch) zwischen dem Datenbankserver und dem PHP-Code erledigt wird. Außerdem gibt es praktisch keine "echte" Verriegelung.

...oder mit Pessimistischem Locking

Derselbe Code kann zu einer guten pessimistischen Locking-Implementierung werden, wenn er nur mit expliziten Anweisungen zum Sperren/Entsperren von Tabellen umgeben ist:

LOCK TABLE slot WRITE, dummy READ;

INSERT INTO `slot` (`start`, `end`, `uid`, `group`, `message`, `devices_id`)
SELECT @startTime, @endTime, @uid, @group, @message, @deviceId
FROM `dummy`
WHERE NOT EXISTS (
    SELECT `id` FROM `slot`
    WHERE `start` <= @endTime AND `end` >= @startTime
    AND `devices_id` = @deviceId);

UNLOCK TABLES;

Natürlich könnten in diesem Fall (Pessimistic Locking) SELECT und INSERT getrennt und etwas PHP-Code dazwischen ausgeführt werden. Dieser Code bleibt jedoch sehr schnell auszuführen (kein Datenaustausch mit PHP, kein PHP-Zwischencode) und daher ist die Dauer des Pessimistic Lock so kurz wie möglich. Pessimistic Lock so kurz wie möglich zu halten, ist ein wichtiger Punkt, um eine Verlangsamung der Anwendung zu vermeiden.

Auf jeden Fall müssen Sie den Rückgabewert der Anzahl der betroffenen Datensätze überprüfen, um zu wissen, ob es erfolgreich war, da der Code praktisch derselbe ist und Sie die Erfolgs-/Fehlerinformationen auf die gleiche Weise erhalten.

Hier http://dev.mysql.com/doc/ refman/5.0/en/insert-select.html sie sagen, dass "MySQL keine gleichzeitigen Einfügungen für INSERT ... SELECT-Anweisungen zulässt" Daher sollte Pessimistic Lock nicht benötigt werden, aber es kann trotzdem eine gute Option sein, wenn Sie glauben, dass sich dies in zukünftigen Versionen von MySQL ändern wird.

Ich bin "optimistisch" dass sich das nicht ändern wird;-)