MySQL: eliminar ... where..in () vs delete..from..join, y tablas bloqueadas en delete con subselect

9

Descargo de responsabilidad: disculpe mi falta de conocimiento sobre las bases de datos internas. Aquí va:

Ejecutamos una aplicación (no escrita por nosotros) que tiene un gran problema de rendimiento en un trabajo de limpieza periódica en la base de datos. La consulta se ve así:

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

Directo, fácil de leer y SQL estándar. Pero desafortunadamente muy lento. Explicar la consulta muestra que el índice existente en VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_IDno se utiliza:

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 |

Esto lo hace muy lento (120 segundos y más). Además de eso, parece bloquear las consultas que intentan insertarse en la BUILDRESULTSUMMARYsalida 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')

Esto ralentiza el sistema y nos obliga a aumentar innodb_lock_wait_timeout.

A medida que ejecutamos MySQL, reescribimos la consulta de eliminación para usar "eliminar de la unión":

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

Esto es un poco menos fácil de leer, desafortunadamente no hay SQL estándar (por lo que pude descubrir), pero mucho más rápido (0.02 segundos más o menos) ya que usa el í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                     |

Información 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 |

(se omiten algunas cosas, es una tabla bastante amplia).

Así que tengo algunas preguntas sobre esto:

  • ¿Por qué el optimizador de consultas no puede utilizar el índice para eliminar cuando se ejecuta la versión de subconsulta, mientras se utiliza la versión de combinación?
  • ¿Hay alguna forma (idealmente conforme a los estándares) de engañarlo para que use el índice? o
  • ¿Hay alguna forma portátil de escribir un delete from join? La aplicación es compatible con PostgreSQL, MySQL, Oracle y Microsoft SQL Server, utilizados a través de jdbc e Hibernate.
  • ¿Por qué la eliminación de VARIABLE_SUBSTITUTIONlas inserciones de bloqueo en BUILDRESULTSUMMARY, que solo se utiliza en la subselección?
0x89
fuente
Percona Server 5.6.24-72.2-1.jessie resp 5.6.24-72.2-1.wheezy (en el sistema de prueba).
0x89
Sí, toda la base de datos usa innodb.
0x89
Parece que 5.6 no ha prestado mucha atención en mejorar el optimizador. Tendrá que esperar el 5.7 (pero intente MariaDB si puede. Sus mejoras del optimizador se realizaron en sus versiones 5.3 y 5.5.)
ypercubeᵀᴹ
@ypercube AFAIK no fork tiene una mejora para optimizar la subconsulta de eliminación ni 5.7. Las eliminaciones se optimizan de manera diferente a las instrucciones SELECT.
Morgan Tocker

Respuestas:

7
  • ¿Por qué el optimizador de consultas no puede utilizar el índice para eliminar cuando se ejecuta la versión de subconsulta, mientras se utiliza la versión de combinación?

Porque el optimizador es / era un poco tonto en ese sentido. No solo para DELETEy UPDATEsino también para las SELECTdeclaraciones, algo así WHERE column IN (SELECT ...)no se optimizó por completo. El plan de ejecución generalmente implicaba ejecutar la subconsulta para cada fila de la tabla externa ( VARIABLE_SUBSTITUTIONen este caso). Si esa mesa es pequeña, todo está bien. Si es grande, no hay esperanza. Incluso en versiones anteriores, una INsubconsulta con una INsubconsulta haría que incluso se EXPLAINejecutara durante siglos.

Lo que puede hacer, si desea mantener esta consulta, es usar las últimas versiones que han implementado varias optimizaciones y volver a probar. Significado de las últimas versiones: MySQL 5.6 (y 5.7 cuando sale de beta) y MariaDB 5.5 / 10.0

(actualización) Ya usa 5.6, que tiene mejoras de optimización, y esta es relevante: Optimización de subconsultas con transformaciones de semi-unión
. Sugiero agregar un índice (BUILD_KEY)solo. Hay uno compuesto pero que no es muy útil para esta consulta.

  • ¿Hay alguna forma (idealmente conforme a los estándares) de engañarlo para que use el índice?

Ninguno que se me ocurra. En mi opinión, no vale la pena intentar usar SQL estándar. Hay tantas diferencias y peculiaridades menores que cada DBMS tiene ( UPDATEy las DELETEdeclaraciones son buenos ejemplos de tales diferencias) que cuando intenta usar algo que funciona en todas partes, el resultado es un subconjunto muy limitado de SQL.

  • ¿Hay alguna forma portátil de escribir una eliminación de unirse? La aplicación es compatible con PostgreSQL, MySQL, Oracle y Microsoft SQL Server, utilizados a través de jdbc e Hibernate.

La misma respuesta que la pregunta anterior.

  • ¿por qué la eliminación de VARIABLE_SUBSTITUTION bloquea las inserciones en BUILDRESULTSUMMARY, que solo se usa en la subselección?

No estoy 100% seguro, pero creo que tiene que ver con ejecutar la subconsulta varias veces y qué tipo de bloqueos está aplicando a la tabla.

ypercubeᵀᴹ
fuente
"Bloqueo de fila 3775190" de innodb_status (de la transacción de eliminación) es muy sugerente. Pero también "las tablas mysql en uso 2, bloqueadas 2" no me parecen demasiado buenas ..
0x89
2

Aquí están las respuestas a dos de sus preguntas.

  • Optimizer no puede usar el índice porque la cláusula where cambia para cada fila. La declaración de eliminación se verá así después de pasar el optimizador

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

pero cuando realiza la unión, el servidor puede identificar las filas sujetas a eliminación.

  • El truco consiste en utilizar una variable para mantener BUILDRESULTSUMMARY_IDy utilizar la variable en lugar de la consulta. Tenga en cuenta que tanto la inicialización de variables como la consulta de eliminación deben ejecutarse dentro de una sesión. Algo como esto.

    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;
    

    puede tener problemas con esto si la consulta devuelve demasiados identificadores y esta no es una forma estándar. Es solo una solución alternativa.

    Y no tengo una respuesta para tus otras dos preguntas :)

Masoud
fuente
Está bien, perdiste mi punto. Creo que lo que no tuvo en cuenta es que tanto VARIABLE_SUBSTITUTION y BUILDRESULTSUMMARY tienen una columna llamada BUILDRESULTSUMMARY_ID, por lo que debe ser: 'eliminar de VARIABLE_SUBSTITUTION donde existe (seleccione BUILDRESULTSUMMARY_ID de BUILDRESULTSUMMARY donde BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID = VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID Y BUILDRESULTSUMMARY.BUILD_KEY = "BAM -1 "); '. Entonces tiene sentido, y ambas consultas hacen lo mismo.
0x89
1
Sí, me falta una referencia a la tabla exterior. Pero ese no es el punto. Eso es solo una ilustración de cómo se tratará en el optimizador.
Masoud
Con la ligera diferencia de que el optimizador producirá una consulta equivalente.
0x89