Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Wie viele Zeilen werden durch SELECT ... ORDER BY xxx LIMIT 1 FOR UPDATE gesperrt?

Das ist eine großartige Frage. InnoDB ist eine Sperr-Engine auf Zeilenebene, muss aber zusätzliche Sperren setzen, um die Sicherheit mit dem Binärlog zu gewährleisten (das für die Replikation verwendet wird; Point-in-Time-Wiederherstellung). Um es zu erklären, betrachten Sie das folgende (naive) Beispiel:

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;

Da Anweisungen erst nach dem Festschreiben in das Binärlog geschrieben werden, würde auf der Slave-Sitzung Nr. 2 zuerst gelten und ein anderes Ergebnis erzeugen, was zu Datenbeschädigung führt .

InnoDB setzt also zusätzliche Sperren. Wenn is_deleted indiziert ist, kann niemand sonst ändern oder in den Bereich einfügen, bevor Sitzung1 festschreibt von Datensätzen, bei denen is_deleted=1 ist . Wenn es keine Indizes auf is_deleted gibt , dann muss InnoDB jede Zeile in der gesamten Tabelle sperren, um sicherzustellen, dass die Wiedergabe in derselben Reihenfolge erfolgt. Sie können sich das als Schließen der Lücke vorstellen , das ist ein anderes Konzept, das direkt von der Sperrung auf Zeilenebene zu verstehen ist .

In Ihrem Fall mit diesem ORDER BY position ASC , muss InnoDB sicherstellen, dass keine neuen Zeilen zwischen dem niedrigsten Schlüsselwert und einem "speziellen" niedrigstmöglichen Wert geändert werden können. Wenn Sie etwas wie ORDER BY position DESC getan haben .. naja, dann könnte niemand in diesen Bereich einfügen.

Hier kommt also die Lösung:

  • Anweisungsbasierte binäre Protokollierung ist scheiße. Ich freue mich wirklich auf eine Zukunft, in der wir alle zu row wechseln basierte binäre Protokollierung (verfügbar ab MySQL 5.1, aber standardmäßig nicht aktiviert).

  • Wenn Sie bei der zeilenbasierten Replikation die Isolationsstufe auf schreibgeschützt ändern, muss nur die eine übereinstimmende Zeile gesperrt werden.

  • Wenn Sie ein Masochist sein wollen, können Sie auch innodb_locks_unsafe_for_binlog mit anweisungsbasierter Replikation.

Aktualisierung 22. April :Um meine verbesserte Version Ihres Testfalls zu kopieren und einzufügen (es wurde nicht 'in der Lücke' gesucht):

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