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_ID
n'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_SUBSTITUTION
blocage des insertions dansBUILDRESULTSUMMARY
, qui n'est utilisée que dans la sous-sélection?
Réponses:
Parce que l'optimiseur est / était un peu stupide à cet égard. Non seulement pour
DELETE
etUPDATE
mais aussi pour lesSELECT
instructions, rien de telWHERE 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_SUBSTITUTION
dans ce cas). Si cette table est petite, tout va bien. Si c'est grand, aucun espoir. Dans les versions encore plus anciennes, uneIN
sous - requête avec uneIN
sous-sous-requête ferait mêmeEXPLAIN
fonctionner 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.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 (
UPDATE
et lesDELETE
instructions 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.Même réponse que la question précédente.
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.
la source
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
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_ID
et 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.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 :)
la source