448 Stimmen

Mehrfache Aktualisierungen in MySQL

Ich weiß, dass Sie mehrere Zeilen auf einmal einfügen können. Gibt es eine Möglichkeit, mehrere Zeilen auf einmal (d. h. in einer Abfrage) in MySQL zu aktualisieren?

Bearbeiten: Ich habe zum Beispiel folgendes

Name   id  Col1  Col2
Row1   1    6     1
Row2   2    2     3
Row3   3    9     5
Row4   4    16    8

Ich möchte alle folgenden Aktualisierungen in einer Abfrage kombinieren

UPDATE table SET Col1 = 1 WHERE id = 1;
UPDATE table SET Col1 = 2 WHERE id = 2;
UPDATE table SET Col2 = 3 WHERE id = 3;
UPDATE table SET Col1 = 10 WHERE id = 4;
UPDATE table SET Col2 = 12 WHERE id = 4;

726voto

Michiel de Mare Punkte 41184

Ja, das ist möglich - Sie können INSERT ... ON DUPLICATE KEY UPDATE verwenden.

Anhand Ihres Beispiels:

INSERT INTO table (id,Col1,Col2) VALUES (1,1,1),(2,2,3),(3,9,3),(4,10,12)
ON DUPLICATE KEY UPDATE Col1=VALUES(Col1),Col2=VALUES(Col2);

35 Stimmen

Wenn es keine Duplikate gibt, dann möchte ich nicht, dass diese Zeile eingefügt wird. was sollte ich tun? weil ich Informationen von einer anderen Website abrufe, die Tabellen mit IDs unterhält. Ich füge Werte in Bezug auf diese ID ein. Wenn die Website neue Datensätze hat, füge ich am Ende nur die IDs und die Anzahl ein, nicht aber alle anderen Informationen.

0 Stimmen

Wenn es also wie insert into skip null oder skip empty on duplicate update ist ... dann wäre das schön.

39 Stimmen

Hinweis: Bei dieser Antwort wird auch davon ausgegangen, dass ID der Primärschlüssel ist.

148voto

Harrison Fisk Punkte 6936

Da Sie dynamische Werte haben, müssen Sie ein IF oder CASE für die zu aktualisierenden Spalten verwenden. Es wird etwas hässlich, aber es sollte funktionieren.

Anhand Ihres Beispiels könnten Sie es so machen:

UPDATE table SET Col1 = CASE id 
                          WHEN 1 THEN 1 
                          WHEN 2 THEN 2 
                          WHEN 4 THEN 10 
                          ELSE Col1 
                        END, 
                 Col2 = CASE id 
                          WHEN 3 THEN 3 
                          WHEN 4 THEN 12 
                          ELSE Col2 
                        END
             WHERE id IN (1, 2, 3, 4);

0 Stimmen

Vielleicht nicht so schön zu schreiben für die dynamische Aktualisierung, aber ein interessanter Blick auf die Funktionalität des Gehäuses...

1 Stimmen

@user2536953 , es kann auch für dynamische Aktualisierungen nützlich sein. Zum Beispiel habe ich diese Lösung in einer Schleife in PHP verwendet: $commandTxt = 'UPDATE operations SET chunk_finished = CASE id '; foreach ($blockOperationChecked as $operationID => $operationChecked) $commandTxt .= " WHEN $operationID THEN $operationChecked "; $commandTxt .= 'ELSE id END WHERE id IN ('.implode(', ', array_keys(blockOperationChecked )).');';

97voto

Roman Imankulov Punkte 8137

Die Frage ist alt, aber ich möchte das Thema um eine weitere Antwort erweitern.

Ich will damit sagen, dass es am einfachsten ist, mehrere Abfragen in eine Transaktion zu verpacken. Die akzeptierte Antwort INSERT ... ON DUPLICATE KEY UPDATE ist ein netter Hack, aber man sollte sich über seine Nachteile und Grenzen im Klaren sein:

  • Wie gesagt, wenn Sie die Abfrage mit Zeilen starten, deren Primärschlüssel nicht in der Tabelle vorhanden sind, fügt die Abfrage neue "halbgare" Datensätze ein. Wahrscheinlich ist es nicht das, was Sie wollen
  • Wenn Sie eine Tabelle mit einem Nicht-Null-Feld ohne Standardwert haben und dieses Feld in der Abfrage nicht berühren wollen, erhalten Sie "Field 'fieldname' doesn't have a default value" MySQL warnt auch dann, wenn Sie überhaupt keine einzige Zeile einfügen. Es wird Sie in Schwierigkeiten bringen, wenn Sie sich entscheiden, streng zu sein und MySQL-Warnungen in Laufzeitausnahmen in Ihrer Anwendung zu verwandeln.

Ich habe einige Leistungstests für drei der vorgeschlagenen Varianten durchgeführt, darunter die INSERT ... ON DUPLICATE KEY UPDATE Variante, eine Variante mit "case / when / then" Klausel und einen naiven Ansatz mit Transaktion. Sie können den Python-Code und die Ergebnisse erhalten aquí . Die allgemeine Schlussfolgerung ist, dass die Variante mit der Case-Anweisung doppelt so schnell ist wie die beiden anderen Varianten, aber es ist ziemlich schwierig, korrekten und injektionssicheren Code dafür zu schreiben, daher bleibe ich persönlich bei der einfachsten Methode: der Verwendung von Transaktionen.

Edit : Feststellungen von Dakusan beweisen, dass meine Leistungseinschätzungen nicht ganz zutreffend sind. Siehe bitte diese Antwort für eine andere, ausführlichere Untersuchung.

0 Stimmen

Verwendung von Transaktionen, sehr schöner (und einfacher) Tipp!

0 Stimmen

Was ist, wenn meine Tabellen nicht vom Typ InnoDB sind?

1 Stimmen

Könnte jemand einen Link zur Verfügung stellen, wie die entsprechenden Transaktionen aussehen? Und/oder Code für einen injektionssicheren Code für die Variante mit case-Anweisung?

87voto

newtover Punkte 29616

Ich weiß nicht, warum eine andere nützliche Option noch nicht erwähnt wurde:

UPDATE my_table m
JOIN (
    SELECT 1 as id, 10 as _col1, 20 as _col2
    UNION ALL
    SELECT 2, 5, 10
    UNION ALL
    SELECT 3, 15, 30
) vals ON m.id = vals.id
SET col1 = _col1, col2 = _col2;

6 Stimmen

Das ist das Beste. Vor allem, wenn Sie die Werte für die Aktualisierung aus einer anderen SQL-Abfrage ziehen, wie ich es tat.

1 Stimmen

Dies war ideal für eine Aktualisierung einer Tabelle mit einer großen Anzahl von Spalten. Ich werde diese Abfrage in Zukunft wahrscheinlich noch oft verwenden. Danke!

0 Stimmen

Ich habe diese Art der Abfrage ausprobiert. Aber wenn die Datensätze 30k erreichen, wird der Boundary-Server angehalten. Gibt es eine andere Lösung?

56voto

Dakusan Punkte 6255

Alle der folgenden Punkte gelten für InnoDB.

Ich denke, es ist wichtig, die Geschwindigkeiten der 3 verschiedenen Methoden zu kennen.

Es gibt 3 Methoden:

  1. INSERT: INSERT mit ON DUPLICATE KEY UPDATE
  2. TRANSACTION: Hier führen Sie eine Aktualisierung für jeden Datensatz innerhalb einer Transaktion durch.
  3. CASE: In dem Sie ein case/when für jeden verschiedenen Datensatz innerhalb eines UPDATE

Ich habe dies gerade getestet, und die INSERT-Methode war 6.7x für mich schneller als die TRANSACTION-Methode. Ich habe es mit einem Satz von 3.000 und 30.000 Zeilen versucht.

Die TRANSACTION-Methode muss immer noch jede einzelne Abfrage ausführen, was Zeit in Anspruch nimmt, obwohl sie die Ergebnisse während der Ausführung im Speicher stapelt oder ähnliches. Die TRANSACTION-Methode ist auch ziemlich teuer, sowohl bei der Replikation als auch bei den Abfrageprotokollen.

Schlimmer noch, die CASE-Methode war 41.1x langsamer als die INSERT-Methode mit 30.000 Datensätzen (6,1x langsamer als TRANSACTION). Und 75x langsamer in MyISAM. Die INSERT- und CASE-Methoden erreichten die Gewinnschwelle bei ~1.000 Datensätzen. Selbst bei 100 Datensätzen ist die CASE-Methode GERINGSTENS schneller.

Im Allgemeinen halte ich die INSERT-Methode für die beste und am einfachsten zu verwendende Methode. Die Abfragen sind kleiner und leichter zu lesen und nehmen nur eine Abfrage in Anspruch. Dies gilt sowohl für InnoDB als auch für MyISAM.

Bonusmaterial:

Die Lösung für das INSERT-Nicht-Standardfeld-Problem besteht darin, die entsprechenden SQL-Modi vorübergehend zu deaktivieren: SET SESSION sql_mode=REPLACE(REPLACE(@@SESSION.sql_mode,"STRICT_TRANS_TABLES",""),"STRICT_ALL_TABLES","") . Stellen Sie sicher, dass Sie die sql_mode wenn Sie es rückgängig machen wollen.

Wie für andere Kommentare, die ich gesehen habe, die sagen, dass die Auto-Inkrement geht mit der INSERT-Methode, scheint dies der Fall in InnoDB, aber nicht MyISAM sein.

Der Code zur Durchführung der Tests lautet wie folgt. Er gibt auch .SQL-Dateien aus, um den Aufwand für den PHP-Interpreter zu verringern

<?php
//Variables
$NumRows=30000;

//These 2 functions need to be filled in
function InitSQL()
{

}
function RunSQLQuery($Q)
{

}

//Run the 3 tests
InitSQL();
for($i=0;$i<3;$i++)
    RunTest($i, $NumRows);

function RunTest($TestNum, $NumRows)
{
    $TheQueries=Array();
    $DoQuery=function($Query) use (&$TheQueries)
    {
        RunSQLQuery($Query);
        $TheQueries[]=$Query;
    };

    $TableName='Test';
    $DoQuery('DROP TABLE IF EXISTS '.$TableName);
    $DoQuery('CREATE TABLE '.$TableName.' (i1 int NOT NULL AUTO_INCREMENT, i2 int NOT NULL, primary key (i1)) ENGINE=InnoDB');
    $DoQuery('INSERT INTO '.$TableName.' (i2) VALUES ('.implode('), (', range(2, $NumRows+1)).')');

    if($TestNum==0)
    {
        $TestName='Transaction';
        $Start=microtime(true);
        $DoQuery('START TRANSACTION');
        for($i=1;$i<=$NumRows;$i++)
            $DoQuery('UPDATE '.$TableName.' SET i2='.(($i+5)*1000).' WHERE i1='.$i);
        $DoQuery('COMMIT');
    }

    if($TestNum==1)
    {
        $TestName='Insert';
        $Query=Array();
        for($i=1;$i<=$NumRows;$i++)
            $Query[]=sprintf("(%d,%d)", $i, (($i+5)*1000));
        $Start=microtime(true);
        $DoQuery('INSERT INTO '.$TableName.' VALUES '.implode(', ', $Query).' ON DUPLICATE KEY UPDATE i2=VALUES(i2)');
    }

    if($TestNum==2)
    {
        $TestName='Case';
        $Query=Array();
        for($i=1;$i<=$NumRows;$i++)
            $Query[]=sprintf('WHEN %d THEN %d', $i, (($i+5)*1000));
        $Start=microtime(true);
        $DoQuery("UPDATE $TableName SET i2=CASE i1\n".implode("\n", $Query)."\nEND\nWHERE i1 IN (".implode(',', range(1, $NumRows)).')');
    }

    print "$TestName: ".(microtime(true)-$Start)."<br>\n";

    file_put_contents("./$TestName.sql", implode(";\n", $TheQueries).';');
}

5 Stimmen

Du tust hier die Arbeit des HERRN ;) Sehr geschätzt.

0 Stimmen

Beim Testen der Leistung von GoLang und PHP mit 40k Zeilen auf MariaDB habe ich 2 Sekunden auf PHP und mehr als 6 Sekunden auf Golang erhalten .... Nun, mir wurde immer gesagt, dass GoLang schneller läuft als PHP !!! SO, ich fange an mich zu fragen, wie man die Leistung verbessern kann ... Mit dem INSERT ... ON DUPLICATE KEY UPDATE ... Ich habe 0,74 Sekunden auf Golang und 0,86 Sekunden auf PHP !!!!

2 Stimmen

Der Sinn meines Codes besteht darin, die Ergebnisse der Zeitmessung auf die SQL-Anweisungen zu beschränken und nicht auf den Code der Sprache oder der Bibliotheken. GoLang und PHP sind 2 völlig verschiedene Sprachen, die für völlig unterschiedliche Zwecke gedacht sind. PHP ist für eine Single-Run-Skriptumgebung auf einem einzigen Thread mit meist begrenzter und passiver Garbage Collection gedacht. GoLang ist für langlaufende kompilierte Anwendungen mit aggressiver Garbage Collection und Multithreading als eine der primären Sprachfunktionen gedacht. Sie könnten in Bezug auf die Sprachfunktionalität und die Gründe kaum unterschiedlicher sein. [Fortsetzung]

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X