J'ai exécuté un client multi-fileté de 25 threads pour faire des appels d'API simultanés et insérer des données sur AWS Aurora Server.
Après un certain temps, j'ai commencé à voir l'erreur de délai d'expiration: lock wait timeout exceeded try restarting transaction
. Nous exécutons le même test pour un serveur exécutant MySQL 5.6.10 et aucun délai d'attente de verrouillage n'est arrivé.
Y a-t-il un moyen d'éviter ce délai d'attente?
Sur le serveur AWS Aurora, SHOW ENGINE INNODB STATUS
montré:
---TRANSACTION 8530565676, ACTIVE 81 sec setting auto-inc lock
mysql tables in use 2, locked 2
LOCK WAIT 6 lock struct(s), heap size 376, 2 row lock(s), undo log entries 1
MySQL thread id 405, OS thread handle 0x2ae270b03700, query id 11045 10.50.101.56 app_migration
INSERT INTO contacts_contactaudit (action,
contact_id,
date_created,
date_updated,
external_contact_id,
entity_name,
first_name,
last_name,
middle_name,
actor_created_id,
actor_updated_id,
email,
phone_number_id,
external_contact_guid,
external_shared_contact_id,
active_timezone, audit_date)
SELECT 'I' as action, new.id,
new.date_created,
new.date_updated,
new.external_contact_id,
new.entity_name,
new.first_name,
new.last_name,
new.middle_name,
new.actor_created_id,
new.actor_updated_id,
new.email,
new.phone_number_id,
new.external_contact_guid,
new.external_shared_contact_id,
new.active_timezone, now();
Ceci est la gâchette que nous avons créée pour les insertions énoncées:
CREATE TRIGGER contacts_contact_insert_audit
AFTER INSERT ON contacts_contact
FOR EACH ROW
INSERT INTO contacts_contactaudit (action,
...
audit_date)
SELECT 'I' as action, new.id,
...
now();
Et c'est le schéma de la table d'audit:
CREATE TABLE `contacts_contactaudit` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`date_created` datetime(6) DEFAULT NULL,
`date_updated` datetime(6) DEFAULT NULL,
`action` varchar(1) NOT NULL,
`audit_date` datetime(6) NOT NULL,
`contact_id` int(11) DEFAULT NULL,
`external_contact_id` bigint(20) DEFAULT NULL,
`entity_name` varchar(128) DEFAULT NULL,
`first_name` varchar(128) DEFAULT NULL,
`last_name` varchar(128) DEFAULT NULL,
`middle_name` varchar(128) DEFAULT NULL,
`actor_created_id` int(11) DEFAULT NULL,
`actor_updated_id` int(11) DEFAULT NULL,
`email` varchar(256) DEFAULT NULL,
`phone_number_id` int(11) DEFAULT NULL,
`external_contact_guid` varchar(128) DEFAULT NULL,
`external_shared_contact_id` bigint(20) DEFAULT NULL,
`active_timezone` varchar(128),
PRIMARY KEY (`id`),
KEY `contacts_contactaud_actor_created_id_3f6f4269_fk_actors_actor_id` (`actor_created_id`),
KEY `contacts_contactaud_actor_updated_id_2fafc937_fk_actors_actor_id` (`actor_updated_id`),
KEY `contacts_contactaudit_contact_id_9b809fe7_uniq` (`contact_id`),
CONSTRAINT `contacts_contactaud_actor_created_id_3f6f4269_fk_actors_actor_id` FOREIGN KEY (`actor_created_id`) REFERENCES `actors_actor` (`id`),
CONSTRAINT `contacts_contactaud_actor_updated_id_2fafc937_fk_actors_actor_id` FOREIGN KEY (`actor_updated_id`) REFERENCES `actors_actor` (`id`)
)
ENGINE=InnoDB
AUTO_INCREMENT=21577
DEFAULT CHARSET=utf8;
Peut-être que des API de "maître" peuvent-ils faire un sous-ensemble des 250-300 inserts?
Pour l'acide, chacun transaction nécessite une écriture de disque - c'est ce qui limite votre vitesse. Ne sachant pas les détails de votre SQL, je ne peux que deviner les choses pour aider:
INSERTs
dans une table (plusieurs lignes dans une seule transaction).INSERTs
. (Cela fonctionne bien si plusieurs tables sont impliquées.) Attention: alors qu'elle réduit (radicalement) sur les chances de délai d'attente, cela augmente les chances d'impasse. Soyez prêt à rejouer une transaction qui obtient rollowback.TRIGGERs
. Depuis que vous avez une API, cela pourrait faire l'audit dans des requêtes séparées, permettant ainsi une plus grande flexibilité dans la construction de transactions.Quelques autres choses à vérifier ... Combien de rangées dans la table principale et la table d'audit? Quelle est la valeur de innodb_buffer_pool_size
? Combien de bélier?
Nous avons identifié la cause première: c'est le innodb_autoinc_lock_mode = 1
.
Voici le résumé de le Doc officiel :
traditional lock mode
, prévu pour la compatibilité en arrière, les tests de performance et le travail autour des problèmes avec des "inserts mixtes", en raison des différences possibles en sémantique.consecutive lock mode
: Dans ce mode, "inserts en vrac" utilise la serrure de niveau de table spécial Auto-Inc et la maintenez-la jusqu'à la fin de la déclaration. Ceci s'applique à tous les inserts ... Sélectionnez, remplacez ... Sélectionnez et chargez des relevés de données. Une seule instruction contenant la serrure Auto-Inc peut exécuter à la foisinterleaved lock mode
: Dans ce mode de verrouillage, aucune instruction "Insert-like" n'utilise la serrure Auto-Inc de niveau de table et plusieurs instructions peuvent exécuter en même temps. Il s'agit du mode de verrouillage le plus rapide et le plus évolutif, mais il n'est pas sûr lorsque vous utilisez une réplication ou des scénarios de récupération basés sur une instruction lorsque des instructions SQL sont rejouées à partir du journal binaire.Dans notre cas, lorsque plusieurs API appelées insérences insertions, si un appel de l'API est prolongé, cette instruction insertion conserve une serrure de table sur la table de destination, qui provoque ensuite les délais d'attente.
Nous l'allumons à innodb_autoinc_lock_mode = 2
, redémarrez le serveur et le voilà. Maintenant, nous pouvons appeler plusieurs API pour insérer dans la même table sans avoir des délais.