MySQL: delete ... where..in () vs delete..from..join e tabelle bloccate su delete con subselect


9

Dichiarazione di non responsabilità: si prega di scusare la mia mancanza di conoscenza degli interni del database. Eccolo:

Eseguiamo un'applicazione (non scritta da noi) che presenta un grosso problema di prestazioni in un processo di pulizia periodica nel database. La query è simile alla seguente:

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

SQL semplice, facile da leggere e standard. Ma purtroppo molto lento. Spiegare la query mostra che l'indice esistente VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_IDnon viene utilizzato:

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 |

Questo lo rende molto lento (120 secondi e più). Oltre a ciò, sembra bloccare le query che provano a inserire BUILDRESULTSUMMARY, l'output da 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')

Questo rallenta il sistema e ci ha costretto ad aumentare innodb_lock_wait_timeout.

Mentre eseguiamo MySQL, riscriviamo la query di eliminazione per utilizzare "elimina dal join":

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

Questo è leggermente meno facile da leggere, sfortunatamente nessun SQL standard (per quanto ne sono stato in grado di scoprire), ma molto più veloce (0,02 secondi circa) in quanto utilizza l'indice:

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                     |

Informazioni addizionali:

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 |

(alcune cose sono state omesse, è un tavolo abbastanza ampio).

Quindi ho alcune domande a riguardo:

  • perché Query Optimizer non è in grado di utilizzare l'indice per l'eliminazione della versione della subquery, mentre lo è durante l'utilizzo della versione del join?
  • esiste un modo (idealmente conforme agli standard) per indurlo a utilizzare l'indice? o
  • c'è un modo portatile per scrivere un delete from join? L'applicazione supporta PostgreSQL, MySQL, Oracle e Microsoft SQL Server, utilizzati tramite jdbc e Hibernate.
  • perché l'eliminazione dal VARIABLE_SUBSTITUTIONblocco inserisce in BUILDRESULTSUMMARY, che viene utilizzata solo nella sottoselezione?

Percona Server 5.6.24-72.2-1.jessie resp 5.6.24-72.2-1.wheezy (sul sistema di test).
0x89,

Sì, l'intero database utilizza innodb.
0x89,

Quindi sembra che 5.6 non abbia avuto molta attenzione nel migliorare l'ottimizzatore. Dovrai aspettare il 5.7 (ma prova MariaDB se puoi. I loro miglioramenti all'ottimizzatore sono stati apportati nelle versioni 5.3 e 5.5.)
ypercubeᵀᴹ

@ypercube AFAIK no fork ha un miglioramento per ottimizzare la subquery di eliminazione né 5.7. Le eliminazioni si ottimizzano in modo diverso dalle istruzioni SELECT.
Morgan Tocker,

Risposte:


7
  • perché Query Optimizer non è in grado di utilizzare l'indice per l'eliminazione della versione della subquery, mentre lo è durante l'utilizzo della versione del join?

Perché l'ottimizzatore è / era un po 'stupido al riguardo. Non solo per DELETEe UPDATEma anche per le SELECTdichiarazioni, nulla del genere WHERE column IN (SELECT ...)non è stato completamente ottimizzato. Il piano di esecuzione di solito comportava l'esecuzione della sottoquery per ogni riga della tabella esterna ( VARIABLE_SUBSTITUTIONin questo caso). Se quel tavolo è piccolo, va tutto bene. Se è grande, nessuna speranza. Nelle versioni ancora più vecchie, una INsottoquery con una INsotto-sottoquery renderebbe l' EXPLAINesecuzione per anni.

Quello che puoi fare - se vuoi conservare questa query - è usare le ultime versioni che hanno implementato diverse ottimizzazioni e testarle di nuovo. Significato delle ultime versioni: MySQL 5.6 (e 5.7 quando esce dalla beta) e MariaDB 5.5 / 10.0

(aggiornamento) Hai già utilizzato 5.6 che presenta miglioramenti di ottimizzazione, e questo è rilevante: Ottimizzazione delle subquery con trasformazioni semi-join
Suggerisco di aggiungere un indice da (BUILD_KEY)solo. Ce n'è uno composito ma non è molto utile per questa query.

  • esiste un modo (idealmente conforme agli standard) per indurlo a utilizzare l'indice?

Nessuno a cui riesco a pensare. A mio avviso, non vale la pena provare a utilizzare SQL standard. Ci sono così tante differenze e stranezze secondarie che ogni DBMS ha ( UPDATEe le DELETEdichiarazioni sono buoni esempi di tali differenze) che quando si tenta di utilizzare qualcosa che funziona ovunque, il risultato è un sottoinsieme molto limitato di SQL.

  • c'è un modo portatile per scrivere una cancellazione da join? L'applicazione supporta PostgreSQL, MySQL, Oracle e Microsoft SQL Server, utilizzati tramite jdbc e Hibernate.

Stessa risposta della domanda precedente.

  • perché l'eliminazione da VARIABLE_SUBSTITUTION blocca gli inserimenti in BUILDRESULTSUMMARY, che viene utilizzato solo nella sottoselezione?

Non sono sicuro al 100%, ma penso che abbia a che fare con l'esecuzione della subquery più volte e che tipo di blocchi sta prendendo sul tavolo.


"3775190 row lock (s)" da innodb_status (della transazione di cancellazione) è altamente suggestivo. Ma anche "tabelle mysql in uso 2, bloccato 2" non mi sembra troppo bello ..
0x89

2

ecco le risposte per due delle tue domande

  • L'ottimizzatore non è in grado di utilizzare l'indice perché la clausola where cambia per ogni riga. La dichiarazione di eliminazione sarà simile a questa dopo aver superato l'ottimizzatore

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

ma quando si effettua il join, il server è in grado di identificare le righe soggette all'eliminazione.

  • il trucco è usare una variabile per contenere BUILDRESULTSUMMARY_IDe usare la variabile invece della query. Si noti che sia l'inizializzazione variabile che la query di eliminazione devono essere eseguite all'interno di una sessione. Qualcosa come questo.

    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;

    potresti riscontrare problemi con questo se la query restituisce troppi ID e questo non è un modo standard. È solo una soluzione alternativa.

    E non ho una risposta per le altre due domande :)


Ok, ti ​​sei perso il punto. Penso che quello che non è stato considerare è che sia VARIABLE_SUBSTITUTION e BUILDRESULTSUMMARY hanno una colonna denominata BUILDRESULTSUMMARY_ID, quindi dovrebbe essere: 'cancellare dalla VARIABLE_SUBSTITUTION dove esiste (selezionare BUILDRESULTSUMMARY_ID da BUILDRESULTSUMMARY dove BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID = VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID E BUILDRESULTSUMMARY.BUILD_KEY = "BAM -1" );'. Quindi ha senso ed entrambe le query fanno lo stesso.
0x89,

1
sì, mi manca solo un riferimento al tavolo esterno. Ma non è questo il punto. Questo è solo un esempio di come verrà trattato in Optimizer.
Masoud,

Con la leggera differenza che l'ottimizzatore produrrà una query equivalente.
0x89,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.