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

简介: 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  |           |          |



相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
8月前
|
存储 SQL 关系型数据库
【赵渝强老师】使用mydumper备份MySQL
本文介绍了使用mydumper工具进行MySQL数据库备份与恢复的操作方法。相比单线程工作的mysqldump,mydumper支持多线程,速度提升可达10倍。其功能包括事务性表快照、快速压缩、导出binlog等,并提供详细的参数说明和操作步骤。文章通过实例演示了安装mydumper、创建存储目录、全库备份、指定数据库及表备份、删除数据库以及使用myloader恢复数据的完整流程,并附带视频讲解,帮助用户更好地理解和应用该工具。
551 0
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
构建AI智能体:三十八、告别“冷启动”:看大模型如何解决推荐系统的世纪难题
协同过滤是推荐系统中广泛使用的技术,其核心思想是利用用户行为数据发现相似用户或物品进行推荐。摘要包括:1)协同过滤基于用户历史行为数据,通过计算相似度(如余弦相似度、皮尔逊相关系数)预测用户偏好;2)主要分为基于用户(寻找相似用户群体)和基于物品(发现相似物品)两种方法;3)面临冷启动、数据稀疏性等挑战,可通过混合推荐(结合内容特征)和矩阵分解等技术解决;4)典型应用包括电商猜你喜欢和流媒体推荐;5)结合大语言模型可增强语义理解能力,提升推荐准确性。
437 9
|
1天前
|
存储 人工智能 关系型数据库
传统数据库与向量数据库:一个管“是什么”,一个管“像什么”
向量数据库是AI时代的语义检索引擎,将文本、图片等非结构化数据转化为“语义向量”,支持基于相似性的毫秒级搜索。它不替代MySQL等传统数据库,而是作为大模型的“海马体”,赋能RAG、智能问答与多模态应用,实现从“关键词匹配”到“理解含义”的跃迁。(239字)
42 7
|
3月前
|
监控 应用服务中间件 API
Agentic 应用时代,Dify 全链路可观测最佳实践
本文讲述 Dify 平台在 Agentic 应用开发中面临的可观测性挑战,从开发者与运维方双重视角出发,系统分析了当前 Dify 可观测能力的现状、局限与改进方向
592 18
Agentic 应用时代,Dify 全链路可观测最佳实践
|
3月前
|
关系型数据库 MySQL Java
开源PolarDB-X备份恢复操作实操
作者介绍: 付文革,航天壹进制(江苏)信息科技有限公司产品研发,专注于数据库备份,主攻MySQL相关数据库以及各种国产分布式数据库的备份恢复,主要使用Java 、Python、Shell等编程语言 航天壹进制(江苏)信息科技有限公司(简称航天壹进制)作为中国航天科工集团有限公司旗下上市公司航天工业发展股份有限公司的全资下属企业,专注于数据安全领域,自主研发并提供数据保护与业务连续性管理产品、解决方案及服务。
|
3月前
|
人工智能 数据挖掘 关系型数据库
内附原文|VLDB论文精读:AI进行时,数据分析迈入增量计算时代
阿里云AnalyticDB团队近期在VLDB 2025上发表了关于增量计算的最新研究成果——论文《Streaming View: An Efficient Data Processing Engine for Modern Real-time Data Warehouse of Alibaba Cloud》。本文将对该工作进行简要介绍。
|
3月前
|
SQL Oracle 关系型数据库
【赵渝强老师】Oracle数据库的PL/SQL编程语言
PL/SQL是Oracle对SQL的扩展,结合过程化编程特性,支持开发存储过程、函数和触发器。本文介绍其基本结构,并演示如何在SQL*Plus中编写“Hello World”程序,需启用serveroutput输出。也可使用Oracle SQL Developer更高效地开发调试PL/SQL应用。
118 3
|
10月前
|
存储 SQL 缓存
Apache Doris & SelectDB 技术能力全面解析
本文将对 Doris & SelectDB 适合的分析场景和技术能力进行概述解析
1552 1
Apache Doris & SelectDB 技术能力全面解析
|
SQL 存储 运维
从Citus深度解密如何基于PostgreSQL做分布式数据库
从源码级别揭秘Citus如何基于PostgreSQL做一款分布式数据库,解决分布式场景的数据分片、分布式SQL、分布式事务、数据倾斜、数据迁移等难点问题,理解分布式领域设计的“取”与“舍”。
2864 3
从Citus深度解密如何基于PostgreSQL做分布式数据库
|
关系型数据库 MySQL 数据库
同一台电脑安装两个不同版本的mysql。简单暴力有效
这篇文章介绍了在同一台电脑上安装并配置两个不同版本的MySQL数据库的过程,包括修改端口号、配置服务、修改连接端口和测试连接情况,以确保不同版本的MySQL能够正常运行而不相互干扰。
同一台电脑安装两个不同版本的mysql。简单暴力有效