一、锁分类
- 性能上分乐观锁和悲观锁,乐观锁适合读较多的场景,悲观锁适合写较多的场景
- 粒度上分表锁、页锁(只支持BDB存储引擎)和行锁
- 操作上分读锁、写锁和意向锁
读锁(共享锁)
针对同一份数据,多个读操作可以同时进行而不会互相影响(lock in share mode)。
写锁(排它锁)
当前写操作没有完成前,它会阻断其他写锁和读锁(for update)。
意向锁(I锁)
针对表锁,主要是为了提高加表锁的效率,是mysql自己加的。当有事务给表的数据行加了共享锁或排它锁,同时会对表设一个标识,代表已经有行锁了。其他事务要想加表锁时,就不必逐行判断有没有行锁可能跟表锁冲突了,直接读这个标识就可以确定自己该不该加表锁。特别是表中的记录很多时,逐行判断加表锁的方式效率很低。而这个标识就是意向锁。
意向锁类型
- 意向共享锁,IS锁,对整个表加共享锁前,需要先获取到意向共享锁
- 意向排它锁,IX锁,对整个表加排它锁前,需要先获取到意向排它锁
表锁
每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。一般用在整表迁移的场景。
行锁
每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。
注意:Innodb的行锁实际上是针对索引加的锁,不是针对整个行记录加锁。并且该索引不能失效,否则会从行锁升级为间隙锁或表锁。(RC级别不存在该问题,在RR级别下有主键/索引且查询不到结果会产生间隙锁,无主键/索引就会产生表级锁)(间隙锁可以共享,使用不当会产生死锁的严重后果!!!)
间隙锁(Gap Lock)
间隙锁就是锁两个值之间的空隙,只有在可重复读隔离级别下生效,且只针对有索引的。 只要在间隙范围内锁了一条不存在的记录会锁住整个间隙范围,不锁边界记录,这样就防止其他session在这个间隙范围内插入数据,就解决了幻读问题。(select * from table where id = 100 for update)
临键锁(Next-key Locks)
是行锁与间隙锁的组合(select * from table where id > 30 and id <= 100 for update)
二、锁等待分析
在大多数情况mysql会自动检测死锁并回滚对应事务
三、锁优化实践
- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
- 合理设计索引,尽量缩小锁的范围
- 尽可能减少检索条件范围,,避免间隙锁
- 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
- 尽可能用低的事务隔离级别
四、总结
- Innodb在执行select(非串行化隔离级别)不会加锁,但是update/insert/delete会加行锁。
- 读锁会阻塞写但不会阻塞读,而写锁则会都阻塞。