前言
数据库是一个允许多用户、多会话、多线程访问的系统。为了在并发访问中能保证数据的一致性和完整性,一般会使用事务来做控制,外加锁来作为辅助手段。所以今天整理了一下锁的相关知识,看看它在事务里是怎么配合使用的。
锁的分类
我们先来看下锁的分类及其作用有哪一些,以便能更好的分析运用场景。
乐观/悲观锁
乐观锁:在读取数据时会假设各个事务互不影响,它们会处理好属于自己的那部分数据。如果在更新数据时,发现有其他事务修改了属于自己的数据,则会回滚之前的一切操作。
悲观锁:采取了先获取锁再访问的保守策略,如果已经有其他事务获取了锁,则必须等待锁释放才能继续。
共享/排它锁
共享锁:又称读锁
,当前事务在读取时,允许其他事务并发读取,但不允许其他事务上排它锁,必须等自己释放了才能继续。
排它锁:又称写锁
,在写锁占有时,如果其他事务想上读写锁,则得排队等待。
表锁/行锁
表锁:在操作数据时,直接将整张表锁住,操作粒度很大,很容易让其他事务在等待,但不会产生死锁。
行锁:针对的是行记录的并发控制,锁粒度很细,能支持高并发,但是不排除会有死锁情况产生。在 mysql 里行锁依赖索引实现,如果没有索引存在,则会直接进行表锁!
其中,行锁
还细分出了下面几种锁:
记录锁:只锁住某一条记录。当对唯一索引(包括主键)进行精确查询时,会使用记录锁。
间隙锁:当使用范围查询时,会对符合条件的区间数据上锁。在涉及到普通索引(即不是唯一索引)的查询时,都会使用间隙锁。
Next-key 锁:临建锁,可以理解为 记录锁 + 间隙锁。当对唯一索引进行范围查找或对唯一索引进行查找但结果不存在时(可以理解为锁住不存在的记录),会使用临建锁。
上面的间隙锁、临建锁有效的防止了事务幻读情况产生,避免了在查找期间有数据新增或删除。
意向锁
意向锁是属于表锁的一种,它仅仅表示一种操作意向。当我们使用粒度比较小的行锁时,在检测是否有锁时,需要一行一行的检查,效率较低。
有了意向锁之后,则不需一行一行的排查,只需检测对应的意向锁即可。
意向锁有意向共享锁
,以及意向排它锁
。mysql 规定事务在上共享/排它锁时,必须能先获取到兼容的意向锁,否则必须等待锁的释放。
例如当前数据上已有意向共享锁,如果此时其他事务想上排它锁,则是不兼容的,必须等待释放。关于意向锁和共享/排它锁的兼容性表如下:
意向共享锁 | 意向排它锁 | |
---|---|---|
共享锁 | 兼容 | 互斥 |
排它锁 | 互斥 | 互斥 |
MVCC
MVCC 并不属于锁操作里的东西,但是它是事务隔离级别里会运用到的技术,并且由于它有类似快照数据
的功能,所以使用 MVCC 时能很大程度的减少锁的使用,减少了并发冲突。具体可以看看这篇文章:MVCC。
事务里锁的运用
在之前的文章事务解释里我们谈到了事务的隔离性,而事务的隔离性离不开锁的运用,所以现在就让我们来看看具体的使用吧。
可重复读
可重复读使用的是 MVCC 快照,所以在读取数据时大多数时候不需要使用锁。
但使用了 UPDATE
, DELETE
,或 SELECT with FOR UPDATE(排它锁) 或 FOR SHARE(共享锁)
,则会根据下面的情况来使用锁:
- 在唯一索引上精确查找某条记录时,使用记录锁
- 对于其他的搜索,InnoDB 将会锁定扫描到的索引范围,使用间隙锁或临建锁来防止幻读的产生
读提交
也是使用 MVCC 机制来读取数据,不过在使用 UPDATE
, DELETE
,或 SELECT with FOR UPDATE(排它锁) 或 FOR SHARE(共享锁)
时和上面的机制不一样,当存储引擎将筛选到的记录交给 mysql server 层后,会对不相干的数据进行解锁,所以不会涉及间隙锁或临建锁。
它们只会在做外键约束检查和重复键检查时使用到。
由于间隙锁的禁用,可能会出现幻读现象。
未提交读
在 mysql 的 innodb 存储引擎里做 SELECT
操作不会做任何锁动作,如果是 myisam 存储引擎,则会上共享锁。
如果使用UPDATE
, DELETE
,或 SELECT with FOR UPDATE(排它锁) 或 FOR SHARE(共享锁)
则和读提交一样的原则。
可序列化读
可序列化读在使用 select 时,一般会自动的转化为 SELECT ...
FOR SHARE(共享锁) ,以保证读写序列化。
总结
本文介绍了锁的分类以及锁在事务里的使用,大多数时候 mysql 的事务都是会自动进行锁动作,并不需要我们干涉。
所以当我们在使用 SELECT with FOR UPDATE(排它锁) 或 FOR SHARE(共享锁)
等显示上锁语句时要特别注意性能损耗,有可能产生间隙锁、临建锁导致不相干的记录也会被阻塞等待。