MySql Gap Lock Deadlock en insertos

8

Obtengo puntos muertos de bloqueos de espacio en una tabla cuando la inserto con frecuencia desde múltiples fuentes. Aquí hay una descripción general de mis procesos.

START TRANSACTION
  UPDATE vehicle_image
  SET active = 0
  WHERE vehicleID = SOMEID AND active = 1

  Loop:
    INSERT INTO vehicle_image (vehicleID, vehicleImageFilePath, vehicleImageSplashFilePath
      ,vehicleImageThumbnailFilePath, vehicleImageMiniFilePath, mainVehicleImage, active)
    VALUES (%s, %s, %s, %s, %s, %s, 1);
END TRANSACTION

La salida de SHOW Create table vehicle_image;es:

CREATE TABLE `vehicle_image` (
  `vehicleImageID` int(11) NOT NULL AUTO_INCREMENT,
  `vehicleID` int(11) DEFAULT NULL,
  `vehicleImageFilePath` varchar(200) DEFAULT NULL,
  `vehicleImageSplashFilePath` varchar(200) DEFAULT NULL,
  `vehicleImageThumbnailFilePath` varchar(200) DEFAULT NULL,
  `vehicleImageMiniFilePath` varchar(200) DEFAULT NULL,
  `mainVehicleImage` bit(1) DEFAULT NULL,
  `active` bit(1) DEFAULT b'1',
  `userCreated` int(11) DEFAULT NULL,
  `dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `userModified` int(11) DEFAULT NULL,
  `dateModified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`vehicleImageID`),
  KEY `active` (`active`),
  KEY `mainvehicleimage` (`mainVehicleImage`),
  KEY `vehicleid` (`vehicleID`)
) ENGINE=InnoDB AUTO_INCREMENT=22878102 DEFAULT CHARSET=latin1

Y el último punto muerto dado por SHOW engine innodb status:

LATEST DETECTED DEADLOCK
------------------------
2018-03-27 12:31:15 11a58
*** (1) TRANSACTION:
TRANSACTION 5897678083, ACTIVE 2 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1248, 2 row lock(s), undo log entries 1
MySQL thread id 873570, OS thread handle 0x124bc, query id 198983754 ec2-34-239-240-179.compute-1.amazonaws.com 34.239.240.179 image_processor update
INSERT INTO vehicle_image (vehicleID, vehicleImageFilePath, vehicleImageSplashFilePath, vehicleImageThumbnailFilePath, vehicleImageMiniFilePath, mainVehicleImage, active)
VALUES (70006176, 'f180928(1)1522168276.230837full.jpg', 'f180928(1)1522168276.230837splash.jpg', 'f180928(1)1522168276.230837thumb.jpg', 'f180928(1)1522168276.230837mini.jpg', 1, 1)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 875 page no 238326 n bits 472
  index `vehicleid` of table `ipacket`.`vehicle_image` trx id 5897678083
  lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 378 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 842c365a; asc  ,6Z;;
 1: len 4; hex 815d03bc; asc  ]  ;;

*** (2) TRANSACTION:
TRANSACTION 5897678270, ACTIVE 1 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1248, 2 row lock(s), undo log entries 1
MySQL thread id 873571, OS thread handle 0x11a58, query id 198983849 ec2-35-171-169-21.compute-1.amazonaws.com 35.171.169.21 image_processor update
INSERT INTO vehicle_image (vehicleID, vehicleImageFilePath, vehicleImageSplashFilePath, vehicleImageThumbnailFilePath, vehicleImageMiniFilePath, mainVehicleImage, active)
VALUES (70006326, '29709(1)1522168277.4443843full.jpg', '29709(1)1522168277.4443843splash.jpg', '29709(1)1522168277.4443843thumb.jpg', '29709(1)1522168277.4443843mini.jpg', 1, 1)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 875 page no 238326 n bits 464
  index `vehicleid` of table `ipacket`.`vehicle_image` trx id 5897678270
  lock_mode X locks gap before rec
Record lock, heap no 378 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 842c365a; asc  ,6Z;;
 1: len 4; hex 815d03bc; asc  ]  ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 875 page no 238326 n bits 472
  index `vehicleid` of table `ipacket`.`vehicle_image` trx id 5897678270
  lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 378 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 842c365a; asc  ,6Z;;
 1: len 4; hex 815d03bc; asc  ]  ;;

*** WE ROLL BACK TRANSACTION (2)

Estoy ejecutando muchos de estos procesos simultáneamente, pero nunca ejecuto dos procesos que usan el mismo VehicleID. Estoy realmente confundido en cuanto a por qué tengo Deadlocks.

He resuelto temporalmente el problema utilizando el nivel de aislamiento READ COMMITTED, pero he leído que esto requiere cambios en la replicación en el sentido de que debe hacer la replicación a nivel de fila.

He leído otras preguntas aquí que son similares a las mías, pero soy algo nuevo en SQL y todavía no puedo entender por qué ocurre esto.

Preguntas similares:
- Deadlock en declaraciones de inserción de MySQL
- MySQL InnoDB Deadlock para 2 consultas de inserción simples

ACTUALIZAR:

Descubrí que el uso READ COMMITTEDno ha resuelto el problema. Todavía no he descubierto por qué están ocurriendo los puntos muertos y realmente no sé cómo diagnosticar más allá de lo que tengo actualmente. Continúo obteniendo Deadlocks en mi sistema de producción. Cualquier ayuda sería apreciada.

Brian Sizemore
fuente
¿Podría darnos más detalles, en particular: configuración del disco, número de platos, características de rendimiento? RAM, cuanto? CPU, número y rendimiento? ¿Número de transacciones por segundo, por minuto, por hora y por día? ¿Estas tarifas varían con el tiempo? ¿Cómo, exactamente, están estos puntos muertos afectando el rendimiento? Danos la salida de SHOW PROCESSLIST;. La mayoría de las veces, REPEATABLE READes el mejor nivel de aislamiento para la mayoría de las aplicaciones, por lo que no me preocuparía demasiado usarlo. ¿Hubo un aumento notable en el rendimiento cuando lo cambiaste por defecto REPEATABLE READ?
Vérace
¿Cómo puede funcionar eso? Tienes cadenas sin comillas al entrar VARCHARs.
Rick James
¿Dónde está el END LOOP?
Rick James
@ RickJames No tengo cadenas sin comillas en VARCHARS, las consultas funcionan como se esperaba cuando se ejecutan el 95% del tiempo. END LOOP se denota al volver a tabular al mismo nivel. Por ejemplo, el ciclo comienza, ejecuto esa instrucción de inserción varias veces, luego el ciclo finaliza y finaliza la transacción. Tenga en cuenta que loopes solo un seudocódigo para representar lo que está sucediendo.
Brian Sizemore
@ Vérace Repetible Read es el valor predeterminado para esta tabla (con motor innodb). Realmente probé cambiarlo repeatable reada read committedun nivel de aislamiento más bajo que la lectura repetible, pero desafortunadamente no detuvo los puntos muertos. Sé que el hardware afectará al servidor (es una instancia ec2, tendría que buscar detalles), pero no creo que esa información sea necesaria para comprender por qué están ocurriendo los puntos muertos. La naturaleza esporádica de esto también hace que sea difícil capturar la salida de show processlist; cuando se produce el punto muerto.
Brian Sizemore

Respuestas:

4

No soy un experto en MySQL, pero por el aspecto de sus registros de Deadlock, a pesar de que está INSERTANDO diferentes ID de vehículos por declaración, estos requieren que toda la página de datos (238326) del VehicleIDíndice no agrupado esté bloqueada .

El hecho de que ocasionalmente tenga puntos muertos significa que dentro de 1 página tiene múltiples ID de vehículos, por lo que hay una pequeña posibilidad de que 2 procesos diferentes necesiten un bloqueo para la misma página.

Lo mejor para aconsejar es mantener sus transacciones lo más pequeñas posible .

Si hay alguna manera de hacer lo siguiente, ayudará a disminuir la posibilidad de un punto muerto:

START TRANSACTION;
  UPDATE vehicle_image SET active = 0 WHERE vehicleID = SOMEID and active = 1;
END TRANSACTION;
Loop:
  START TRANSACTION;
  INSERT INTO vehicle_image (vehicleID, vehicleImageFilePath,
    vehicleImageSplashFilePath, vehicleImageThumbnailFilePath,
    vehicleImageMiniFilePath, mainVehicleImage, active)
  VALUES (%s, %s, %s, %s, %s, %s, 1);  
  END TRANSACTION;
--EndLoop here

Si puede, intente cambiar el factor de relleno de ese índice al 95% y pruebe para ver si tiene menos puntos muertos.

Una prueba más extrema sería eliminar ese índice por completo al INSERTAR y luego volver a crearlo cuando haya terminado.

Oreo
fuente
¿Tiene alguna idea de por qué toda la página se bloquearía frente a las filas que estoy tratando de insertar?
Brian Sizemore el
1
Además, voy a refactorizar un poco mi código y reducir el tiempo de transacción. Creo que tienes razón en que debería marcar una diferencia significativa.
Brian Sizemore el
No estoy seguro de cómo funcionan los componentes internos de MySQL, pero esta respuesta lo explica para MS SQL. Algunos buenos consejos de MySQL en el Manual de MySQL también.
Oreo
1
MySQL no proporciona control sobre el factor de relleno.
Rick James
2
Después de refactorizar mi código para poner en cola mis inserciones y la declaración de actualización y ejecutarlos muy juntos, ha resuelto mi problema. No solo eso, sino que he podido seguir ampliando esto (aproximadamente el doble de la cantidad anterior de procesos paralelos) y todavía funciona correctamente. Gracias Oreo!
Brian Sizemore
1

MySQL no solo bloquea la fila afectada, sino también la fila de índice afectada y el espacio entre las filas de índice (como se describe aquí ). Dado que las claves primarias siempre están indexadas y las usa en sus actualizaciones, sospecho que las transacciones múltiples que intentan actualizar varias filas resultan en bloqueos de brecha de índice superpuestos que a su vez crean el punto muerto.

Para resolver esto, también recomiendo el consejo de Oreos para mantener una transacción lo más pequeña posible. Si las filas actualizadas son independientes entre sí, debe usar una transacción separada para cada una de ellas.

Flourid
fuente