Isenção de responsabilidade: desculpe meu desconhecimento sobre os dados internos do banco de dados. Aqui vai:
Executamos um aplicativo (não escrito por nós) que apresenta um grande problema de desempenho em um trabalho de limpeza periódica no banco de dados. A consulta fica assim:
delete from VARIABLE_SUBSTITUTION where BUILDRESULTSUMMARY_ID in (
select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");
SQL simples, fácil de ler e padrão. Mas infelizmente muito lento. Explicar a consulta mostra que o índice existente no VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID
não é usado:
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 |
Isso torna muito lento (120 segundos e mais). Além disso, parece bloquear as consultas que tentam inserir na BUILDRESULTSUMMARY
saída 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')
Isso diminui a velocidade do sistema e nos força a aumentar innodb_lock_wait_timeout
.
Enquanto rodamos o MySQL, reescrevemos a consulta delete para usar "delete from join":
delete VARIABLE_SUBSTITUTION from VARIABLE_SUBSTITUTION join BUILDRESULTSUMMARY
on VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID = BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID
where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1";
Isso é um pouco menos fácil de ler, infelizmente, nenhum SQL padrão (até onde pude descobrir), mas muito mais rápido (0,02 segundos), pois ele usa o índice:
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 |
Informação adicional:
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 |
(algumas coisas omitidas, é uma mesa bastante ampla).
Então, eu tenho algumas perguntas sobre isso:
- por que o otimizador de consulta não pode usar o índice para excluir quando a versão da subconsulta, enquanto está usando a versão de junção?
- existe alguma maneira (idealmente compatível com os padrões) de induzi-lo a usar o índice? ou
- existe uma maneira portátil de escrever um
delete from join
? O aplicativo suporta PostgreSQL, MySQL, Oracle e Microsoft SQL Server, usado via jdbc e Hibernate. - por que a exclusão do
VARIABLE_SUBSTITUTION
bloqueio de inserçõesBUILDRESULTSUMMARY
é usada apenas na subseleção?
Respostas:
Porque o otimizador é / era um pouco burro nesse sentido. Não apenas para
DELETE
e tambémUPDATE
paraSELECT
declarações, algo assimWHERE column IN (SELECT ...)
não foi totalmente otimizado. O plano de execução geralmente envolvia a execução da subconsulta para todas as linhas da tabela externa (VARIABLE_SUBSTITUTION
neste caso). Se essa mesa é pequena, está tudo bem. Se for grande, não há esperança. Em versões ainda mais antigas, umaIN
subconsulta com umaIN
subconsulta tornariaEXPLAIN
a execução por séculos.O que você pode fazer - se deseja manter essa consulta - é usar as versões mais recentes que implementaram várias otimizações e testar novamente. Versões mais recentes: MySQL 5.6 (e 5.7 quando sai da versão beta) e MariaDB 5.5 / 10.0
(atualização) Você já usa o 5.6, que possui aprimoramentos de otimização, e este é relevante: Otimizando subconsultas com transformações de semi-junção
Sugiro adicionar um índice
(BUILD_KEY)
sozinho. Existe um composto, mas isso não é muito útil para esta consulta.Nada que eu possa pensar. Na minha opinião, não vale a pena tentar usar o SQL padrão. Existem tantas diferenças e peculiaridades menores que cada DBMS possui (
UPDATE
e asDELETE
instruções são bons exemplos dessas diferenças) que, quando você tenta usar algo que funciona em qualquer lugar, o resultado é um subconjunto muito limitado de SQL.Mesma resposta que a pergunta anterior.
Não tenho 100% de certeza, mas acho que isso tem a ver com a execução da subconsulta várias vezes e com o tipo de bloqueios que está ocorrendo na tabela.
fonte
aqui estão as respostas para duas de suas perguntas
O Optimizer não pode usar o índice porque a cláusula where é alterada para cada linha. A instrução delete será mais ou menos assim depois de passar no otimizador
mas quando você faz a associação, o servidor consegue identificar as linhas sujeitas a exclusão.
O truque é usar uma variável para conter
BUILDRESULTSUMMARY_ID
e usar a variável em vez da consulta. Observe que a inicialização da variável e a consulta de exclusão devem ser executadas dentro de uma sessão. Algo assim.você pode enfrentar problemas com isso se a consulta retornar muitos IDs e isso não for uma maneira padrão. É apenas uma solução alternativa.
E eu não tenho uma resposta para suas outras duas perguntas :)
fonte