MySQL: supprimer… where..in () vs delete..from..join, et les tables verrouillées lors de la suppression avec une sous-sélection

9

Avertissement: veuillez excuser mon manque de connaissances sur les bases de données internes. Ça y est:

Nous exécutons une application (non écrite par nous) qui a un gros problème de performances dans un travail de nettoyage périodique dans la base de données. La requête ressemble à ceci:

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

Simple, facile à lire et SQL standard. Mais malheureusement très lent. L'explication de la requête montre que l'index existant sur VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_IDn'est pas utilisé:

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 |

Cela le rend très lent (120 secondes et plus). En plus de cela, il semble bloquer les requêtes qui tentent de s'insérer dans BUILDRESULTSUMMARY, la sortie de 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')

Cela ralentit le système et nous oblige à augmenter innodb_lock_wait_timeout.

Lorsque nous exécutons MySQL, nous avons réécrit la requête de suppression pour utiliser "supprimer de la jointure":

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

C'est un peu moins facile à lire, malheureusement pas de SQL standard (pour autant que j'ai pu le découvrir), mais beaucoup plus rapide (environ 0,02 seconde) car il utilise l'index:

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                     |

Information additionnelle:

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 |

(certains trucs omis, c'est une table assez large).

J'ai donc quelques questions à ce sujet:

  • pourquoi l'optimiseur de requêtes n'est-il pas en mesure d'utiliser l'index pour la suppression lors de la version de la sous-requête, alors qu'il l'est lors de l'utilisation de la version join?
  • Existe-t-il un moyen (idéalement conforme aux normes) de l'inciter à utiliser l'index? ou
  • existe-t-il un moyen portable d'écrire un delete from join? L'application prend en charge PostgreSQL, MySQL, Oracle et Microsoft SQL Server, utilisé via jdbc et Hibernate.
  • pourquoi la suppression du VARIABLE_SUBSTITUTIONblocage des insertions dans BUILDRESULTSUMMARY, qui n'est utilisée que dans la sous-sélection?
0x89
la source
Percona Server 5.6.24-72.2-1.jessie resp 5.6.24-72.2-1.wheezy (sur le système de test).
0x89
Oui, toute la base de données utilise innodb.
0x89
Il semble donc que 5.6 n'ait pas beaucoup d'attention dans l'amélioration de l'optimiseur. Vous devrez attendre la version 5.7 (mais essayez MariaDB si vous le pouvez. Leurs améliorations de l'optimiseur ont été faites dans leurs versions 5.3 et 5.5.)
ypercubeᵀᴹ
@ypercube AFAIK no fork a une amélioration pour rendre la sous-requête de suppression optimisée ni 5.7. Les suppressions sont optimisées différemment des instructions SELECT.
Morgan Tocker

Réponses:

7
  • pourquoi l'optimiseur de requêtes n'est-il pas en mesure d'utiliser l'index pour la suppression lors de la version de la sous-requête, alors qu'il l'est lors de l'utilisation de la version join?

Parce que l'optimiseur est / était un peu stupide à cet égard. Non seulement pour DELETEet UPDATEmais aussi pour les SELECTinstructions, rien de tel WHERE column IN (SELECT ...)n'était pas entièrement optimisé. Le plan d'exécution impliquait généralement l'exécution de la sous-requête pour chaque ligne de la table externe ( VARIABLE_SUBSTITUTIONdans ce cas). Si cette table est petite, tout va bien. Si c'est grand, aucun espoir. Dans les versions encore plus anciennes, une INsous - requête avec une INsous-sous-requête ferait même EXPLAINfonctionner pendant des siècles.

Ce que vous pouvez faire - si vous souhaitez conserver cette requête - est d'utiliser les dernières versions qui ont implémenté plusieurs optimisations et de tester à nouveau. Dernières versions signifiant: MySQL 5.6 (et 5.7 à la sortie de la bêta) et MariaDB 5.5 / 10.0

(mise à jour) Vous utilisez déjà la version 5.6 qui présente des améliorations d'optimisation, et celle-ci est pertinente: Optimisation des sous-requêtes avec des transformations de semi-jointure
Je suggère d'ajouter un index (BUILD_KEY)uniquement. Il y en a un composite mais ce n'est pas très utile pour cette requête.

  • Existe-t-il un moyen (idéalement conforme aux normes) de l'inciter à utiliser l'index?

Aucun que je puisse penser. À mon avis, il ne vaut pas la peine d'essayer d'utiliser du SQL standard. Il y a tellement de différences et de bizarreries mineures que chaque SGBD a ( UPDATEet les DELETEinstructions sont de bons exemples de telles différences) que lorsque vous essayez d'utiliser quelque chose qui fonctionne partout, le résultat est un sous-ensemble très limité de SQL.

  • existe-t-il un moyen portable d'écrire une suppression de la jointure? L'application prend en charge PostgreSQL, MySQL, Oracle et Microsoft SQL Server, utilisé via jdbc et Hibernate.

Même réponse que la question précédente.

  • pourquoi la suppression de VARIABLE_SUBSTITUTION bloque-t-elle les insertions dans BUILDRESULTSUMMARY, qui n'est utilisée que dans la sous-sélection?

Pas sûr à 100%, mais je pense que cela a à voir avec l'exécution de la sous-requête plusieurs fois et le type de verrous qu'elle prend sur la table.

ypercubeᵀᴹ
la source
"3775190 verrou (s) de ligne" de innodb_status (de la transaction de suppression) est très suggestif. Mais aussi "les tables mysql en cours d'utilisation 2, verrouillées 2" ne me semblent pas très bonnes ..
0x89
2

voici les réponses à deux de vos questions

  • L'optimiseur n'est pas en mesure d'utiliser l'index car la clause where change pour chaque ligne. L'instruction delete ressemblera à ceci après avoir passé l'optimiseur

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

mais lorsque vous effectuez la jointure, le serveur est en mesure d'identifier les lignes soumises à suppression.

  • astuce consiste à utiliser une variable pour contenir le BUILDRESULTSUMMARY_IDet utiliser la variable au lieu de la requête. Notez que l'initialisation de variable et la requête de suppression doivent s'exécuter dans une session. Quelque chose comme ça.

    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;

    vous pourriez être confronté à un problème si la requête renvoie trop d'identifiants et ce n'est pas un moyen standard. C'est juste une solution de contournement.

    Et je n'ai pas de réponse pour vos deux autres questions :)

Masoud
la source
D'accord, vous avez manqué mon point. Je pense que ce que vous n'avez pas envisagé est que les deux VARIABLE_SUBSTITUTION et BUILDRESULTSUMMARY ont une colonne nommée BUILDRESULTSUMMARY_ID, il devrait donc être: « supprimer de VARIABLE_SUBSTITUTION où EXISTE (sélectionnez BUILDRESULTSUMMARY_ID de BUILDRESULTSUMMARY où BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID = VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID ET BUILDRESULTSUMMARY.BUILD_KEY = « BAM -1");'. Ensuite, cela a du sens et les deux requêtes font de même.
0x89
1
ouais je manque juste une référence à la table extérieure. Mais ce n'est pas le sujet. C'est juste une illustration de la façon dont il sera traité dans l'optimiseur.
Masoud
Avec la légère différence que l'optimiseur produira une requête équivalente.
0x89