Mysql
 sql >> Base de données >  >> RDS >> Mysql

Combien de lignes seront verrouillées par SELECT ... ORDER BY xxx LIMIT 1 FOR UPDATE ?

c'est une excellente question. InnoDB est un moteur de verrouillage au niveau des lignes, mais il doit définir des verrous supplémentaires pour assurer la sécurité avec le journal binaire (utilisé pour la réplication ; récupération à un moment donné). Pour commencer à l'expliquer, considérons l'exemple (naïf) suivant :

session1> START TRANSACTION;
session1> DELETE FROM users WHERE is_deleted = 1; # 1 row matches (user_id 10), deleted.
session2> START TRANSACTION;
session2> UPDATE users SET is_deleted = 1 WHERE user_id = 5; # 1 row matches.
session2> COMMIT;
session1> COMMIT;

Étant donné que les instructions ne sont écrites dans le journal binaire qu'une fois validées, sur la session esclave # 2 s'appliquerait en premier et produirait un résultat différent, conduisant à la corruption des données .

Donc, ce que fait InnoDB, c'est définir des verrous supplémentaires. Si is_deleted est indexé, alors avant que la session1 ne soit validée, personne d'autre ne pourra modifier ou insérer dans la plage d'enregistrements où is_deleted=1 . S'il n'y a pas d'index sur is_deleted , alors InnoDB doit verrouiller chaque ligne de la table entière pour s'assurer que la relecture est dans le même ordre. Vous pouvez considérer cela comme verrouiller l'écart , qui est un concept différent à saisir directement à partir du verrouillage au niveau de la ligne .

Dans votre cas avec cette ORDER BY position ASC , InnoDB doit s'assurer qu'aucune nouvelle ligne ne peut être modifiée entre la valeur de clé la plus basse et une valeur "spéciale" la plus basse possible. Si vous avez fait quelque chose comme ORDER BY position DESC .. eh bien, personne ne pourrait s'insérer dans cette plage.

Voici donc la solution :

  • La journalisation binaire basée sur les instructions est nulle. J'attends avec impatience un avenir où nous passerons tous à row journalisation binaire (disponible à partir de MySQL 5.1, mais pas activé par défaut).

  • Avec la réplication basée sur les lignes, si vous modifiez le niveau d'isolement en lecture validée, seule la ligne qui correspond doit être verrouillée.

  • Si vous voulez être masochiste, vous pouvez également activer innodb_locks_unsafe_for_binlog avec réplication basée sur les instructions.

Mise à jour du 22 avril :Pour copier + coller ma version améliorée de votre cas de test (il ne cherchait pas 'dans l'espace'):

session1> CREATE TABLE test (id int not null primary key auto_increment, data1 int, data2 int, INDEX(data1)) engine=innodb;
Query OK, 0 rows affected (0.00 sec)

session1> INSERT INTO test VALUES (NULL, 1, 2), (NULL, 2, 1), (5, 2, 2), (6, 3, 3), (3, 3, 4), (4, 4, 3);
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

session1> start transaction;
Query OK, 0 rows affected (0.00 sec)

session1> SELECT id FROM test ORDER BY data1 LIMIT 1 FOR UPDATE;
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

session2> INSERT INTO test values (NULL, 0, 99); # blocks - 0 is in the gap between the lowest value found (1) and the "special" lowest value.

# At the same time, from information_schema:

localhost information_schema> select * from innodb_locks\G
*************************** 1. row ***************************
    lock_id: 151A1C:1735:4:2
lock_trx_id: 151A1C
  lock_mode: X,GAP
  lock_type: RECORD
 lock_table: `so5694658`.`test`
 lock_index: `data1`
 lock_space: 1735
  lock_page: 4
   lock_rec: 2
  lock_data: 1, 1
*************************** 2. row ***************************
    lock_id: 151A1A:1735:4:2
lock_trx_id: 151A1A
  lock_mode: X
  lock_type: RECORD
 lock_table: `so5694658`.`test`
 lock_index: `data1`
 lock_space: 1735
  lock_page: 4
   lock_rec: 2
  lock_data: 1, 1
2 rows in set (0.00 sec)

# Another example:
select * from test where id < 1 for update; # blocks