目录
前言
1. 锁的分类
1.1 实现方式
1.2 锁的粒度
2. 查询操作加锁方式
2.1 一致性非锁定读
2.2 一致性锁定读
3. 锁的算法
4. 锁的升级
5. 死锁
6.总结
前言
锁机制的目的是最大程度提高数据库的并发访问,另一方面确保可以以一致的方式读取和修改数据。了解mysql的锁的相关知识,可以在处理涉及数据库的并发操作问题时,有正确的判断和处理方法。打算梳理这些东西的起因是项目组里有一个同事,张口数据被加锁,闭口死锁了,这个问题没法处理,总是推脱,然后自己也说不清原因,可见他对锁的认识并不深入,但我又不好当面直接说驳了他的面子,仅以此文梳理并加以分享,希望有小伙伴在面对类似情况时,可以侃侃而谈,不至于模模糊糊说不清。
1. 锁的分类
1.1 实现方式
锁机制是用于管理对共享资源的并发访问。InnoDB支持行级锁,按照锁的实现方式,InnoDB实现了两种标准的行级锁:
共享锁(S Lock):允许事务读取一行数据;
排他锁(X Lock):允许事务删除或更新一行数据;
举个例子:如果一个事务t1已经获取了行n的共享锁,那么另一个事务t2可以立即获得行n的共享锁,因为读取操作没有改变行n的数据,所以这种情况叫作锁的兼容()。如果另外有一个事务t3想获得行n的排他锁,那么这时需要等待事务t1、t2释放行n上的共享锁才行,t3等待的过程处于阻塞状态,这种情况就是锁不兼容。
通过例子可以得出以下结论:
- 排他锁与共享锁不兼容;
- 排他锁与排他锁不兼容;
- 共享锁与共享锁是兼容的;
- 共享锁与排他锁不兼容;
来一个表格更直观一些,另外需要注意的是共享锁与排他锁都是行级锁,兼容是指结同一行数据而言的。
排他锁 | 共享锁 | |
排他锁 | 不兼容 | 不兼容 |
共享锁 | 不兼容 | 兼容 |
1.2 锁的粒度
InnoDB存储引擎还支持多粒度的锁定,即允许事务在行级上的锁和表级上的锁同时存在。这种在不同粒度上的锁定称为意向锁,可以将锁定的对象分为多个层次,在更细的粒度上进行加锁的操作,所以意向锁又分为两种实现方式:
意向共享锁(IS Lock)事务想要获取表中某几行数据的排他锁;
意向排他锁(IX Lock)事务想要获取表中某几行数据的共享锁;
举个例子:如果需要对页上的行记录n上加排他锁,那么需要分别对其所在的数据库、表、页上意向锁,最后对行记录n上排他锁。假如在给行记录n上排他锁前,已经有事务给行n所在的表上了共享锁,那么由排他锁与共享锁的不兼容,就需要等待已有的事务结束,共享锁释放后,才能继续。
需要特别注意的是:共享锁和排他锁锁定的是一行数据,意向共享锁和意向排他锁锁定的是几行,因此意向锁共享锁和意向排他锁不是锁定的相同几行数据,相互之间是锁兼容的。
2. 查询操作加锁方式
InnoDB查询操作有两种读取方式,本质是两种加锁方式:一致性非锁定读和一致性锁定读。
2.1 一致性非锁定读
InnoDB默认事务隔离级别为可重复读,InnoDB默认读取方式为一致性非锁定读。举个例子说明一下什么是一致性非锁定读?如果读取数据行n时,行数据n正在执行delete或update操作而被锁定,那么读取的操作不会因此去等待行n上的锁释放,而是直接去读取行n的快照数据,所谓以快照数据就行n锁定之前的版本数据。一致性非锁定锁极大提高了数据库的并发性,所以才是InnoDB默认的读取方式。
需要特别注意的是,不同的事务隔离级别下读取的数据有区别,这主要是因为不同事务隔离级别下对快照数据的定义不同。因为一条行记录可能有不止一个快照数据,因不同版本并发控制产生的技术,就是传说中的多版本控制(MVCC),但这并不是这里要讲述的重点。
- 在事务隔离级别为read commited(读已提交),读取被锁定行的最新一份快照数据。
- 在事务隔离级别为repeatable read,可重复,读取事务开始时的行数据版本。
这好像也没看出来具体到底有什么区别?别急,举个例子就明白了:
- 时间列表示会话操作的时间节点顺序,
- 在时间节点1会话1开启事务,在时间节点2执行了查询操作;
- 时间节点3时,会话2开启事务,时间节点4执行了一个与时间节点2相同的条件的更新操作,这时id=1的行其实加了一个排他锁;
- 时间节点5会话1再执行id=1的查询操作,如果事务隔离级别是读已提交,那么读取被锁定行的最新一份快照数据id是1,因现在会话2的更新操作的事务还未提交,读取已提交的肯定还是id=1; 如果事务隔离级别是可重复读,读取事务开始时的行数据版本也是id=1;所以不管不管事务的隔离级别是读已提交,还是可重复读,结果是一样的。
- 时间节点6会话2提交了事务,加在id=1的行上的排他锁释放,在时间节点7再执行查询操作,这时读已提交和可重复读两种事务隔离级别下的结果是不同的。如果是读已提交,由于会话2的更新操作事务已提交,读取最新的一份快照数据id=3了。如果是可重复读,读取事务开始时行数据的版本,由于会话1事务未提交,事务开始时数据的版本是id=1。
时间 | 会话1 | 会话2 |
1 | begin | |
2 | select * from table1 where id=1; | |
3 | begin | |
4 | update table1 set id=3 where id=1; | |
5 | select * from table1 where id=1; | |
6 | commit; | |
7 | select * from table1 where id=1; | |
8 | commit; |
通过例子可以了解一个事实:一致性非锁定读在不同的事务隔离级别下读取的结果可能不同,也可能相同,关键在于时机。可以根据实际的场景,显性指定合适的事务的隔离级别。
2.2 一致性锁定读
InnoDB默认事务隔离级别可重复读时,select查询使用一致性非锁定读,但是用户可以显性对数据库查询操作指定为一致性锁定读。
两种一致性锁定读的操作:
select ... for update;
select ... lock in share mode;
select ... for update对读取记录加排他锁,其他事务不能对加排他锁的行上加任何锁,因为锁不兼容;select ... lock in share mode对读取行记录加共享锁,其他事务可以向被锁定的行加共享锁,但不能是排他锁,因为锁不兼容,其事务也会阻塞。
3. 锁的算法
---未完待续,敬请期待
4. 锁的升级
---未完待续,敬请期待
5. 死锁
---未完待续,敬请期待
6.总结
处理数据并发问题时,绝不能想当然,搞清楚数据库是什么版本存储引擎?事务隔离级别是什么?读取操作加锁方式是什么?这些信息搞清楚,才是分析并发问题的第一步。