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.


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.


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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.