MySql Gap Lock Deadlock em inserções

8

Estou obtendo Deadlocks de bloqueios de espaço em uma tabela ao inseri-lo frequentemente de várias fontes. Aqui está uma visão geral dos meus processos.

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

A saída de SHOW Create table vehicle_image;é:

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

E o último impasse 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)

Estou executando muitos desses processos simultaneamente, mas nunca executando dois processos que estão usando o mesmo VehicleID. Estou realmente confuso sobre o motivo pelo qual estou recebendo deadlocks.

Tenho temporariamente resolvido o problema utilizando o nível de isolamento READ COMMITTED, mas eu li que este exige mudanças para replicação em que você deve fazer a replicação nível de linha.

Li aqui outras perguntas semelhantes às minhas, mas sou um pouco nova no SQL e ainda não consigo entender por que isso está ocorrendo.

Perguntas semelhantes:
- Deadlock no MySQL insertion statements
- MySQL InnoDB Deadlock Para 2 consultas simples de inserção

ATUALIZAR:

Eu descobri que o uso READ COMMITTEDnão resolveu o problema. Ainda não descobri por que os impasses estão ocorrendo e realmente não sei como diagnosticar mais do que atualmente. Continuo recebendo Deadlocks no meu sistema de produção. Qualquer ajuda seria apreciada.

Brian Sizemore
fonte
Você poderia nos dar mais detalhes - em particular: configuração de disco, número de pratos, características de desempenho? RAM, quanto? CPU, número e desempenho? Número de transações por segundo, por minuto, por hora e por dia? Essas taxas variam com o tempo? Como, exatamente, esses impasses estão afetando o desempenho? Dê-nos a saída de SHOW PROCESSLIST;. Na maioria das vezes, REPEATABLE READé o melhor nível de isolamento para a maioria dos aplicativos, por isso não me preocupo em usá-lo. Houve um aumento perceptível no desempenho quando você o alterou do padrão - REPEATABLE READ?
Vérace 06/04
Como isso pode funcionar? Você tem strings sem aspas entrando VARCHARs.
Rick James
Onde está o loop final?
Rick James
@ RickJames Eu não tenho seqüências de caracteres não citadas no VARCHARS, as consultas funcionam conforme o esperado quando executadas 95% do tempo. O END LOOP é indicado pela tabulação de volta ao mesmo nível. Por exemplo, o loop inicia, eu executo essa instrução insert várias vezes, o loop termina e a transação termina. Observe que o looppseudocódigo é apenas para representar o que está acontecendo.
Brian Sizemore
@ Leitura repetível da Vérace é o padrão para esta tabela (usando o mecanismo innodb). Na verdade, testei alterá-lo repeatable readpara read committedum nível de isolamento mais baixo do que a leitura repetível, mas infelizmente isso não impediu os impasses. Eu sei que o hardware afetará o servidor (é uma instância do ec2, eu precisaria procurar detalhes), mas não acho que essas informações sejam necessárias para entender por que os impasses estão ocorrendo. A natureza esporádica disso também dificulta a captura da saída do show processlist; quando o impasse ocorre.
precisa

Respostas:

4

Eu não sou um especialista em MySQL, mas pela aparência dos seus logs de Deadlock, mesmo que você esteja INSERINDO IDs de veículo diferentes por instrução, eles exigem que todoVehicleID o conjunto de dados (238326) do índice não agrupado seja bloqueado .

O fato de você estar ocasionalmente recebendo impasses significa que, em 1 página, você tem vários IDs de veículo, então há uma pequena chance de que 2 processos diferentes precisem de um bloqueio para a mesma página.

A melhor coisa a aconselhar é manter as transações o menor possível .

Se houver alguma maneira de fazer o seguinte, isso ajudará a diminuir a chance de um impasse:

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

Se puder, tente alterar o fator de preenchimento desse índice para 95% e teste para verificar se você obtém menos impasses.

Um teste mais extremo seria remover completamente esse índice durante a INSERÇÃO e, em seguida, recriá-lo quando terminar.

Oreo
fonte
Você tem alguma idéia de por que a página inteira estaria sendo bloqueada contra apenas as linhas que estou tentando inserir?
Brian Sizemore
11
Além disso, vou refatorar meu código um pouco e reduzir o tempo de transação. Eu acredito que você está certo de que isso deve fazer uma diferença significativa.
Brian Sizemore
Não sei como funcionam os internos do MySQL, mas esta resposta explica isso para o MS SQL. Algumas boas dicas do MySQL no Manual do MySQL também.
Oreo
11
O MySQL não fornece controle sobre o fator de preenchimento.
Rick James
2
Depois de refatorar meu código para enfileirar minhas inserções e a instrução de atualização e executá-las muito próximas, o problema foi resolvido. Não apenas isso, mas eu fui capaz de continuar ampliando isso (aproximadamente o dobro da quantidade anterior de processos paralelos) e ainda está funcionando corretamente. Obrigado Oreo!
Brian Sizemore
1

O MySQL não apenas bloqueia a linha afetada, mas também a linha de índice afetada e o espaço entre as linhas de índice (conforme descrito aqui ). Como as chaves primárias são sempre indexadas e você as utiliza em suas atualizações, suspeito que várias transações que tentam atualizar várias linhas resultem em bloqueios de gap de índice sobrepostos que, por sua vez, criam o impasse.

Para resolver isso, também recomendo o conselho da Oreos para manter uma transação o menor possível. Se as linhas atualizadas forem independentes uma da outra, você deve usar uma transação separada para cada uma delas.

Flourid
fonte