MySQL: delete… where..in () vs delete..from..join und gesperrte Tabellen beim Löschen mit subselect

9

Haftungsausschluss: Bitte entschuldigen Sie mein mangelndes Wissen über Datenbank-Interna. Hier kommt's:

Wir führen eine Anwendung aus (nicht von uns geschrieben), die bei einem regelmäßigen Bereinigungsjob in der Datenbank ein großes Leistungsproblem aufweist. Die Abfrage sieht folgendermaßen aus:

delete from VARIABLE_SUBSTITUTION where BUILDRESULTSUMMARY_ID in (
       select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
       where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");

Einfach, einfach zu lesen und Standard-SQL. Aber leider sehr langsam. Das Erläutern der Abfrage zeigt, dass der vorhandene Index für VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_IDnicht verwendet wird:

mysql> explain delete from VARIABLE_SUBSTITUTION where BUILDRESULTSUMMARY_ID in (
    ->        select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
    ->        where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");
| id | select_type        | table                 | type            | possible_keys                    | key     | key_len | ref  | rows    | Extra       |
+----+--------------------+-----------------------+-----------------+----------------------------------+---------+---------+------+---------+-------------+
|  1 | PRIMARY            | VARIABLE_SUBSTITUTION | ALL             | NULL                             | NULL    | NULL    | NULL | 7300039 | Using where |
|  2 | DEPENDENT SUBQUERY | BUILDRESULTSUMMARY    | unique_subquery | PRIMARY,key_number_results_index | PRIMARY | 8       | func |       1 | Using where |

Dies macht es sehr langsam (120 Sekunden und mehr). Hinzu kommt , dass, so scheint es zu Block Abfragen , die in einzufügen versuchen BUILDRESULTSUMMARYaus, Ausgabe show engine innodb status:

---TRANSACTION 68603695, ACTIVE 157 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 127964, OS thread handle 0x7facd0670700, query id 956555826 localhost 127.0.0.1 bamboosrv updating
update BUILDRESULTSUMMARY set CREATED_DATE='2015-06-18 09:22:05', UPDATED_DATE='2015-06-18 09:22:32', BUILD_KEY='BLA-RELEASE1-JOB1', BUILD_NUMBER=8, BUILD_STATE='Unknown', LIFE_CYCLE_STATE='InProgress', BUILD_DATE='2015-06-18 09:22:31.792', BUILD_CANCELLED_DATE=null, BUILD_COMPLETED_DATE='2015-06-18 09:52:02.483', DURATION=1770691, PROCESSING_DURATION=1770691, TIME_TO_FIX=null, TRIGGER_REASON='com.atlassian.bamboo.plugin.system.triggerReason:CodeChangedTriggerReason', DELTA_STATE=null, BUILD_AGENT_ID=199688199, STAGERESULT_ID=230943366, RESTART_COUNT=0, QUEUE_TIME='2015-06-18 09:22:04.52
------- TRX HAS BEEN WAITING 157 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 38 page no 30140 n bits 112 index `PRIMARY` of table `bamboong`.`BUILDRESULTSUMMARY` trx id 68603695 lock_mode X locks rec but not gap waiting
------------------
---TRANSACTION 68594818, ACTIVE 378 sec starting index read
mysql tables in use 2, locked 2
646590 lock struct(s), heap size 63993384, 3775190 row lock(s), undo log entries 117
MySQL thread id 127845, OS thread handle 0x7facc6bf8700, query id 956652201 localhost 127.0.0.1 bamboosrv preparing
delete from VARIABLE_SUBSTITUTION  where BUILDRESULTSUMMARY_ID in   (select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY where BUILDRESULTSUMMARY.BUILD_KEY = 'BLA-BLUBB10-SON')

Dies verlangsamt das System und zwingt uns zu erhöhen innodb_lock_wait_timeout.

Während wir MySQL ausführen, haben wir die Löschabfrage neu geschrieben, um "Aus Join löschen" zu verwenden:

delete VARIABLE_SUBSTITUTION from VARIABLE_SUBSTITUTION join BUILDRESULTSUMMARY
   on VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID = BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID
   where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1";

Dies ist etwas weniger einfach zu lesen, leider kein Standard-SQL (soweit ich es herausfinden konnte), aber viel schneller (0,02 Sekunden oder so), da es den Index verwendet:

mysql> explain delete VARIABLE_SUBSTITUTION from VARIABLE_SUBSTITUTION join BUILDRESULTSUMMARY
    ->    on VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID = BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID
    ->    where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1";
| id | select_type | table                 | type | possible_keys                    | key                      | key_len | ref                                                    | rows | Extra                    |
+----+-------------+-----------------------+------+----------------------------------+--------------------------+---------+--------------------------------------------------------+------+--------------------------+
|  1 | SIMPLE      | BUILDRESULTSUMMARY    | ref  | PRIMARY,key_number_results_index | key_number_results_index | 768     | const                                                  |    1 | Using where; Using index |
|  1 | SIMPLE      | VARIABLE_SUBSTITUTION | ref  | var_subst_result_idx             | var_subst_result_idx     | 8       | bamboo_latest.BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID |   26 | NULL                     |

Zusätzliche Information:

mysql> SHOW CREATE TABLE VARIABLE_SUBSTITUTION;
| Table                 | Create Table |
| VARIABLE_SUBSTITUTION | CREATE TABLE `VARIABLE_SUBSTITUTION` (
  `VARIABLE_SUBSTITUTION_ID` bigint(20) NOT NULL,
  `VARIABLE_KEY` varchar(255) COLLATE utf8_bin NOT NULL,
  `VARIABLE_VALUE` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
  `VARIABLE_TYPE` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `BUILDRESULTSUMMARY_ID` bigint(20) NOT NULL,
  PRIMARY KEY (`VARIABLE_SUBSTITUTION_ID`),
  KEY `var_subst_result_idx` (`BUILDRESULTSUMMARY_ID`),
  KEY `var_subst_type_idx` (`VARIABLE_TYPE`),
  CONSTRAINT `FK684A7BE0A958B29F` FOREIGN KEY (`BUILDRESULTSUMMARY_ID`) REFERENCES `BUILDRESULTSUMMARY` (`BUILDRESULTSUMMARY_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin |

mysql> SHOW CREATE TABLE BUILDRESULTSUMMARY;
| Table              | Create Table |
| BUILDRESULTSUMMARY | CREATE TABLE `BUILDRESULTSUMMARY` (
  `BUILDRESULTSUMMARY_ID` bigint(20) NOT NULL,
....
  `SKIPPED_TEST_COUNT` int(11) DEFAULT NULL,
  PRIMARY KEY (`BUILDRESULTSUMMARY_ID`),
  KEY `FK26506D3B9E6537B` (`CHAIN_RESULT`),
  KEY `FK26506D3BCCACF65` (`MERGERESULT_ID`),
  KEY `key_number_delta_state` (`DELTA_STATE`),
  KEY `brs_build_state_idx` (`BUILD_STATE`),
  KEY `brs_life_cycle_state_idx` (`LIFE_CYCLE_STATE`),
  KEY `brs_deletion_idx` (`MARKED_FOR_DELETION`),
  KEY `brs_stage_result_id_idx` (`STAGERESULT_ID`),
  KEY `key_number_results_index` (`BUILD_KEY`,`BUILD_NUMBER`),
  KEY `brs_agent_idx` (`BUILD_AGENT_ID`),
  KEY `rs_ctx_baseline_idx` (`VARIABLE_CONTEXT_BASELINE_ID`),
  KEY `brs_chain_result_summary_idx` (`CHAIN_RESULT`),
  KEY `brs_log_size_idx` (`LOG_SIZE`),
  CONSTRAINT `FK26506D3B9E6537B` FOREIGN KEY (`CHAIN_RESULT`) REFERENCES `BUILDRESULTSUMMARY` (`BUILDRESULTSUMMARY_ID`),
  CONSTRAINT `FK26506D3BCCACF65` FOREIGN KEY (`MERGERESULT_ID`) REFERENCES `MERGE_RESULT` (`MERGERESULT_ID`),
  CONSTRAINT `FK26506D3BCEDEEF5F` FOREIGN KEY (`STAGERESULT_ID`) REFERENCES `CHAIN_STAGE_RESULT` (`STAGERESULT_ID`),
  CONSTRAINT `FK26506D3BE3B5B062` FOREIGN KEY (`VARIABLE_CONTEXT_BASELINE_ID`) REFERENCES `VARIABLE_CONTEXT_BASELINE` (`VARIABLE_CONTEXT_BASELINE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin |

(einige Sachen weggelassen, es ist ein ziemlich breiter Tisch).

Ich habe also ein paar Fragen dazu:

  • Warum kann das Abfrageoptimierungsprogramm den Index nicht zum Löschen der Unterabfrageversion verwenden, während die Join-Version verwendet wird?
  • Gibt es eine (im Idealfall standardkonforme) Möglichkeit, den Index zu verwenden? oder
  • Gibt es eine tragbare Möglichkeit, eine zu schreiben delete from join? Die Anwendung unterstützt PostgreSQL, MySQL, Oracle und Microsoft SQL Server, die über jdbc und Hibernate verwendet werden.
  • Warum wird das Löschen aus VARIABLE_SUBSTITUTIONblockierenden Einfügungen in BUILDRESULTSUMMARY, das nur in der Unterauswahl verwendet wird?
0x89
quelle
Percona Server 5.6.24-72.2-1.jessie bzw. 5.6.24-72.2-1.wheezy (auf dem Testsystem).
0x89
Ja, die gesamte Datenbank verwendet innodb.
0x89
Es scheint also, dass 5.6 bei der Verbesserung des Optimierers nicht viel Aufmerksamkeit auf sich gezogen hat. Sie müssen auf 5.7 warten (aber versuchen Sie es mit MariaDB, wenn Sie können. Die Optimierungsverbesserungen wurden bereits in den Versionen 5.3 und 5.5 vorgenommen.)
ypercubeᵀᴹ
@ypercube AFAIK no fork hat eine Erweiterung, um die Lösch-Unterabfrage noch 5.7 zu optimieren. Löschungen optimieren anders als SELECT-Anweisungen.
Morgan Tocker

Antworten:

7
  • Warum kann das Abfrageoptimierungsprogramm den Index nicht zum Löschen der Unterabfrageversion verwenden, während die Join-Version verwendet wird?

Weil der Optimierer in dieser Hinsicht etwas dumm ist / war. Nicht nur für DELETEund UPDATEsondern auch für SELECTAussagen wurde so etwas WHERE column IN (SELECT ...)nicht vollständig optimiert. Der Ausführungsplan umfasste normalerweise das Ausführen der Unterabfrage für jede Zeile der externen Tabelle ( VARIABLE_SUBSTITUTIONin diesem Fall). Wenn dieser Tisch klein ist, ist alles in Ordnung. Wenn es groß ist, keine Hoffnung. In noch älteren Versionen würde eine INUnterabfrage mit einer INUnterunterabfrage sogar das EXPLAINfür Ewigkeiten laufen lassen.

Wenn Sie diese Abfrage beibehalten möchten, können Sie die neuesten Versionen verwenden, die mehrere Optimierungen implementiert haben, und erneut testen. Neueste Versionen bedeuten: MySQL 5.6 (und 5.7, wenn es aus der Beta kommt) und MariaDB 5.5 / 10.0

(Update) Sie verwenden bereits 5.6 mit Optimierungsverbesserungen, und diese ist relevant: Optimieren von Unterabfragen mit Semi-Join-Transformationen
Ich empfehle, nur einen Index hinzuzufügen (BUILD_KEY). Es gibt eine zusammengesetzte, aber das ist für diese Abfrage nicht sehr nützlich.

  • Gibt es eine (im Idealfall standardkonforme) Möglichkeit, den Index zu verwenden?

Keine, an die ich denken kann. Meiner Meinung nach lohnt es sich nicht, Standard-SQL zu verwenden. Es gibt so viele Unterschiede und kleinere Macken, die jedes DBMS aufweist ( UPDATEund DELETEAnweisungen sind gute Beispiele für solche Unterschiede), dass das Ergebnis eine sehr begrenzte Teilmenge von SQL ist, wenn Sie versuchen, etwas zu verwenden, das überall funktioniert.

  • Gibt es eine tragbare Möglichkeit, eine Löschung aus dem Join zu schreiben? Die Anwendung unterstützt PostgreSQL, MySQL, Oracle und Microsoft SQL Server, die über jdbc und Hibernate verwendet werden.

Gleiche Antwort wie die vorherige Frage.

  • Warum blockiert das Löschen von VARIABLE_SUBSTITUTION Einfügungen in BUILDRESULTSUMMARY, das nur in der Unterauswahl verwendet wird?

Nicht 100% sicher, aber ich denke, es hat damit zu tun, dass die Unterabfrage mehrmals ausgeführt wird und welche Art von Sperren sie für die Tabelle verwendet.

ypercubeᵀᴹ
quelle
"3775190 Zeilensperre (n)" von innodb_status (der löschenden Transaktion) ist sehr suggestiv. Aber auch "MySQL-Tabellen in Verwendung 2, gesperrt 2" sieht für mich nicht allzu gut aus.
0x89
2

Hier finden Sie die Antworten auf zwei Ihrer Fragen

  • Das Optimierungsprogramm kann den Index nicht verwenden, da sich die where-Klausel für jede Zeile ändert. Die delete-Anweisung sieht ungefähr so ​​aus, nachdem sie den Optimierer bestanden hat

    delete from VARIABLE_SUBSTITUTION where EXISTS (
    select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
    where BUILDRESULTSUMMARY.BUILD_KEY = BUILDRESULTSUMMARY_ID AND BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");

Wenn Sie jedoch den Join ausführen, kann der Server die zu löschenden Zeilen identifizieren.

  • Trick ist, eine Variable zu verwenden, um die zu halten BUILDRESULTSUMMARY_IDund die Variable anstelle der Abfrage zu verwenden. Beachten Sie, dass sowohl die Variableninitialisierung als auch die Löschabfrage innerhalb einer Sitzung ausgeführt werden müssen. Etwas wie das.

    SET @ids = (SELECT GROUP_CONCAT(BUILDRESULTSUMMARY_ID) 
            from BUILDRESULTSUMMARY where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1" ); 
    delete from VARIABLE_SUBSTITUTION where FIND_IN_SET(BUILDRESULTSUMMARY_ID,@ids) > 0;

    Dies kann zu Problemen führen, wenn die Abfrage zu viele IDs zurückgibt und dies kein Standardweg ist. Es ist nur eine Problemumgehung.

    Und ich habe keine Antwort auf deine beiden anderen Fragen :)

Masoud
quelle
Okay, du hast meinen Standpunkt verfehlt. Ich denke , was Sie nicht berücksichtigen ist , dass beide VARIABLE_SUBSTITUTION und BUILDRESULTSUMMARY haben eine Spalte namens BUILDRESULTSUMMARY_ID, soll es so sein sollte: ‚aus VARIABLE_SUBSTITUTION streichen VORHANDEN (wählen BUILDRESULTSUMMARY_ID von BUILDRESULTSUMMARY wo BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID = VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID UND BUILDRESULTSUMMARY.BUILD_KEY =„BAM -1 "); '. Dann macht es Sinn, und beide Abfragen machen dasselbe.
0x89
1
Ja, ich vermisse nur einen Verweis auf die äußere Tabelle. Aber das ist nicht der Punkt. Dies ist nur ein Beispiel dafür, wie es im Optimierer behandelt wird.
Masoud
Mit dem kleinen Unterschied, dass der Optimierer eine äquivalente Abfrage erzeugt.
0x89