Abfrage zum Kombinieren einer Aktualisierungs- und einer Einfügeabfrage in einer einzelnen Abfrage in MySQL

9

Ich möchte den Änderungsverlauf für einen Benutzer verfolgen, sodass ich bei jeder Änderung seines Profils die alten Daten übernehmen und im Verlauf speichern und mit neuen Daten aktualisieren muss.

Ich kann a verwenden select, um die alten Daten abzurufen, um einen Verlauf insertzu erstellen und schließlich updateum Daten zu ändern.

Kann ich all dies in einer einzigen Abfrage in MySQL haben, ohne gespeicherte Prozeduren, Trigger usw. zu verwenden, wie z. B. Sperren usw.? Wenn ja, geben Sie mir ein kleines Beispiel.

Saravanan
quelle
1
@savaranan: Diese Frage verdient eine +1, da sie DBAs und Entwickler stark daran erinnert, Transaktionen zu verwenden und die ACID-Eigenschaften der Datenbank voll auszunutzen.
RolandoMySQLDBA
2
@savaranan: In jeder Hinsicht hat Jack die EINZIGE plausible Antwort geliefert, die es gibt. Tatsächlich machte Jack Douglas einen zusätzlichen Schritt und erzwang eine intermittierende Sperre für jede Zeile mit der ID = 10 für zusätzlichen MVCC-Schutz, indem er SELECT ... FOR UPDATE ausführte. Seine Antwort unterstreicht den Punkt, den Jack und ich die ganze Zeit gesagt haben: Ein UPDATE und ein INSERT können keine einzige Abfrage sein, sie können nur eine einzige Transaktion für das von Ihrer Frage vorgeschlagene SQL-Verhalten sein.
RolandoMySQLDBA

Antworten:

13

Um dies zu tun , ohne das Risiko , einen anderen Benutzer blockieren versucht , das gleiche Profil in der gleichen Zeit zu aktualisieren, müssen Sie die Zeile sperren in t1ersten, dann verwenden Sie eine Transaktion (wie Rolando auf Ihre Frage in den Kommentaren weist darauf hin):

start transaction;
select id from t1 where id=10 for update;
insert into t2 select * from t1 where id=10;
update t1 set id = 11 where id=10;
commit;
Jack sagt, versuchen Sie es mit topanswers.xyz
quelle
Dies ist einfach hervorragend, um jede Zeile mit id = 10 weiter zu sperren. Das sollte eine +2 sein. Alles was ich geben kann ist eine +1 !!!
RolandoMySQLDBA
1

Ich glaube nicht, dass es eine Möglichkeit gibt, alle drei Aussagen zu kombinieren. Das, was dem am nächsten kommt, hilft dir nicht wirklich, und das ist ein SET SELECT. Ihre beste Wette ist ein Auslöser. Unten finden Sie ein Beispiel für einen Trigger, den ich häufig verwende, um einen solchen Audit-Trail (mit PHP erstellt) zu verwalten:

$trigger = "-- audit trigger --\nDELIMITER $ \n".
    "DROP TRIGGER IF EXISTS `{$prefix}_Audit_Trigger`$\n".
    "CREATE TRIGGER `{$prefix}_Audit_Trigger` AFTER UPDATE ON `$this->_table_name` FOR EACH ROW BEGIN\n";

foreach ($field_defs as $field_name => $field) {
    if ($field_name != $id_name) {
       $trigger .= "IF (NOT OLD.$field_name <=> NEW.$field_name) THEN \n".'INSERT INTO AUDIT_LOG ('.
                    'Table_Name, Row_ID, Field_Name, Old_Value, New_Value, modified_by, DB_User) VALUES'.
                    "\n ('$this->_table_name',OLD.$this->_id_name,'$field_name',OLD.$field_name,NEW.$field_name,".
                    "NEW.modified_by, USER()); END IF;\n";
    }
}
$trigger .= 'END$'."\n".'DELIMITER ;';
Bryan Agee
quelle
-3

Ich habe festgestellt, dass diese Abfrage auf SQL- und MySQL-Servern funktioniert INSERT INTO t2 SELECT * FROM t1 WHERE id=10; UPDATE t1 SET id=11 WHERE id=10;

Hoffe, dass dies auch in Zukunft für andere nützlich ist.

Saravanan
quelle
4
Dies ist nicht wirklich eine Abfrage. Dies sind eigentlich zwei Abfragen, die als Transaktion behandelt werden sollten.
RolandoMySQLDBA
@rolandomysqldba: Dies funktioniert gut als einzelne Abfrage, wenn ich den Anwendungscode an einen Datenbankserver sende, wo ich diesen Satz als einzelne Abfrage behandle. warum sagst du das?. Können Sie dies mit starken Gründen widerlegen
?
2
@saravanan: In den Augen von InnoDB oder einem ACID-kompatiblen RDBMS (Oracle, SQLServer, PostreSQL, Sybase usw.) ist es unmöglich, diese beiden SQL-Anweisungen als eine Abfrage aufzurufen. Als ACID-konforme Datenbank würden sie als zwei Anweisungen behandelt. In InnoDB ist die automatische Festschreibung standardmäßig aktiviert. Die erste Anweisung, INSERT, wird als einzelne Transaktion ausgeführt. MVCC-Daten (Multiversioning Concurrency Control) werden generiert, um eine Kopie der Originaldaten in der t2-Tabelle zeilenweise zu speichern. Wenn MySQL während der Ausführung von INSERT abstürzt, verwendet InnoDB MVCC-Daten, um t2 auf seinen ursprünglichen Zustand zurückzusetzen.
RolandoMySQLDBA
1
@saravanan: Angenommen, das INSERT hat erfolgreich funktioniert. Die aus dem INSERT resultierenden Daten wurden festgeschrieben (mit aktiviertem Autocommit) und die MVCC-Schutztabelle t2 wird verworfen. Wenn Sie das UPDATE ausführen, wird MVCC für die Tabelle t1 generiert und das UPDATE wird ausgeführt. Wenn MySQL während des UPDATE abstürzt, verwendet InnoDB MVCC-Daten auf t1, um das UPDATE zurückzusetzen. Selbst wenn das UPDATE nur eine Zeile ändert, besteht die Möglichkeit, dass Datensätze mit der ID 10 von t1 nach t2 verschoben werden und die ID 10 in t1 nicht in die ID 11 geändert wird. Um dieses einzigartige Szenario zu verhindern, müssen Sie Folgendes tun ...
RolandoMySQLDBA
@savaranan: Behandle die beiden SQL-Anweisungen als eine einzige Transaktion. Ein einfacher Weg, dies zu tun, ist: BEGIN; INSERT IN t2 SELECT * FROM t1 WHERE id = 10; UPDATE t1 SET id = 11 WHERE id = 10; VERPFLICHTEN; Der stärkste Grund, die beiden SQL-Anweisungen als eine einzige Transaktion zu behandeln, ist die Tatsache, dass das für INSERT erstellte MVCC während des UPDATE bestehen bleibt. Sollte während des UPDATE innerhalb einer Transaktion ein MySQL-Absturz auftreten (BEGIN; ... COMMIT; Block), würde MVCC alle Änderungen auf einen konsistenten Zustand zurücksetzen. Wenn sowohl INSERT als auch UPDATE abgeschlossen sind, wird MVCC im letzten Moment verworfen.
RolandoMySQLDBA