大家好,我是热心的大肚皮,皮哥。
锁
InnoDB锁的内存结构
直接上图,我们看看InnoDB中锁的结构。
锁所在的事务信息: 任何锁都属于一个事务,这里记载对应的事务信息。
索引信息: 对于行级锁来说,需要记录加锁的记录属于哪个索引。
表/行锁信息: 记录的对应的信息。
type_mode: 32比特的数,包含lock_mode、lock_type、rec_lock_type这三部分。lock_mode(锁模式)占用低4比特。LOCK_IS(十进制的0):共享意向锁。
LOCK_IX(十进制的1):独占意向锁。 LOCK_S(十进制的2):共享锁。 LOCK_X(十进制的3):独占锁。 LOCK_AUTO_INC(十进制的4):AUTO_INC锁,轻量级锁。 lock_type(锁类型)占用第5-8位。
LOCK_TABLE(十进制的16):也就是当第五比特设置为1时,表示表级锁。
LOCK_REC(十进制的32):也就是当第六比特设置为1时,表示行级锁。
rec_lock_type代表行锁的具体类型,不过多讲述了。不过这里面有个关键的属性。
LOCK_WAIT(10进制的256):也就是第九比特设置为1时,表示is_waiting为true,即当前事务尚未获取到锁,等待中,反之is_waiting为false,即事务获取锁成功。
其他信息:不用关注。
比特位:映射到页中的记录。 InnoDB行锁的分类
Record Lock: 记录锁。只对记录本身加锁。Gap Lock: 锁住记录前的间隙,防止别的事物向该间隙插入新纪录。在repeatable read隔离级别下可以很大程度解决幻读,解决方案有两种:一种是MVCC,另一种就是使用这种锁。Next-Key Lock: Record Lock与Gap Lock的结合体,既保护记录本身,也防止别的事务插入记录。Insert Intention Lock: 某个事务获取这种锁后,不会阻止别的事务继续获取这个记录上任何类型的锁,这个可以忽略。隐式锁: 依靠记录的trx_id属性来保护不被别的事务改动记录。在InnoDB存储引擎中,锁都对应一个结构,为了节约内存,会把符合要求的锁放到同一个锁结构中:
在同一个事物中进行加锁操作; 被加锁的记录在同一个页面中; 加锁的类型都是一样的; 等待状态是一样的。 死锁
假设我们开启了两个事务T1和T2,如下。
select * from hero where id=1 for update;
select * from hero where id=3 for update;
select * from hero where id=3 for update;
select * from hero where id=1 for update;
通过上面的执行顺序可以发现,事务T1第5步执行时,在等待事务T2中的第4步,而事务T2中第6步在等待事务T1中的第3步,它们在互相等待,造成死锁,InnoDB中有个死锁检测机制,当检测到死锁时,会选择一个较小的事务进行回滚(指事务执行过程中增删改操作记录少的事务),而且会给客户端发送Deadlock found when trying to get lock 。发生了死锁,我们一般会采用SHOW ENGINE INNODB STATUS命令来查看最近一次的死锁信息。
具体应用场景
读操作
这种普通的读操作在READ COMMITTED 和 REPEATABLE READ(默认的)级别下,都是利用MVCC来控制,只是拍快照的时机不一样。 SELECT ... LOCK IN SHARE MODE; -- 加读锁
SELECT ... FOR UPDATE; --加写锁写操作
只有INSERT特殊,一般不加锁(但是别的事务有干扰的情况下,别的事务会来帮执行INSERT的这个事务的行记录加锁) 其他普遍都得加锁,比如UPDATE(一般是先删除后插入),DELETE
有索引:在READ COMMITTED :普通的写锁 在REPEATABLE READ除非是唯一索引且等值的条件:加普通的写锁 其他普遍情况加的都是next-key锁(普通写锁+间隙锁) 无索引:全表都加锁