Esta es una gran pregunta. InnoDB es un motor de bloqueo de nivel de fila, pero tiene que establecer bloqueos adicionales para garantizar la seguridad con el registro binario (utilizado para la replicación; recuperación de un punto en el tiempo). Para empezar a explicarlo, considere el siguiente ejemplo (ingenuo):
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;
Debido a que las declaraciones solo se escriben en el registro binario una vez confirmadas, en la sesión esclava #2 se aplicaría primero y produciría un resultado diferente, lo que provocaría daños en los datos. .
Entonces, lo que hace InnoDB es establecer bloqueos adicionales. Si is_deleted
está indexado, luego, antes de que la sesión 1 se confirme, nadie más podrá modificar o insertar en el rango de registros donde is_deleted=1
. Si no hay índices en is_deleted
, entonces InnoDB necesita bloquear cada fila en toda la tabla para asegurarse de que la reproducción esté en el mismo orden. Puedes pensar en esto como cerrar la brecha , que es un concepto diferente a comprender del bloqueo de nivel de fila directamente .
En su caso con ese ORDER BY position ASC
, InnoDB debe asegurarse de que no se puedan modificar filas nuevas entre el valor de clave más bajo y un valor posible más bajo "especial". Si hiciste algo como ORDER BY position DESC
.. bueno, entonces nadie podría insertar en este rango.
Así que aquí viene la solución:
-
El registro binario basado en declaraciones apesta. Espero con ansias un futuro en el que todos cambiemos a row registro binario basado (disponible desde MySQL 5.1, pero no activado de forma predeterminada).
-
Con la replicación basada en filas, si cambia el nivel de aislamiento a lectura confirmada, solo se debe bloquear la fila que coincide.
-
Si quieres ser masoquista, también puedes activar innodb_locks_unsafe_for_binlog con replicación basada en sentencias.
Actualización del 22 de abril :Para copiar y pegar mi versión mejorada de su caso de prueba (no estaba buscando 'en el espacio'):
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