Mysql锁机制和优化

95 阅读3分钟

一、锁分类

  • 性能上分乐观锁和悲观锁,乐观锁适合读较多的场景,悲观锁适合写较多的场景
  • 粒度上分表锁、页锁(只支持BDB存储引擎)和行锁
  • 操作上分读锁、写锁和意向锁

读锁(共享锁)

针对同一份数据,多个读操作可以同时进行而不会互相影响(lock in share mode)。

写锁(排它锁)

当前写操作没有完成前,它会阻断其他写锁和读锁(for update)。

意向锁(I锁)

针对表锁,主要是为了提高加表锁的效率,是mysql自己加的。当有事务给表的数据行加了共享锁或排它锁,同时会对表设一个标识,代表已经有行锁了。其他事务要想加表锁时,就不必逐行判断有没有行锁可能跟表锁冲突了,直接读这个标识就可以确定自己该不该加表锁。特别是表中的记录很多时,逐行判断加表锁的方式效率很低。而这个标识就是意向锁。

意向锁类型

  1. 意向共享锁,IS锁,对整个表加共享锁前,需要先获取到意向共享锁
  2. 意向排它锁,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)

二、锁等待分析

image.png

image.png 在大多数情况mysql会自动检测死锁并回滚对应事务

三、锁优化实践

  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
  • 合理设计索引,尽量缩小锁的范围
  • 尽可能减少检索条件范围,,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
  • 尽可能用低的事务隔离级别

四、总结

  1. Innodb在执行select(非串行化隔离级别)不会加锁,但是update/insert/delete会加行锁。
  2. 读锁会阻塞写但不会阻塞读,而写锁则会都阻塞。