LightDB 包含8种表级锁和四种行级锁,这些锁的关系和作用比较复杂,
下面尝试从锁的演变升级的角度来梳理这些关系。以帮助真正理解各个锁的作用
1. 最简单的锁
从作用来看,一个数据库只要实现一种锁,类似与多线程的 mutex
。 就足以达到保护的目的。 访问某张表的时候,必须要获取他的锁才可以, 这就可以了。 这种逻辑在多线程编程里面应该经常可以看到。
t1.lock(); // 锁表
update t1 set x = y; // 更新数据
t1.unlock(); //释放表锁
但是这样的锁设计,有个巨大的问题: 性能 。
被锁住的逻辑是无法并发执行的,数据库性能会极差。
后面增加那么多锁的目的就只有一个: 增加并发能力,提升性能 。
改善思路是什么呢?
就是区分不同的操作类型,让能并行的操作并行起来。下面逐步递进分析
2. 读写锁
所有操作可能都可以归结为读和写两种操作, 读操作是可以并行的,为了让读操作并行,
我们设计两种锁分别对应这两种操作: 读锁(Share
)和写锁Exclusive
。 然后设计锁的互斥关系。可以通过如下互斥表来表示:
- | - | - |
---|---|---|
Share | Exclusive | |
Share | x | |
Exclusive | x | x |
按照这个互斥关系实现数据库逻辑,在读操作前获取读锁,在写操作前获取写锁,如果大家都是查询的情况下,都可以拿到锁,这样数据库的查询性能就会得到大幅提升。
3. MVCC锁
有了读写锁,查询性能提升了,但改善的比较有限,因为写操作和所有操作都冲突,这会让读写锁设计的一切努力归零。
目前数据库通常使用MVCC技术来解决读写冲突问题。在这个技术下,读和写是不会有冲突的(具体查看其他资料,这里不展开)。
有了这个技术,我们增加一种增强版的读锁(ACCESS SHARE
),这个锁不会和写锁(Exclusive
)冲突。
普通的查询只要持有这个锁就可以了。 这样写操作就不会和读操作冲突了,查询性能立马搜搜提升。 增加后,锁冲突关系如下:
- | - | - | - |
---|---|---|---|
Access Share | Share | Exclusive | |
Access Share | |||
Share | x | ||
Exclusive | x | x |
但是现在这样,新的问题出现了,Access Share 锁可以无法无天了,没人管的住他了,换句话就是: 即使有MVCC这个牛逼技术的加持,也不是所有的操作都能和读操作并行,例如drop,truncate等。
所以再加个锁类型管管它, 这个锁就是: Access Exclusive
锁。基于这个定位,他是要和所有的锁都冲突的
- | - | - | - | - |
---|---|---|---|---|
Access Share | Share | Exclusive | Access Exclusive | |
Access Share | x | |||
Share | x | x | ||
Exclusive | x | x | x | |
Access Exclusive | x | x | x | x |
这样看起来比较完美了
4. 意向锁
MVCC的写写还是有冲突的,仍然有提升空间。
为了解决写写的问题,我们考虑上面的锁,都是表级别的,实际上,大部分写操作不需要锁全表,只需要锁要修改的行就可以了(行级锁)。
但是锁对应的行没问题,但行级锁也不是和所有操作都能并行。 所以加了行锁之后仍然需要加表级锁。我们也是增加两种表级锁来应对这种情况: 读意向锁(RowShare
), 写意向锁(RowExclusive
) 。
本质上讲,意向锁是用来协调表锁与行锁之间的关系。当进程想要锁定或修改某表上的某一行时,它要在这张表上加一个意向锁(表示自己要加行锁),然后在这一行上加上行级锁。
如果没有意向锁,A进程加上行锁对进程进行写入,B进程获取表级锁Access Exclusive进行同时进行操作,这是不行的。 有了意向锁,并让意向锁和Access Exclusive冲突,就可以规避这种情况。
基于这个理解,我们可以知道意向锁之间是不会有冲突的,只需要考虑意向锁和其他锁的冲突关系(本质上就是行级锁和表级锁的冲突关系)。
到现在为止,锁的关系差不多了,这里冲突关系直接截取PG官网的图。
5. 还有两个锁
前面已经讲了LightDB6个锁,还剩下两个:ShareUpdateExclusiveLock和ShareRowExclusiveLock,这两个锁从官网上看,是应用于vacuum()和触发器的,其重要特点就是自己和自己是互斥的。对应的操作本身不支持并发(不能同时对多表vacuum)。
这些操作要自己和自己互斥,所以找不到对应级别的锁来用用, 就有创建了两个。
总结
把数据库操作细致分类,识别出能并行的操作,赋予不同的锁类型,根据对应的操作能否并行来设计锁的互斥关系,便可以让能并行的操作并行起来,从而提升性能。