鹅厂T12详解MySQL加锁机制

锁机制

InnoDB使用不同的锁策略(Locking Strategy)以及MVCC机制来实现不同的隔离级别。

  • 读未提交(Read Uncommitted)

    事务在读数据的时候不对数据加锁。

    事务在修改数据的时候只对数据增加行级共享锁。

  • 读已提交(Read Committed)

    普通读是快照读,这是一种不加锁的一致性读,底层使用MVCC实现。

    加锁的select, update, delete等语句,除了在外键约束检查(foreign-key constraint checking)以及重复键检查(duplicate-key checking)时会封锁区间,其他时刻都只使用记录锁。

  • 可重复读(Repeatable Read)

    普通的select使用快照读。

    加锁的select(select…in share mode/select…for update),update,delete等语句,它们的锁,依赖于它们是否在唯一索引上使用了唯一的查询条件,或者范围查询条件:

    1)在唯一索引上使用唯一的查询条件,会使用记录锁,而不会封锁记录之间的间隔,即不会使用间隙锁与临键锁。

    2)范围查询条件为非唯一值时,会使用临键锁,锁住索引记录之间的范围,避免范围间插入记录,以避免产生幻读,以及避免不可重复读。

  • 串行化(Serializable) 这种事务隔离级别下,所有select语句会被隐式的转化为select…in share mode。

    这会导致,如果有未提交的事务正在修改某些行,所有读取这些行的操作都会被阻塞。

锁分类

  • 从性能上分为乐观锁(用版本对比来实现)和悲观锁

  • 从对数据库操作的类型分,分为读锁和写锁(都属于悲观锁)

    读锁(共享锁,S锁(Shared)):针对同一份数据,多个读操作可以同时进行而不会互相影响

    写锁(排它锁,X锁(eXclusive)):当前写操作没有完成前,它会阻断其他写锁和读锁

  • 从对数据操作的粒度分,分为表锁和行锁

    表锁:每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;一般用在整表数据迁移的场景。

    无索引行锁会升级为表锁(RR级别会升级为表锁,RC级别不会升级为表锁)。

行锁

每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。

间隙锁,锁的就是两个值之间的空隙。间隙锁是在可重复读隔离级别下才会生效。

临键锁(Next-key Locks)是行锁与间隙锁的组合。

保姆级教程!2 万字 + 30 张图搞懂 MySQL 是怎么加行级锁的?

锁优化建议

  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁

  • 合理设计索引,尽量缩小锁的范围

  • 尽可能减少检索条件范围,避免间隙锁

  • 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行

  • 尽可能低级别事务隔离

参考