当遇到对临界资源进行并发访问的时候,一方面要最大化的性能,另一方面还要保障数据的一致性,而锁机制可以很好的达成这样的效果。本文主要介绍InnoDB中的锁。
InnoDB中锁按照粒度来分可分为行级锁与表级锁。行级锁通过索引上的索引项实现,其中包含共享锁与排他锁:
- 共享锁(S):允许事务读一行数据。
- 排他锁(X):允许事务delete或update一行数据
S与S兼容,其它情况都不兼容。在使用索引进行检索时,InnoDB会加上S锁,这一点可能会引起大量的锁冲突,需要注意。
表级锁包含意向共享锁和意向排他锁:
意向共享锁(IS):事务想要获得一个表中某几行的共享锁。
意向排他锁(IX):事务想要获得一个表中某几行的排他锁。
意向锁不会阻塞除全表扫描外的请求。
可以通过三张表查询现在事务与锁的状态:
- INNODB_TRX:现在正在执行的事务的情况。
- INNODB_LOCKS:现在锁的情况。
- INNODB_LOCK_WAITS:现在被阻塞的事务与锁情况。
如果在锁不兼容的情况下需要读取行的数据,就需要使事务读取行的一个快照数据。原理是通过Undo段回滚数据。一行可能有多个版本的快照数据,此时就需要多版本并发控制技术(MVCC)。在read committed的书屋隔离条件下,InnoDB使用非锁定的一致性读,总是读取被锁定行的最新一份快照数据,而在Repeatable的事务隔离级别下,InnoDB则是读取事务开始前的快照数据版本。
在InnoDB中行锁的算法有3种,分别是:
- Record Lock:单个行记录上的锁;
- Gap Lock:间隙锁,锁定除记录本身的范围;
- Next-Key Lock:锁定一个范围并锁定记录本身,是默认的算法。
当然锁虽然可以提高并发,满足事务的隔离性需求,但锁也带来了问题。
- 丢失更新:事务的更新操作丢失。这是件非常严重的问题,尤其是在金融领域。想在数据库层面解决这个问题,就需要将操作串行化,按次序执行。
- 脏读:读到了未提交的数据,违反了隔离性。解决方案是将事务隔离级别提升到read uncommit。
- 不可重复读:一个事务多次读取同一数据期间数据被其他事务修改,导致两次读取的数据不一样。解决方案是将事务隔离级别提升到Read Repeatable。
- 死锁:A等待B,B等待A。解决方案是进行回滚操作,往往InnoDB会解决大多数的死锁。