什么是锁?
下面的介绍基于Innodb存储引擎。因为Mysql不同存储引擎对锁的实现方式不一样。
我们知道事物的基本特性:
- A-原子性(atomicity)
- C-一致性(consistency)
- I-隔离性(isolation)
- D-持久性(durability)
Mysql的锁实现了事物的隔离性。
那到底什么是锁呢?锁机制用于管理对共享资源的并发访问(包括但不限于行记录)。
比如对一条记录进行update或delete操作A,会对行记录添加独占锁X;其它的请求必须等待A释放X,然后获取对应的锁X,才能进行操作。
Innodb的逻辑存储结构
Innodb默认将数据逻辑的存放在一个表空间中,称为tablespace。表空间又由段-segment,区-extend,页-page组成。
大致的存储结构如下
锁特性
Innodb实现的行级锁有2种类型:
- 共享锁(S Lock),允许事物读一行数据
- 排他锁(X Lock),允许事物删除或更新一行数据。
锁的兼容性
- 锁兼容:如果事物T1获取了行R的共享锁,那么事物T2可以获得行R的共享锁,因为读取不会改变行R
- 锁不兼容:如果事物T1获取了行R的共享锁,事物T2必须等待T1释放,才能获得行R的排它锁X
X | S | |
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
对行级锁来说,S锁与X锁不兼容的。
补充表级锁
Innodb不仅支持行级锁,还支持表级上的锁。并允许事物在行级上的锁与表级上的锁同时存在,为此支持一种意向锁。
意向锁是表级锁,有以下分类:
- 意向共享锁。事物想要获得一张表中某几行的共享锁。
- 意向排他锁。事物想要获得一张表中某几行的排他锁。
可以大致如下理解:
一致性锁定读
在某些情况下SELECT读取行记录,可以显示的对查询SELECT行记录加锁。这就表示,即使是对于SELECT,也可以阻塞其它操作。
Innodb支持2种加锁语句
SELECT...FOR UPDATE;
SELECT...LOCK IN SHARE MODE;
SELECT...FOR UPDATE会对读取的行加X锁,会阻塞其它对行的操作,因此不能再对它加任何锁。
SELECT...LOCK IN SHARE MODE对读取的行加一个S锁,其它事物可以向被锁定的行加S锁,如果加X锁,会被阻塞。
要注意的一点为的是,上面2个语句都是必须在一个事物中,事物提交了,锁也会释放。
非一致性锁定读
上面说了通过语句手动加锁,会对读取的行加锁,导致其它操作,甚至另一个查询的SELECT等待。
这种无疑并发性是比较低的,那可以用什么办法提高读取的并发性呢?
Innodb通过行多版本-MVCC来实现非一致性锁定读!
如果采用MVCC的方式读取数据库的中行R,此时该行R正在执行DELETE或UPDATE操作(此时会加上X锁),这时读取操作不会去等待R上的锁释放;相反,它会读取R的一个快照数据。MVCC在READ COMMITED,REPEATABLE READ隔离级别下默认开启。看下图
上面展示了Innodb存储引擎一致性非锁定读。因为不需要等待行上的X锁释放。
快照数据的意思是:该行的之前版本的数据,通过undo段完成。undo段里面是undo log,用来在事物中回滚数据,保证事物的一致性。因此快照数据本身是没有额外的开销的,而读取快照是不用上锁,因为没有事物需要对历史数据进行修改。
这里需要说明一下,快照数据并不是无限增加。举个例子,对一条数据R做了delete,如果直接删除,会造成其它引用的它的事物访问出错,所以并不会直接物理删除,而是等到没有事物引用它的时候,才会真正用purge操作清理操作,并清理它的快照数据。
锁的算法
行锁有3种算法
- Record Lock: 单个行记录上的锁
- Gap Lock :间隙锁,锁定一个范围,但不包含记录本身
- Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并且包含记录本身
Record Lock会锁住索引记录,如果建表时没有设置添加索引,Innodb会去锁定隐式的主键。