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

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
云原生数据库 PolarDB 分布式版,标准版 2核8GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: PostgreSQL通过表级锁和行级锁实现并发控制,结合MVCC机制保障数据一致性。锁模式多样,粒度精细,可有效避免事务冲突,提升并发性能。

b411.png

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

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


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


一、 表级锁

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

image.png


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

image.png


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

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

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。

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语句。

postgres=# start transaction;
postgres=*# update testtable1 set tname='Tom123' where tid=1;
# 这里执行的事务没有结束。


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

postgres=# alter table testtable1 add dno int;
# 此时会话二的操作将会被阻塞。


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

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)执行语句检查锁的等待,即:会话之间的阻塞信息。

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)在会话一执行结束事务操作。

postgres=*# commit;
# 此时会话二将成功执行。


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

postgres=# select * from pg_locks where relation = 16395;


二、 行级锁

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

image.png


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

image.png


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


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

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  |           |          |



相关文章
|
4天前
|
搜索推荐 编译器 Linux
一个可用于企业开发及通用跨平台的Makefile文件
一款适用于企业级开发的通用跨平台Makefile,支持C/C++混合编译、多目标输出(可执行文件、静态/动态库)、Release/Debug版本管理。配置简洁,仅需修改带`MF_CONFIGURE_`前缀的变量,支持脚本化配置与子Makefile管理,具备完善日志、错误提示和跨平台兼容性,附详细文档与示例,便于学习与集成。
296 116
|
19天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
7天前
|
数据采集 人工智能 自然语言处理
Meta SAM3开源:让图像分割,听懂你的话
Meta发布并开源SAM 3,首个支持文本或视觉提示的统一图像视频分割模型,可精准分割“红色条纹伞”等开放词汇概念,覆盖400万独特概念,性能达人类水平75%–80%,推动视觉分割新突破。
460 44
Meta SAM3开源:让图像分割,听懂你的话
|
13天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
684 222
|
1天前
|
Windows
dll错误修复 ,可指定下载dll,regsvr32等
dll错误修复 ,可指定下载dll,regsvr32等
134 95
|
11天前
|
人工智能 移动开发 自然语言处理
2025最新HTML静态网页制作工具推荐:10款免费在线生成器小白也能5分钟上手
晓猛团队精选2025年10款真正免费、无需编程的在线HTML建站工具,涵盖AI生成、拖拽编辑、设计稿转代码等多种类型,均支持浏览器直接使用、快速出图与文件导出,特别适合零基础用户快速搭建个人网站、落地页或企业官网。
1678 158
|
存储 人工智能 监控
从代码生成到自主决策:打造一个Coding驱动的“自我编程”Agent
本文介绍了一种基于LLM的“自我编程”Agent系统,通过代码驱动实现复杂逻辑。该Agent以Python为执行引擎,结合Py4j实现Java与Python交互,支持多工具调用、记忆分层与上下文工程,具备感知、认知、表达、自我评估等能力模块,目标是打造可进化的“1.5线”智能助手。
927 61