当前位置: 首页 > news >正文

PostgreSQL——事务处理与并发控制

PostgreSQL事务处理与并发控制

  • 一、事务管理简介
    • 1.1、事务的含义
    • 1.2、事务的属性
    • 1.3、事务快管理的常用语句
    • 1.3、事务的应用案例
  • 二、并发控制
  • 三、锁机制
    • 3.1、锁的类型
    • 3.2、死锁
  • 四、锁的应用案例

一、事务管理简介

1.1、事务的含义

事务要有非常明确的开始和结束点,PostgreSQL中的每一条数据操作语句,例如SELECT、INSERT、UPDATE和DELETE都是隐式事务的一部分。即使只有一条语句,系统也会把这条语句当作一个事务,要么执行所有语句,要么什么都不执行。

在Postgr©SQL中,由事务管理器负责管理事务运行的模块,主要结构如图所示。

在这里插入图片描述

  • 事务管理器是事务系统的中枢,通过接收的信息,处理下一步的事务操作。
  • 锁管理器主要提供在事务的写阶段并发控制所需要的各种锁,从而保证事务的各种隔离级别。
  • 日志管理器主要记录事务执行的状态和数据的变化过程。

事务开始之后,事务中所有的操作都会写到事务日志中,写到日志中的事务,一般有两种:

  • 一是针对数据的操作,例如插入、修改和删除,这些操作的对象是大量的数据:
  • 另一种是针对任务的操作,例如创建索引。

当取消这些事务操作时,系统自动执行这种操作的反操作,保证系统的一致性。系统自动生成一个检查点机制,这个检查点周期地检查事务日志。如果在事务日志中,事务全部完成,那么检查点事务日志中的事务提交到数据库中,并且在事务日志中做一个检查点提交标识:如果在事务日志中,事务没有完成,那么检查点将事务日志中的事务不提交到数据库中,并且在事务日志中做一个检查点未提交的标识。事务的恢复及检查点保证了系统的完整和可恢复。

1.2、事务的属性

事务是作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元必须有4个属性,称为原子性(Atomic)、一致性(Consistent)、隔离性(Isolated)和持久性(Durable)属性,简称ACID属性,只有这样才能成为一个事务。

  • 原子性:事务必须是原子工作单元:对于其数据修改,要么全都执行,要么全都不执行。
  • 一致性:事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构都必须是正确的,
  • 隔离性:由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务识别数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是第二个事务修改它之后的状态,事务不会识别中间状态的数据。这称为可串行性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同
  • 持久性:事务完成之后,它对于系统的影响是永久性的。该修改即使出现系统故障也将一直保持

1.3、事务快管理的常用语句

在PostgreSQL中,一个事务是通过把SQL命令用BEGN和COMMIT命令包围实现的。语法格式如下:

BEGIN;
SQL语句1;
...
COMMIT;

事务块是指包围在BEGN和COMMIT之间的语句。PostgreSQL9中,常用的事务快管理语句的含义如下。

  • START TRANSACTION:此命令表示开始一个新的事务块.
  • BEGN:此命令表示初始化一个事务块。在BEGN命令后的语句都将在一个事务里面执行,直到出现COMMITROLLBACK,此命令和START TRANSACTION是等价的
  • COMMIT:此命今表示提交事务.
  • ROLLBACK:此命令表示事务失败时执行回滚操作
  • SET TRANSACTION:设置当前事务的特性,对后面的事务没有影响,

1.3、事务的应用案例

-- 向person表插入3条学生记录,此时发现是不应该插入的,进行回滚操作。-- 首先,查看stu info表中当前的记录,查询语句如下:
select * from person;

在这里插入图片描述

-- 输入事务语句
begin;
insert into person values(1003, '路飞', 80, '10456354');
insert into person values(1004, '张露', 85, '56423424');
insert into person values(1005, '魏波', 70, '41243774');
rollback transaction;
commit;

该段代码使用INSERT向person表中插入3条记录,插入完成之后,使用ROLLBACK TRANSACTI1ON撤销所有的操作。

-- 在此查询,数据无变化
select * from person;

在这里插入图片描述

二、并发控制

1. 脏读:

当个事务读取的记录是另一个事务的一部分时,如果第一个事务正常完成,就没有什么问题,如果此时另一个事务尚未完成,就产生了脏读。例如,员工表中编号为1001的员工工资为1740,如果事务1将工资修改为1900,但还没有提交确认:此时事务2读取员工的工资为1900:事务1中的操作因为某种原因执行了ROLLBACK回滚,取消了对员工工资的修改,但事务2已经把编号为1001的员工的数据读走了,此时就发生了脏读。

2. 幻读:
当某一数据行执行INSERT或DELETE操作,而该数据行恰好属于某个事务正在读取的范围时,就会发生幻读现象。例如,现在要对员工涨工资,将所有低于1700的工资都涨到新的1900,事务1使用UPDATE语句进行更新操作,事务2同时读取这一批数据,但是在其中插入了几条工资小于1900的记录,此时事务1如果查看数据表中的数据,会发现自己UPDATE之后还有工资小于1900的记录!幻读事件是在某个凑巧的环境下发生的,简而言之,它是在运行UPDATE语句的同时有人执行了NSERT操作。因为插入了一个新记录行,所以没有被锁定,因而不能正常运行。

3. 不可重复性读取:

如果一个事务不止一次的读取相同的记录,但在两次读取中间有另一个事务刚好修改了数据,则两次读取的数据将出现差异,此时就发生了不可重复性读取。例如,事务1和事务2都读取一条工资为2310的数据行,如果事务1将记录中的工资修改为2500并提交,而事务2使用的员工的工资仍为2310。

PostgreSQL为开发者提供了丰富的对数据并发访问进行管理的工具。PostgreSQL利用多版本控制(MVCC)来维护数据的一致性。这就意味着当检索数据时,每个事务看到的都只是一小段时间之前的数据快照(一个数据库版本),而不是数据的当前状态。这样,如果对每个数据库会话进行事务隔离,就可以避免一个事务看到其他并发事务的更新而导致不一致的数据。

隔离级别脏读幻读不可重复性读取
读未提交可能可能可能
读已提交不可能可能可能
可重复读不可能可能不可能
可串行读不可能不可能不可能

在PostgreSQL中,可以请求4种可能的事务隔离级别中的任意一种。但是在内部,实际上只有两种独立的隔离级别,分别对应读已提交和可串行化。如果选择了读未提交的级别,实际上你用的是读已提交,在选择可重复读级别的时候,实际上用的是可串行化,所以实际的隔离级别可能比选择的更严格。这是SQL标准允许的:4种隔离级别只定义了哪种现象不能发生,但是没有定义哪种现象一定发生。PostgreSQL只提供两种隔离级别的原因是,这是把标准的隔离级别与多版本并发控制架构映射相关的唯一合理方法。

PostgreSQL中的两种级别的隔离如下。

1. 读已提交:

读已提交是PostgreSQL里的默认隔离级别.当一个事务运行在这个隔离级别时,一个SELECT查询只能看到查询开始之前已提交的数据,而无法看到未提交的数据或者在查询执行期间其他事务己提交的数据。

如果两个事务在对同一组数据进行更新操作,第二个事务需要等待第一个事务提交或者更新回滚。如果第一个事务进行提交,系统将重新计算查询条件,如果符合条件,则第二个事务继续进行更新操作:如果第一个事务进行更新回滚,那么它的作用将被忽略,而第二个事务将继续更新最初发现的行。

2. 可串行化:

可串行化级别提供最严格的事务隔离。这个级别模拟串行的事务执行,就好像事务将被-一个接着一个串行(而不是并行)地执行。不过,使用这个级别的应用必须准备在串行化失败的时候重新启动事务。

如果两个事务在对同一组数据进行更新操作,串行化事务将等待第一个正在更新的事务提交或者回滚。如果第一个事务提交了,那么串行化事务将回滚,从头开始重新进行整个事务:如果第一个事务回滚,那么它的影响将被忽略,这个可串行化的事务就可以在该元组上进行其更新操作。

三、锁机制

3.1、锁的类型

PostgreSQL中提供了3种锁模式,分别为SpinLockLWLookRegularLock

1. SpinLock(自旋锁):
SpinLock使用互斥信号,与操作系统和硬件环境联系比较密切。SpinLock锁封锁的时间很短,没有等待队列和死锁检测机制。另外事务结束时,不能自动释放SpinLock锁。

2. LWLock(轻量级锁):
LWLock主要提供对共享存储器的数据结构的互斥访问。特点主要是有等待队列和无死锁检测。另外事务结束时,可以自动释放LWLock锁。LWLock锁分为排他模式和共享模式。

  • 排他模式一用于数据修改操作,例如INSERT、UPDATE或DELETE。确保不会同时对同一资源进行多重更新。
  • 共享模式一用于读取数据操作,允许多个事务读取相同的数据,但不允许其他事务修改当前数据,如SELECT语句。当多个事务读取一个资源时,资源上存在共享锁,任何其他事务都不能修改数据,除非将事务隔离级别设置为可重复读或者更高的级别,或者在事务生存周期内用锁定提示对共享锁进行保留,那么,一旦数据完成读取,资源上的共享锁立即得以释放。

3. RegularLock(常规锁):

RegularLock为一般数据库事务管理中所指的锁。主要特点为有等待队列、有死锁检测和能自动释放锁。

RegularLock支持的锁的模式有8种,按排他级别从低到高分别如下。

  • ACCESS SHARE(访问共享锁):查询命令(SELECT)将会在它查询的表上获取访问共享锁。一般地,任何一个对表上的只读查询操作都将获取这种类型的锁。此模式的锁和ACCESS EXCLUSIVE(访问排他锁)是冲突的。
  • ROW SHARE(行共享锁):使用SELECT FOR UPDATESELECT FOR SHARE语句将获得行共享锁。另外此锁和EXCLUSIVE(排他锁)和ACCESS EXCLUSIVE(访问排他锁)是冲突的。
  • ROW EXCLUSIVE(行排他锁):使用UPDATEDELETEINSERT命令会在目标表上获得行排他锁,并且在其他被引用的表上加上ACCESS SHARE锁。一般的,更改表数据的命令都将在这张表上获得ROW EXCLUSIVE锁.另外此锁和SHARE(共享锁).SHAREROW EXCLUSIVE(共享行排他锁)、EXCLUSIVE(排他锁)和ACCESS EXCLUSIVE(访问排他锁)是冲突的,
  • SHARE UPDATE EXCLUSIVE(共享更新排他锁):使用VACUUM(不带FULL选项)ANALYZECREATE INDEX CONCURRENTLY语句时使用共享更新排他锁。
  • SHARE(共享锁):使用CREATE INDEX(不带CONCURRENTLY选项)语句请求时使用共享锁。
  • SHARE ROW EXCLUSIVE(共享行排他锁):和排他锁类似,但是允许行共享。
  • EXCLUSIVE(排他锁):阻塞行共享和使用SELECT FOR UPDATE语句时使用排他锁。
  • ACCESS EXCLUSIVE(访问排他锁):使用ALTER TABLE、DROP TABLE,TRUNCATE REINDEX,CLUSTER或VACUUM FULL语句会获得访问排他锁。在Lock table命今中,
    如果没有声明其他模式,它也是默认模式,

3.2、死锁

在两个或多个任务中,如果每个任务锁定了其他任务试图锁定的资源,此时会造成这些任务永久阻塞,从而出现死锁,此时系统处于死锁状态。

1.死锁的原因:
在多用户环境下,死锁的发生是由于两个事务都锁定了不同的资源、而同时又都在申请对方锁定的资源,即一组进程中的各个进程均占有不会释放的资源,但因互相申请其他进程占用的不会释放的资源而处于一种永久等待的状态。形成死锁有4个必要条件:

  • 请求与保持条件—获取资源的进程可以同时中请新的资源
  • 非剥夺条件一已经分配的资源不能从该进程中剥夺
  • 循环等待条件一多个进程构成环路,并且其中每个进程都在等待相邻进程正占用的资源,
  • 互斥条件—资源只能被一个进程使用.

2.可能会造成死锁的资源:
每个用户会话可能有一个或多个代表它运行的任务,其中每个任务可能获取或等待获取各种资源。以下类型的资源可能会造成阻塞,并最终导致死锁。

  • :等待获取资源(如对象、页、行、元数据和应用程序)的锁可能导致死锁。例如,事务T1在行1上有共享锁(S锁)并等待获取行r2的排他锁(X锁),事务T2在行r2上有共享锁(S锁)并等待获取行1的排他锁(X锁)。这将导致一个锁循环,其中,T1和T2都等待对方释放已锁定的资源。
  • 工作线程:排队等待可用工作线程的任务可能导致死锁。如果排队等待的任务拥有阻塞所有工作线程的资源,则将导致死锁。例如,会话S1启动事务并获取行1的共享锁(S锁)》后,进入睡眠状态。在所有可用工作线程上运行的活动会话正尝试获取行1的排他锁(X锁),因为会话S1无法获取工作线程,所以无法提交事务并释放行1的锁,这将导致死锁。
  • 内存:当并发请求等待获得内存,而当前的可用内存无法满足其需要时,可能发生死锁。例如,两个并发查询(Q1和Q2)作为用户定义函数执行,分别获取10MB和20MB的内存。如果每个查询需要30MB而可用总内存为20MB,则Q1和Q2必须等待对方释放内存,这将导致死锁,
  • 并行查询执行的相关资源,通常与交换端口关联的处理协调器、发生器或使用者线程至少包含一个不属于并行查询的进程时,可能会相互阻塞,从而导致死锁,此外,当并行查询
    启动执行时,PostgreSQL将根据当前的工作负荷确定并行度或工作线程数,如果系统工作负荷发生意外更改,例如,当新查询开始在服务器中运行或系统用完工作线程时,则可能发生死锁。

3.减少死锁的策略:
复杂的系统中不可能百分之百地避免死锁,从实际出发为了减少死锁,可以采用以下策略:

  • 在所有事务中以相同的次序使用资源。
  • 使事务尽可能简短并且在一个批处理中。
  • 为死锁超时参数设置一个合理范围,如3~30分钟超时,则自动放弃本次操作,避免进程挂起
  • 避免在事务内部和用户进行交互,减少资源的锁定时间。
  • 使用较低的隔离级别,相比较高的隔离级别能够有效减少持有共享锁的时间,减少锁之间的竞争

四、锁的应用案例

在PostgreSQL中,使用LOCK命令锁定一个表。具体语法如下:

LOCK [TABLE] name [, ...] [IN locakmode MODE] [NOWAIT]

其中,name为要锁定的现存表的名称:lockmode为锁模式,声明这个锁和哪些锁冲突,如果没有声明锁模式,默认模式为ACCESS EXCLUSIVE模式:NOWAIT声明LOCK TABLE不去等待任何冲突的锁释放,如果无法不等待获取所要求的锁,那么事务退出。

-- 有一个现存的表person,判断此表,如果有外键,则在插入数据时使用SHARE(共享锁),事务如下:
begin;
lock table person in share mode;
select id from person
where name = 'Star Wars: Episode I - The Phantom Menace';
-- 如果记录没有返回则ROLLBACK
insert into person_user_comments 
values(_id_, 'GREAT! I was waiting for it for so lang!');
commit;
-- 如果一个表含有主键,则在删除时,进行SHARE ROW EXCLUSIVE(共享行排他锁)操作,事务如下:
begin;
lock table person in share row exclusive mode;
delete from person_user_comments 
where id in (select id from filmswhere rating < 5
);
delete from person 
where rating < 5;
commit;
http://www.dtcms.com/a/334797.html

相关文章:

  • CVE-2021-4300漏洞复现
  • 海康机器人3D相机的应用
  • ZKmall开源商城的数据校验之道:用规范守护业务基石
  • Vue 3与React内置组件全对比
  • 【lucene】SegmentInfos
  • 《Leetcode》-面试题-hot100-技巧
  • 科研工具的一些注意事项
  • 【minio】一、Linux本地部署MinIO
  • stringstream + getline()实现字符串分割
  • Java 10 新特性及具体应用
  • 二分查找。。
  • 【大语言模型 02】多头注意力深度剖析:为什么需要多个头
  • Python 类元编程(元类的特殊方法 __prepare__)
  • nflsoi 8.16 题解
  • 【数据结构】-2- 泛型
  • Python - 100天从新手到大师:第十一天常用数据结构之字符串
  • Java实现汉诺塔问题
  • AI Agents 2025年十大战略科技趋势
  • 【嵌入式C语言】六
  • .net印刷线路板进销存PCB材料ERP财务软件库存贸易生产企业管理系统
  • mit6.824 2024spring Lab1 MapReduce
  • 衡石使用指南嵌入式场景实践之仪表盘嵌入
  • 3 统一建模语言(UML)(上)
  • 力扣 hot100 Day75
  • 动手学深度学习(pytorch版):第三章节—线性神经网络(6) softmax回归的从零开始实现
  • 基于深度学习的老照片修复系统
  • 嵌入式硬件篇---电源电路
  • SpringBoot自动配置原理(二)
  • 智能客服、AI工作流、语音、聊天模板
  • MySQL的下载安装(MSI和ZIP版本都有)