sql >> Databasteknik >  >> RDS >> Mysql

Hur många rader kommer att låsas av SELECT ... BESTÄLL EFTER xxx LIMIT 1 FÖR UPPDATERING?

Det här är en bra fråga. InnoDB är en låsmotor på radnivå, men den måste ställa in ytterligare lås för att säkerställa säkerheten med den binära loggen (används för replikering; återställning av tidpunkten). För att börja förklara det, överväg följande (naiva) exempel:

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;

Eftersom uttalanden endast skrivs till den binära loggen när de har begåtts, skulle slavsession nr 2 gälla först, och skulle ge ett annat resultat, leda till datakorruption .

Så vad InnoDB gör är att sätta ytterligare lås. Om is_deleted indexeras, innan session1 commits kommer ingen annan att kunna ändra eller infoga i intervallet av poster där is_deleted=1 . Om det inte finns några index på is_deleted , då måste InnoDB låsa varje rad i hela tabellen för att se till att reprisen är i samma ordning. Du kan se detta som att låsa gapet , vilket är ett annat koncept att förstå från låsning på radnivå direkt .

I ditt fall med den ORDER BY position ASC , InnoDB måste se till att inga nya rader kan ändras mellan det lägsta nyckelvärdet och ett "speciellt" lägsta möjliga värde. Om du gjorde något som ORDER BY position DESC .. ja, då kunde ingen läggas in i det här intervallet.

Så här kommer lösningen:

  • Uttalandetsbaserad binär loggning suger. Jag ser verkligen fram emot en framtid där vi alla byter till rad baserad binär loggning (tillgänglig från MySQL 5.1, men inte på som standard).

  • Med radbaserad replikering, om du ändrar isoleringsnivån till läs-committed, behöver bara den rad som matchar låsas.

  • Om du vill vara masochist kan du även slå på innodb_locks_unsafe_for_binlog med satsbaserad replikering.

Uppdatering 22 april :För att kopiera + klistra in min förbättrade version av ditt testfall (det sökte inte 'i gapet'):

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


  1. Vad är transaction.commit() i Hibernate?

  2. MySQL 8 ignorerar heltalslängder

  3. Kör Oracle Client i 32-bitarsläge på en 64-bitars maskin

  4. ASCIISTR() Funktion i Oracle