web-dev-qa-db-fra.com

MariaDB - "ERREUR 1205 ... Dépassement du délai d'attente de verrouillage; ..." lors de plusieurs mises à jour parallèles affectant différentes lignes

Nous utilisons MariaDB 10.1.19 hébergeant une base de données héritée. Nous avons une table lpr (sur InnoDB) où nous voulons passer d'une colonne de texte gate à une clé étrangère normalisée gate_id. Notre code effectue la MISE À JOUR en parallèle, comme ceci:

UPDATE lpr SET gate_id=1 WHERE gate_id IS null AND gate LIKE '%[1]%'
UPDATE lpr SET gate_id=2 WHERE gate_id IS null AND gate LIKE '%[2]%'
UPDATE lpr SET gate_id=3 WHERE gate_id IS null AND gate LIKE '%[3]%'
UPDATE lpr SET gate_id=4 WHERE gate_id IS null AND gate LIKE '%[4]%'
...

puisque les lignes affectées par toutes les MISES À JOUR sont disjointes et que la table est sur InnoDB, nous ne nous attendrions pas à une contention de verrouillage, mais nous obtenons l'erreur:

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

pour toutes les MISES À JOUR. L'augmentation de innodb_lock_wait_timeout À ~ 100 ne change rien, ainsi que la définition de FOREIGN_KEY_CHECKS À 0.

La colonne gate ne contient jamais plus d'une chaîne [VALUE].

Si nous effectuons une seule MISE À JOUR à la main, aucune erreur de ce type ne se produit.

Faire un SHOW FULL PROCESSLIST N'affiche aucun verrou pendant les MISES À JOUR.

Que faisons-nous de mal? Existe-t-il un verrou au niveau de la table même si vous utilisez InnoDB? Ou les mises à jour verrouillent-elles plus de lignes que celles strictement sélectionnées par la clause WHERE?

5

Que faisons-nous de mal? Existe-t-il un verrou au niveau de la table même si vous utilisez InnoDB?

Votre problème est le manque d'un index approprié à utiliser. InnoDB fait verrouillage de la clé suivante , ce qui signifie qu'il ne verrouille que les lignes qu'il mettra à jour, mais aussi les écarts entre les deux en utilisant l'index de recherche. Parce qu'aucun index approprié ne peut être utilisé pour le filtre donné gate LIKE '%[1]%' techniquement, il ne fait pas de verrou de table, mais il configure un verrou sur chaque intervalle de ligne selon le plan de requête (verrouiller toutes les lignes).

J'ai recréé votre structure et SHOW ENGINE INNODB STATUS nous donne toutes les informations dont nous avons besoin:

mysql> create table lpr (gate_id int, gate varchar(10));
Query OK, 0 rows affected (0.21 sec)

mysql> insert into lpr values (1, 'wqer[1]sd');
Query OK, 1 row affected (0.11 sec)

mysql> insert into lpr values (2, 'wqer[2]sd');  
Query OK, 1 row affected (0.06 sec)

mysql> insert into lpr values (3, 'wqer[2]sd'); 
Query OK, 1 row affected (0.03 sec)

mysql> insert into lpr values (4, 'wqer[4]sd');  
Query OK, 1 row affected (0.05 sec)

mysql> insert into lpr values (5, 'wqer[5]sd');  
Query OK, 1 row affected (0.09 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE lpr SET gate_id=1 WHERE gate_id IS null AND gate LIKE '%[1]%';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

mysql> SHOW ENGINE INNODB STATUS\G
...
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 4126, ACTIVE 8 sec
2 lock struct(s), heap size 1136, 6 row lock(s)
MySQL thread id 8, OS thread handle 140628280551168, query id 21 localhost root starting
SHOW ENGINE INNODB STATUS

Sur une autre session:

mysql> UPDATE lpr SET gate_id=2 WHERE gate_id IS null AND gate LIKE '%[2]%'
    -> ;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

---TRANSACTION 4127, ACTIVE 8 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 9, OS thread handle 140628236515072, query id 28 localhost root updating
UPDATE lpr SET gate_id=2 WHERE gate_id IS null AND gate LIKE '%[2]%'
------- TRX HAS BEEN WAITING 8 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4 page no 4 n bits 72 index GEN_CLUST_INDEX of table `enwiki`.`lpr` trx id 4127 lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 6; hex 000000000200; asc       ;;
 1: len 6; hex 000000001011; asc       ;;
 2: len 7; hex 82000001060110; asc        ;;
 3: len 4; hex 80000001; asc     ;;
 4: len 9; hex 777165725b315d7364; asc wqer[1]sd;;

Vous avez plusieurs options:

  • Augmentez votre simultanéité en relâchant votre niveau d'isolement des transactions (mais des lectures fantômes peuvent se produire):

    mysql> SET SESSION transaction_isolation='READ-COMMITTED';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> UPDATE lpr SET gate_id=1 WHERE gate_id IS null AND gate LIKE '%[1]%';
    Query OK, 0 rows affected (0.00 sec)
    Rows matched: 0  Changed: 0  Warnings: 0
    

    Sur une autre session, maintenant la session réussit:

    mysql> SET SESSION transaction_isolation='READ-COMMITTED';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> BEGIN;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> UPDATE lpr SET gate_id=2 WHERE gate_id IS null AND gate LIKE '%[2]%'
    -> ;
    Query OK, 0 rows affected (0.00 sec)
    Rows matched: 0  Changed: 0  Warnings: 0
    
  • Améliorez votre conception et/ou vos requêtes afin de n'utiliser que des requêtes correctement indexées pour une concurrence améliorée.

En savoir plus sur la concurrence et le verrouillage des écarts: https://www.percona.com/blog/2012/03/27/innodbs-gap-locks/

7
jynus