大家好,我是热心的大肚皮,皮哥。
锁
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,即事务获取锁成功。
- lock_mode(锁模式)占用低4比特。
- 其他信息:不用关注。
- 比特位:映射到页中的记录。
InnoDB行锁的分类
- Record Lock:记录锁。只对记录本身加锁。
- Gap Lock:锁住记录前的间隙,防止别的事物向该间隙插入新纪录。在repeatable read隔离级别下可以很大程度解决幻读,解决方案有两种:一种是MVCC,另一种就是使用这种锁。
- Next-Key Lock:Record Lock与Gap Lock的结合体,既保护记录本身,也防止别的事务插入记录。
- Insert Intention Lock:某个事务获取这种锁后,不会阻止别的事务继续获取这个记录上任何类型的锁,这个可以忽略。
- 隐式锁:依靠记录的trx_id属性来保护不被别的事务改动记录。
在InnoDB存储引擎中,锁都对应一个结构,为了节约内存,会把符合要求的锁放到同一个锁结构中:
- 在同一个事物中进行加锁操作;
- 被加锁的记录在同一个页面中;
- 加锁的类型都是一样的;
- 等待状态是一样的。
死锁
假设我们开启了两个事务T1和T2,如下。
执行顺序 | T1 | T2 |
---|---|---|
1 | begin; | |
2 | begin; | |
3 | select * from hero where id=1 for update; | |
4 | select * from hero where id=3 for update; | |
5 | select * from hero where id=3 for update; | |
6 | 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 ....FROM ..
- 特殊的select会加锁
SELECT ... LOCK IN SHARE MODE; -- 加读锁
SELECT ... FOR UPDATE; --加写锁
写操作
- 只有INSERT特殊,一般不加锁(但是别的事务有干扰的情况下,别的事务会来帮执行INSERT的这个事务的行记录加锁)
- 其他普遍都得加锁,比如UPDATE(一般是先删除后插入),DELETE
- 有索引:
- 在READ COMMITTED :普通的写锁
- 在REPEATABLE READ
- 除非是唯一索引且等值的条件:加普通的写锁
- 其他普遍情况加的都是next-key锁(普通写锁 间隙锁)
- 无索引:全表都加锁
- 有索引: