postgres对表的访问一共包含有八级锁,如下图所示的
其中每个锁代表了一系列的对表进行的操作行为,比如,SELECT操作需要获取AccessShareLock,INSERT操作需要获取RowExclusiveLock,AlterTable需要获取AccessExclusiveLock。不同的操作类型对应着不同的锁的类型,然后再通过锁之间的冲突矩阵来展示出不同行为之间的冲突,不同锁模式之间的冲突矩阵如下:
横轴和纵轴均为锁的八个级别,从上到下,锁的级别逐渐提高。分别为1-8级锁。锁的级别越高,和其他类型的锁之间产生冲突就越多。第8级锁AccessExclusiveLock,和所有其他类型的锁都有冲突,也就是如果一个事务拿到了8级锁,进行DDL语句或者vacuum full,其他所有类型对该表的操作都会被阻塞住,等待该事务结束释放锁后,才能继续进行。
Insert、update、delete操作都是3级锁,RowExclusiveLock,Select是1级锁,AccessShareLock,它们之间是没有冲突的。这是因为MVCC的机制带来的好处,每个事务通过多版本来进行隔离,Select操作不会看到其他事务正在insert的数据,所以解决传统inplace原位更新带来的读写冲突的问题,大幅提高了读写并发的性能。
除了表级锁,还有行级锁,多个事务都拿到了不冲突的表锁,对具体的行进行操作时,可能会对出现不同事务对同一行进行操作的情况,这就需要行级锁来进行多个事务之间的并发控制。
行级锁用于控制对单行记录的访问,主要有以下几种:
- FOR UPDATE:将记录锁定,以防其他事务更新或删除该记录。
- FOR NO KEY UPDATE:对记录本身加锁,允许并发操作,但防止关键字段的更改。
- FOR SHARE:允许并发读取该记录,但防止其他事务对记录进行 FOR UPDATE 操作。
- FOR KEY SHARE:比 FOR SHARE 锁更宽松,允许更多的并发性。
和表级锁一样,行级锁也有冲突矩阵,如下
行级锁也是分为四个级别,级别越高和其他模式锁之间的冲突就越大。For Update是最高级别,就是在直接给行加上排他锁,访问同一行的其他事务在进行访问的时候都只能等待该锁释放,并且通过事务的状态来决定下一步操作。
对于两个update操作作用于同一行来说,其中一个事务先拿到for update行锁,进行更新,另一个事务在操作该行时发现已经被加for update锁,就进入等待。当事务1结束后,如果事务1回滚,则事务2可以继续执行update操作,如果事务1提交,则事务2会进行回滚。这样就通过行锁保证了两个update操作作用于同一行,只有一个可以执行成功,保证了数据的正确性,以及事务隔离性。
postgres通过表锁和行锁在保证最大并发效率的情况下保证数据的正确性。同时使用MVCC机制来解开了读写冲突,提高了并发度,降低了冲突解决的复杂度。