解决这个的办法是加锁。但是有个问题是没有个具体的数据,或者说实体可以用来加锁。如果是两个事务更新同一行数据,分别执行 update,那是可以在这行数据上加 X 锁。这里 T1 执行的是 select count,如果能够在某个实体上加锁,这种锁也必须要满足:加了这个锁以后,不会有新的数据满足 name = ‘Physics’,同时现有的数据也不会被变更并满足 name = ‘Physics’ 。要锁住的是这个关系。
------------ TRANSACTIONS ------------ Trx id counter 99177 Purge done for trx's n:o < 99174 undo n:o < 0 state: running but idle History list length 149 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 281479545796400, not started 0 lock struct(s), heap size 1136, 0 row lock(s) ---TRANSACTION 99172, ACTIVE 1520 sec inserting (99172事务信息开始) mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 3 MySQL thread id 22, OS thread handle 123145382735872, query id 258 localhost 127.0.0.1 root update insert into t values(6) Trx read view will not see trx with id >= 99172, sees < 99171 ------- TRX HAS BEEN WAITING 25 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 513 page no 4 n bits 80 index idx_a of table `db_test`.`t` trx id 99172 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000008; asc ;; 1: len 6; hex 00000000020b; asc ;;
------------------ TABLE LOCK table `db_test`.`t` trx id 99172 lock mode IX RECORD LOCKS space id 513 page no 4 n bits 80 index idx_a of table `db_test`.`t` trx id 99172 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000008; asc ;; 1: len 6; hex 00000000020b; asc ;;
---TRANSACTION 99171, ACTIVE 1541 sec (99171事务信息开始) 4 lock struct(s), heap size 1136, 3 row lock(s) MySQL thread id 3, OS thread handle 123145382178816, query id 233 localhost root TABLE LOCK table `db_test`.`t` trx id 99171 lock mode IX RECORD LOCKS space id 513 page no 4 n bits 72 index idx_a of table `db_test`.`t` trx id 99171 lock_mode X Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000008; asc ;; 1: len 6; hex 00000000020b; asc ;;
RECORD LOCKS space id 513 page no 3 n bits 72 index GEN_CLUST_INDEX of table `db_test`.`t` trx id 99171 lock_mode X locks rec but not gap Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 0: len 6; hex 00000000020b; asc ;; 1: len 6; hex 00000001835e; asc ^;; 2: len 7; hex be00000225013d; asc % =;; 3: len 4; hex 80000008; asc ;;
RECORD LOCKS space id 513 page no 4 n bits 72 index idx_a of table `db_test`.`t` trx id 99171 lock_mode X locks gap before rec Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 8000000b; asc ;; 1: len 6; hex 00000000020c; asc ;;
有些疑问:
单从 innodb_locks 和 innodb_lock_waits 的查询结果看,innodb_lock_waits 查询到的是当前被阻塞的事务,99172 是 T1 的 insert。他请求的锁是 X,GAP。被 T1 事务的 X 锁给阻塞。有疑问啊 … select for update 加的是 X 的写锁,锁的状态查询结果看起来是没有加间隙锁(其实应该是加了的),那么后面的 insert 语句是要加间隙锁并且被阻塞,那么看起来是申请排他间隙锁并被一个排他写锁给阻塞了。排他写锁会阻塞排他间隙锁吗?select for update 难道不应该是加排他写锁和排他共享锁吗?
从 show engine innodb status 的 99171 事务的加锁情况看:
1 2 3 4 5 6 7 8 9 10 11
1: RECORD LOCKS space id 513 page no 4 n bits 72 index idx_a of table `db_test`.`t` trx id 99171 lock_mode X Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
2: RECORD LOCKS space id 513 page no 3 n bits 72 index GEN_CLUST_INDEX of table `db_test`.`t` trx id 99171 lock_mode X locks rec but not gap Record lock, heap no 5 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
3: RECORD LOCKS space id 513 page no 4 n bits 72 index idx_a of table `db_test`.`t` trx id 99171 lock_mode X locks gap before rec Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
99171 事务执行的 select for update 其实加的是 next-key lock,他包含了 record lock 和 gap lock。上面的信息 2 的锁是 lock_mode X locks rec but not gap,是 record lock。上面的信息 3 是 lock_mode X locks gap before rec ,是 gap lock。所以在 RR 隔离级别下,MySQL 默认加的 X 锁不仅仅是一个 X record lock,还会加一个 gap lock,和一起就是 next-key lock。加一个 gap lock 是为了防止幻象读。
光 select 似乎是的,但是执行 delete from … 或者 insert (写入一个唯一列或者主键)又会发生了,因为这两个其实执行了当前读。但是网上的文章都说 MySQL 的 RR 能避免幻读 TODO。
解答 3:
引用:
Phantom reads do not occur if you’re simply doing a SELECT. They only occur if you do UPDATE or DELETE or SELECT FOR UPDATE. InnoDB provides REPEATABLE READ for read-only SELECT, but it behaves as if you use READ COMMITTED for all write queries, in spite of your chosen transaction isolation level (considering only the two most common isolation levels, REPEATABLE READ and READ COMMITTED)