1.PostgreSQL锁基本概念
那么PostgreSQL数据库的表锁和行锁又有哪些呢?下面简单为大家介绍一下:
- 表锁:
在PostgreSQL数据库中,表级锁主要有SHARE(共享锁)和EXCLUSIVE(排他锁)。其中SHARE锁表示读锁,表中加了SHARE锁后,表的内容就不能变化了。我们可以在PostgreSQL中的多个事务中加SHARE锁,但是只要其中任意一个事务不进行释放,那么就没有任何事务可以修改这张表。而EXCLUSIVE锁表示写锁,表中加了EXCLUSIVE锁后,其他事务均无法对该表进行读写操作。
由于数据库中也存在多版本查询的概念。因此,在多版本的功能下,PostgreSQL中除了有SHARE锁和EXCLUSIVE锁以外,又新增了两个锁,其中一个是ACCESS SHARE锁,另一个是ACCESS EXCLUSIVE锁。其中,表中上了ACCESS SHARE锁,表示即使有事务正在修改数据,其他事务也可以读取数据。而ACCESS SHARE锁则表示表中上了该锁,即使数据库中存在多版本概念,其他事务也不能访问该表中的数据。
另外,我们也知道,表级锁的上锁对象是数据库中的表。在数据库中表锁往往会导致数据库高并发处理能力大大下降。因此,为了保证PostgreSQL中的高并发能力,数据库中也支持影响更小的行级锁。而行级锁和表级锁在PostgreSQL中则会产生一定的冲突。故数据库中也引入了意向锁的概念,即ROW SHARE和ROW EXCLUSIVE。这两个锁我们也叫做共享意向锁和排他意向锁。意向锁之间是不会发生冲突的,这时由于意向锁只是准备去给表上锁,但是实际上表中并没有进行上锁。但是意向锁和其他真实锁之间的关系和与真实锁之间的关系是相同的,比如SHARE锁和SHARE锁是相互冲突的,那么ROW SHARE和SHARE锁之间也是冲突的。
由于意向锁之间不会互相冲突,那么数据库中就必须出现一种更为严格的锁,即SHARE UPDATE EXCLUSIVE锁和SHARE ROW EXCLUSIVE锁,这种锁在自己之间同样会相互冲突。但是SHARE ROW EXCLUSIVE锁在当前的PostgreSQL中没有任何命令会使用。
总结一下上述内容,即PostgreSQL中总共有8中表锁。分别有SHARE、EXCLUSIVE、ACCESS SHARE、ACCESS EXCLUSIVE、ROW SHARE、ROW EXCLUSIVE、ROW UPDATE EXCLUSIVE、SHARE ROW EXCLUSIVE。
那么在PostgreSQL中有哪些命令会造成这8中锁呢?下面我们可以分别列举一下:
SHARE锁:CREATE INDEX语句会在数据库中请求该锁;
EXCLUSIVE锁:PostgreSQL中没有任何语句会请求该锁,但在一些操作中,会在系统表或者表上产生该锁,下面也会进行模拟示范。
ACCESS SHARE锁:select语句都会在表上请求该锁。
ACCESS EXCLUSIVE锁:alter table、drop table、truncate、reindex、cluster、VACUUM FULL、LOCK TABLE命令会向postgreSQL请求该锁。
ROW SHARE锁:select ...... for update和select....... for share会请求该锁。
ROW EXCLUSIVE锁:UPDATE、DELETE、INSERT命令会在所修改的表上请求该锁。
SHARE UPDATE EXCLUSIVE锁:VACUUM、ANALYZE、CREATE INDEX CONCURRENTLY命令会请求该锁。
SHARE ROW EXCLUSIVE锁:目前,没有命令会请求该锁。
- 行锁:
行锁分为共享锁和排它锁两种。但是由于PostgreSQL中多版本的概念,实际上在做select操作时,不会在行上加锁。
2.锁查看方法
那么在了解了PostgreSQL中的锁基本概念后,如何在实际情况下查看锁的信息,帮助我们进行一些问题的排查呢?
PostgreSQL中,提供了一个系统视图可以供我们对PostgreSQL中的锁进行查看,该视图即为pg_locks。
首先我们查看一下pg_locks的定义:
其中,比较重要的几个字段有locktype、relation、page、tuple、virtualxid、transactionid、virtualtransaction、mode、granted。
Locktype,表示被锁定的对象类型;
Relation,表示锁定对象为表或者表的一部分时,会显示表的OID(通过::regclass转换可以转换为表名),否则为空;
Page,表示表中的页号,如果所对象不是表行或者表页,则为空;
Tuple,表示表中的页内的行号,如果锁定对象不是表行,则为空;
这里需要我们注意的一个点是,page和tuple表示了一张表中的页号和页号中的行号,那么这两个值就可以确定到一张表中的具体数据行,而这两个值得组合其实也是描述表中数据的一个字段ctid。
Vitualxid:代表虚拟事务ID,如果对象不是虚拟事务,则为空值,简称vxid。
Transactionid:代表事务ID,如果对象不是事务,则为空值,简称xid。
Virtualtransaction:持有或等待锁的虚拟事务ID。
Mode:表示锁的名称,即上一节中介绍的8个表锁类型。
Granted:表示对象是否持有锁,如果值为true,则表示当前对象持有该锁;如果值为false,则表示当前对象正在等待该锁。
这里还需要强调一个概念,就是事务和虚拟事务有什么区别?其实,在PostgreSQL中开启一个事务块,我们可能会在事务块中进行读操作,也可以进行写操作。当在事务块中进行读操作时(没有对数据进行实质性的变更),则为了节省资源,PostgreSQL只会给该SQL分配一个虚拟事务ID。当事务块中进行写操作时(对数据进行实质性的变更),PostgreSQL则会给SQL分配一个事务ID。
下面,我们分别在PostgreSQL中进行一系列测试,查看在PostgreSQL中操作产生的锁和现象。
- 表锁测试,观察产生的相关表锁
第一步,在session 1中通过lock table操作进行锁定表,但是必须要注意的是,该操作必须在事务块中执行,否则会报错。
|
第二步,在session2中查询pg_locks,观察此时的锁信息。
|
可以看到,我们relation为t的锁,是我们进行lock table操作时获取的锁,此时锁的类型就为Access Exclusive’。
其实,在开启事务块后,未做任何操作时,postgresql只会分配一个vxid,并在该vxid上请求Exclusive锁。
当执行特殊lock table table_name,select txid_current的时候,首先会产生vxid,并请求Exclusive锁,然后会产生xid,并请求Exclusive锁,最后如果有锁表动作,则会产生AccessExclusive锁,如果没有其他动作,则不会有其他锁,这个时候我们可以在pg_locks中看到的锁至少会有两个,一个是vxid的Exclusive锁,另一个是xid的Exclusive锁(而在这个测试中有锁表动作,所以在t上有一个ACCESS EXCLUSIVE锁)。
此时,我们在session 3中,再次对t表进行锁表操作。
|
然后在session 2中,再次查看锁表情况。
|
可以看到,此时会话7139在t表也产生了一个AccessExclusive锁,但是granted字段中的值为false,说明此时该事务没有没有获取到这个排它锁,而是正在等待。
当我们在session 1中提交事务后,观察锁表信息。
这时,我们看到这个事务的排他锁granted的值已经变成true,此时说明pgsql持有了这个锁。
- 行锁测试,并并确定行锁位置
第一步,在session1中执行select......for update语句产生行锁
|
在session3中查询锁信息:
|
此时,可以看到加行锁会在t表上加一个表级意向锁,而且还在主键上加了AccessShare共享锁。但是在这个里面并没有显示行锁的消息。
在postgresql中,pg_locks并不能显示出每个行锁的信息,这是因为行锁信息不会记录到共享内存中。如果每个行锁都在内存中有一条记录的话,在对表做全表更新,表有多少行,就需要在内存中记录多少行所信息,那么内存有可能会吃不消。所以PostgreSQL被设计成在内存中不记录行锁信息。
当一个进程被另一个进程的行锁阻塞了,pg_locks有另一种信息来表示这种阻塞关系。第三步,在session 2上运行行锁语句。
|
此时session 2会被阻塞,在session3中查看所信息。
此时可以发现,granted为false的xid正在等待sharelock。那么可以说明,pid为7139的进程申请一个类型为transactionid的锁时,被阻塞了,这个transactionid对应的xid是715。而从上面一条数据中可以看到,xid为715的锁被进程6625持有了(granted)为true。那么则可以知道PID为6625的会话即为行锁源。
通过这个测试,可以发现行锁中的阻塞信息是通过’transactiond’类型的锁体现出来的。从原理上来说,行锁是会在数据行上加自己的xid的,另一个进程读到这一行时,如果发现有行锁,会把行上另一个事务的xid读取出来,然后申请在这个xid上加’Share’锁。而持有行锁的进程已经在此xid上加了’Exclusive’锁,所以后面更新这行的进程会被阻塞。
所以如果查询因行锁被阻塞的进程信息,只需查询视图pg_locks中类型为’transactiond’的锁信息就可以了。
- 在PostgreSQL中当出现行锁,我们还可以确定该锁发生在哪一行数据。
第一步,session 1中执行update语句,但不提交事务。
|
第二步,session 2中执行相同的update语句,此时,事务会被锁住。
|
此时,在session 3中查询锁的信息。
|
从查询结果中,我们可以看到xid为724的7139会话正在等待xid为723的6625会话中的行锁。另外,我们可以看到查询结果中的page和tuple的组合参数为(0,13)。由于这个组合表示了表的ctid字段。则我们通过这个值来查询save_point1的值,则可以确定该行锁产生在哪一行。
|
从查询结果可以知道,此时事务阻塞在save_point1的id=4,name=ase的值上了。这与我们进行update测试而未提交的行一致。
标签:EXCLUSIVE,事务,Postgresql,行锁,SHARE,pgsql,PostgreSQL From: https://blog.51cto.com/u_13482808/7012476