【赵渝强老师】PostgreSQL锁的类型

在并发环境下为了解决并发一致性问题保证事务的隔离性,PostgreSQL采用了锁的机制。当一个事务在进行操作时会对操作的数据进行加锁,从而限制另一个事务的操作。为保证效率和想,加锁的粒度不宜太大。加锁的意义就在于当多个会话同时访问数据库的同一数据时,为所有会话提供高效的数据访问,并同时维护严格的数据一致性,从而实现数据的多版本并发控制。

多版本并发控制的英文名称是Multiversion Concurrency Control,简称MVCC。它是指每个SQL语句看到的都只是当前事务开始的数据快照,而不用去关心底层数据的当前状态。这样可以保护语句不会看到可能由其他在相同数据行上执行更新的并发事务造成的不一致数据,为每一个数据库会话提供事务隔离。MVCC避免了传统的数据库系统的锁定方法,将通过锁争夺最小化的方法来达到多会话并发访问时的性能最大化目的。

PostgreSQL提供了多种类型的锁模式用于控制对表中数据的并发访问。在这些锁当中最主要的是表级锁与行级锁,此外还有页级锁、咨询锁等等。下面将重点介绍表级锁与行级锁。

视频讲解如下
【赵渝强老师】PostgreSQL锁的类型

一、 表级锁

表级锁通常会在执行各种命令执行时自动获取,或者通过在事务中使用lock语句显式获取,每种表级锁都有自己的冲突集合。两个事务在同一时刻不能在同一个表上持有属于相互冲突模式的锁,但可以持有不冲突的锁。PostgreSQL的表级锁总共有八种模式,并存储在PostgreSQL的共享内存中。下表详细说明了这八种PostgreSQL表级锁。

这八种表级锁彼此之间存在一定的冲突,下表说明了它们之间的冲突关系。

在PostgreSQL数据库中可以通过pg_locks系统视图查询表上已经获取到的表级锁信息,下面通过一个具体的示例来说明。

(1)在会话一中查看当前数据库中的表,并查看表testtable1的结构。

sql 复制代码
postgres=# \d
           List of relations
 Schema |    Name    | Type  |  Owner   
--------+------------+-------+----------
 public | testtable1 | table | postgres
(1 row)

postgres=# \d testtable1;
             Table "public.testtable1"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 tid    | integer |           | not null | 
 tname  | text    |           |          | 
Indexes:
    "testtable1_pkey" PRIMARY KEY, btree (tid)
Tablespace: "mydemotbs"

(2)查看表testtable1的oid。

sql 复制代码
postgres=# select oid,relname,relkind,relfilenode from pg_class
postgres-# where relname ='testtable1';
  oid  |  relname   | relkind | relfilenode 
-------+------------+---------+-------------
 16395 | testtable1 | r       |       16395
(1 row)

(3)在会话一中开启一个事务并执行一条update语句。

sql 复制代码
postgres=# start transaction;
postgres=*# update testtable1 set tname='Tom123' where tid=1;

# 这里执行的事务没有结束。

(4)在会话二中更改表testtable1的结构,如:添加一个新的列。

sql 复制代码
postgres=# alter table testtable1 add dno int;

# 此时会话二的操作将会被阻塞。

(5)在会话三中查看表testtable1上的锁信息。

sql 复制代码
postgres=# \x
postgres=# select * from pg_locks where relation = 16395;

# 这里的"\x"表示将输出结果进行竖式显示。
# 输出的结果如下:
-[ RECORD 1 ]------+--------------------
locktype           | relation
database           | 13580
relation           | 16395
page               | 
tuple              | 
virtualxid         | 
transactionid      | 
classid            | 
objid              | 
objsubid           | 
virtualtransaction | 4/401
pid                | 41381
mode               | AccessExclusiveLock
granted            | f
fastpath           | f
-[ RECORD 2 ]------+--------------------
locktype           | relation
database           | 13580
relation           | 16395
page               | 
tuple              | 
virtualxid         | 
transactionid      | 
classid            | 
objid              | 
objsubid           | 
virtualtransaction | 3/644
pid                | 40920
mode               | RowExclusiveLock
granted            | t
fastpath           | f

# 从输出的结果可以看出,此时表testtable1上有两把表级锁,
# 分别是:AccessExclusiveLock和RowExclusiveLock。
# 但此时并不能观察到会话之间的阻塞。

(6)执行语句检查锁的等待,即:会话之间的阻塞信息。

sql 复制代码
postgres=# with  
	t_wait as  
	(  
	 select 
	   a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,
	   a.classid,a.granted,a.objid,a.objsubid,a.pid,
	   a.virtualtransaction,a.virtualxid,a.transactionid,a.fastpath,
	   b.state,b.query,b.xact_start,b.query_start,b.usename,
	   b.datname,b.client_addr,b.client_port,b.application_name  
	 from pg_locks a,pg_stat_activity b where a.pid=b.pid and not a.granted  
	),  
	t_run as  
	(  
	 select 
	   a.mode,a.locktype,a.database,a.relation,a.page,a.tuple,
	   a.classid,a.granted,a.objid,a.objsubid,a.pid,
	   a.virtualtransaction,a.virtualxid,a.transactionid,a.fastpath,
	   b.state,b.query,b.xact_start,b.query_start,b.usename,
	   b.datname,b.client_addr,b.client_port,b.application_name  
	 from pg_locks a,pg_stat_activity b where a.pid=b.pid and a.granted  
	),  
	t_overlap as  
	(  
	 select r.* from t_wait w join t_run r on  
	 (  
	  r.locktype is not distinct from w.locktype and  
	  r.database is not distinct from w.database and  
	  r.relation is not distinct from w.relation and  
	  r.page is not distinct from w.page and  
	  r.tuple is not distinct from w.tuple and  
	  r.virtualxid is not distinct from w.virtualxid and  
	  r.transactionid is not distinct from w.transactionid and  
	  r.classid is not distinct from w.classid and  
	  r.objid is not distinct from w.objid and  
	  r.objsubid is not distinct from w.objsubid and  
	  r.pid <> w.pid  
	 )  
	),  
	t_unionall as  
	(  
	 select r.* from t_overlap r  
	 union all  
	 select w.* from t_wait w  
	)  
	select 
	locktype,datname,relation::regclass,page,tuple,
	virtualxid,transactionid::text,classid::regclass,
	objid,objsubid, 
	string_agg(
	'Pid: '
	||case when pid is null 
		   then 'NULL' else pid::text end||chr(10)||'Lock_Granted: '
	||case when granted is null 
		   then 'NULL' else granted::text end||' , Mode: '
	||case when mode is null 
		   then 'NULL' else mode::text end||' , FastPath: '
	||case when fastpath is null 
		   then 'NULL' else fastpath::text end||' , VirtualTransaction: '
	||case when virtualtransaction is null 
		   then 'NULL' else virtualtransaction::text end||' , Session_State: '
	||case when state is null 
		   then 'NULL' else state::text end||chr(10)||'Username: '
	||case when usename is null 
		   then 'NULL' else usename::text end||' , Database: '
	||case when datname is null 
		   then 'NULL' else datname::text end||' , Client_Addr: '
	||case when client_addr is null 
		   then 'NULL' else client_addr::text end||' , Client_Port: '
	||case when client_port is null 
		   then 'NULL' else client_port::text end||' , Application_Name: '
	||case when application_name is null 
		   then 'NULL' else application_name::text end
	||chr(10)||'Xact_Start: '
	||case when xact_start is null 
		   then 'NULL' else xact_start::text end||' , Query_Start: '
	||case when query_start is null 
		   then 'NULL' else query_start::text end||' , Xact_Elapse: '
	||case when (now()-xact_start) is null 
		   then 'NULL' else (now()-xact_start)::text end||' , Query_Elapse: '
	||case when (now()-query_start) is null 
		   then 'NULL' else (now()-query_start)::text end
	||chr(10)||'SQL (Current SQL in Transaction): '||chr(10)
	||case when query is null then 'NULL' else query::text end,chr(10)
	||'--------'||chr(10)  
	order by  
	 ( case mode  
	  when 'INVALID' then 0  
	  when 'AccessShareLock' then 1  
	  when 'RowShareLock' then 2  
	  when 'RowExclusiveLock' then 3  
	  when 'ShareUpdateExclusiveLock' then 4  
	  when 'ShareLock' then 5  
	  when 'ShareRowExclusiveLock' then 6  
	  when 'ExclusiveLock' then 7  
	  when 'AccessExclusiveLock' then 8  
	  else 0  
	 end ) desc,  
	 (case when granted then 0 else 1 end) 
	) as lock_conflict 
	from t_unionall  
	group by locktype,datname,relation,page,tuple,virtualxid,
	transactionid::text,classid,objid,objsubid; 

# 输出的结果如下:
-[ RECORD 1 ]-+------------------------------------------------------
locktype      | relation                                             
datname       | postgres                                             
relation      | testtable1                                           
page          |                                                      
tuple         |                                                      
virtualxid    |                                                      
transactionid |                                                      
classid       |                                                      
objid         |                                                      
objsubid      |                                                      
lock_conflict 
| Pid: 41381                                           
              | Lock_Granted: false , Mode: AccessExclusiveLock , ...
			    | SQL (Current SQL in Transaction):                    
              | alter table testtable1 add dno int;                  
              | --------                                             
              | Pid: 40920                                           
              | Lock_Granted: true , Mode: RowExclusiveLock , ...    
			    | SQL (Current SQL in Transaction):                    
              | update testtable1 set tname='Tom123' where tid=1;    

# 通过这里输出的信息可以看到阻塞的进程ID号,以及发生等待的SQL语句。

(7)在会话一执行结束事务操作。

sql 复制代码
postgres=*# commit;

# 此时会话二将成功执行。

(8)重新在会话三中查看表testtable1上的锁信息,此时将没有任何锁的信息输出。

sql 复制代码
postgres=# select * from pg_locks where relation = 16395;

二、 行级锁

同一个事务可能会在相同的行上保持冲突的锁。但是除此之外,两个事务永远不可能在相同的行上持有冲突的锁。PostgreSQL行级锁不影响数据查询,它们只阻塞对同一行的写入者和加锁者。行级锁在事务结束时或保存点回滚的时候释放,就像表级锁一样。PostgreSQL行级锁支持以下四种不同的模式,如下表所示。

这四种行级锁彼此之间存在一定的冲突,下表说明了它们之间的冲突关系。

×表示存在相互之间的冲突。

在PostgreSQL中可以通过查询pg_locks的系统视图来获取行级锁的相关信息。下面展示了该系统视图的结构。

sql 复制代码
postgres=# \d pg_locks;
                   View "pg_catalog.pg_locks"
       Column       |   Type   | Collation | Nullable | Default 
--------------------+----------+-----------+----------+---------
 locktype           | text     |           |          | 
 database           | oid      |           |          | 
 relation           | oid      |           |          | 
 page               | integer  |           |          | 
 tuple              | smallint |           |          | 
 virtualxid         | text     |           |          | 
 transactionid      | xid      |           |          | 
 classid            | oid      |           |          | 
 objid              | oid      |           |          | 
 objsubid           | smallint |           |          | 
 virtualtransaction | text     |           |          | 
 pid                | integer  |           |          | 
 mode               | text     |           |          | 
 granted            | boolean  |           |          | 
 fastpath           | boolean  |           |          | 
相关推荐
a***56061 小时前
【Navicat+MySQL】 在Navicat内创建管理数据库、数据库表。
数据库·mysql·oracle
一点事1 小时前
oracle:密码过期处理
数据库·oracle
Allen_LVyingbo1 小时前
解锁医疗AI新引擎:从数据库与编程语言透视合成数据生成(代码部分)
数据库·人工智能·深度学习·健康医疗
n***26561 小时前
数据库(MySQL):使用命令从零开始在Navicat创建一个数据库及其数据表(一).创建基础表
数据库·mysql·oracle
枫叶丹41 小时前
【Qt开发】Qt窗口(五) -> 非模态/模态对话框
c语言·开发语言·数据库·c++·qt
几何心凉1 小时前
从全密态到AI运维:openGauss构建企业级数据安全与效率的双重屏障
开发语言·数据库
李景琰2 小时前
多级缓存架构与一致性核心挑战:基于Caffeine本地缓存、Redis分布式缓存与MySQL数据库的多级缓存数据一致性方案
数据库·缓存·架构
盼哥PyAI实验室3 小时前
MySQL 数据库基础:字段判空、逻辑查找、排序、限制、模糊搜索与聚合函数全解析
数据库·mysql·oracle
v***91306 小时前
【MySQL】JDBC的连接
数据库·mysql