一、 事务
1、事务:构成单一逻辑工作单元的操作集合称为事务。这些操作要么完整地执行,要么完全不执行。满足ACID(原子性、一致性、隔离性、持久性)。
2、整个事务系统可以分为三部分:
· 事务管理器:事务处理系统的中枢;
· 锁管理器:实现了系统并发控制所需要的各种锁;
· 日志管理器:记录事务执行的状态以及数据的变化过程。
3、事务系统的层次:
· 事务块:能包含多个子事务,多个子事务逻辑上隶属于一个事务块。
· PGSQL默认把每条SQL语句都看成是一个“事务”来处理。事务块使得一个“事务”能够处理多条SQL语句,在引入子事务处理后,可以用于控制事务的执行步骤。
· 事务块的操作:每个SQL语句(包括BEGIN、END等)执行时,会先调用中间层函数来检测和改变事务块的状态。
· 中间层:SQL语句执行时,用于判断和改变事务块状态。
· 底层操作:进行实际的数据库操作,包括资源和锁的获取及释放、元组插入、删除、更新、保存日志记录等。
4、事务执行状态:
事务状态用来标明事务此时所处的状态,包括“提交”“中止”“正在处理”等。
5、 事务处理的函数:处理事务最底层的工作,如初始化内存、释放内存、清理事务状态、写日志等。
· StartTransaction:开启一个新事务
· CommitTransaction:提交事务
· AbortTransaction:中止事务
· CleanupTransaction:事务完成后清除相关信息
· StartSubTransaction:开启子事务
· CommitSubTransaction:提交子事务
· AbortSubTransaction:中止子事务
· CleanupSubTransaction:清除子事务
6、在PG中,通过使用子事务来实现保存点。保存点(savepoint)用于在事务内进行回滚。
7、实ID和虚ID:当事务内不存在对数据表进行修改(增删改)操作时,为保存点分配虚ID(VXID);否则,为当前及之前保存点全部赋予实ID(XID)用于记录日志。
· 事务没有被分配XID时,也需要一个唯一标识。为了标识顶层事务,PG为其分配一个VXID。通过后台进程号和本进程的本地事务计数器保证顶层事务具有一定的唯一性,当整个事务块都不进行数据库的增删改时(即只读时),用VirtualTransactionId来标识事务。
· 子事务申请XID时,需要先为其父事务分配XID,以保证子事务XID的分配在父事务后。
8、两阶段提交:支持分布式数据库的事务处理(一个事务涉及到多个数据库)
· 分布式处理:预提交阶段和全局提交阶段。
· 预提交阶段:
§ (1)有一个数据库会被选择成为“协调者”。协调者在本地开始一个分布式事务,并向其他数据库发送“PREPARE”消息,发送消息时会使用专门的事务ID(GID)来标识此分布式事务。这样数据库之间可以确定需要同步执行的事务。
§ (2)其他数据库接收到“PREPARE”消息后,会试图开始一个本地事务以完成分布式事务的功能。它决定这个事务是提交或者终止,然后把该决定发送给“协调者”。
§ (3)如果其他数据库决定提交上述的本地事务,那么它就进入了一个“预提交”阶段。在此阶段,如果协调者没有发送终止的消息,它不能终止这个本地事务。如果其他数据库决定终止这个事物,它会向协调者发送取消的信息,然后由协调者进行全局性的取消动作。
· 全局提交阶段:
§ (1)如果其他数据库返回给协调者的消息都是“READY”,协调者将提交这个分布式事务,并将“COMMIT”消息发送给其他数据库。如果协调者收到一条“取消”消息,则取消这个分布式事务,然后发送消息进行全局性的取消动作。
§ (2)本地数据库根据协调者的消息,对本地事务进行提交或终止。
二、事务隔离
1. 事务隔离的概念
在数据库管理系统中,事务隔离是一项重要的功能,它能确保在并发访问数据库时事务之间能够独立运行,不会相互干扰。数据库系统通常支持不同级别的事务隔离,用来满足不同应用程序之间的需求。
2. 事务隔离的种类
常见的事务隔离的种类也一共有四种,包括读未提交、读已提交、可重复读和可序列化。而对于并发的事务,常见的一些可能发生的行为包括:
l 脏读:一个事务读取了另一个未提交的事务写入的数据
l 不可重复读:一个事务重新读取了前面读取过的数据,但是该数据已经发生了改变
l 幻读:一个事务开始之后,需要根据数据库中现有的数据做一些更新,于是重新执行一个查询,返回符合查询条件的行,这个时候发现因为其他最近提交的事务自身发生改变,导致现有事务如果继续执行可能会发生错误。
下表代表的是不同的事务隔离级别对应的可能会发生的行为的对照关系。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不可能 | 可能 | 可能 |
重复读 | 不可能 | 不可能 | 可能 |
可串行化 | 不可能 | 不可能 | 不可能 |
注:在PG中,默认的事务隔离级别是读已提交,而读未提交相当于是读已提交的一个更弱的版本。
这是因为读已提交的隔离级别要求一个事务只能看到已经提交的数据修改,这意味着一个事务不能读取另一个事务尚未提交的数据变换。那么这种隔离级别提供了一定的数据一致性,防止脏读。这也是为什么PG中采用这种隔离级别为默认的隔离级别,因为他的适应性比较广。
而相比于读已提交,读未提交的隔离级别不要求事务等待其他事务的提交,它允许一个事务读取另一个事务尚未提交的数据变化,这种隔离级别容易导致脏读,因为一个事务可能会读取到另一个正在被修改的事务,而这些事务可能会回滚,从而导致数据状态前后不一致。
并且PG中内部没有实现读未提交的隔离级别,因为读未提交的隔离级别存在严重的数据一致性问题,不符合事务的ACID特性,所以并未实现读未提交,主要还是为了确保数据库的一致性和可靠性。
2.1 读已提交
l PG的默认事务隔离级别
l 事务只能看到已经提交的数据
l 可能会发生不可重复读和幻读
案例:
首先创建一个示例表,其中包含如下的信息:
create table products(id serial primary key,name varchar(50),price numeric(10,2));
select * from products;
会话1:(读操作)
begin;
select * from products where id=1;
会话2:(写操作)
begin;
update products set price =66 where id = 1;
此时回到会话1:查询id=1,发现数据还是原来的数据,
会话2:
commit;
当会话2提交以后,回到会话1,就会发现数据进行了更改
这个案例说明了在读已提交隔离级别中,一个事务只能看到其他事务已经提交的数据修改,如果事务未提交,那么对于查询事务来说,这些修改是不可见的,直到其他事务提交为止
2.2 可重复读
l 保证在同一事务内的查询不会受到其他事务的影响
l 防止不可重复读,但是仍然可能发生幻读
l 适用场景:需要一定程度的数据一致性,可以容忍轻微的幻读。
案例
沿用上面的products表的数据,将修改的数据修改回原样。
会话1:
begin isolation level repeatable read ;
select * from products where id =1;
会话2:
此时会话2.执行更新语句,但不提交,在会话1中查看数据。
会话2:
对事务进行提交,再在事务1中查询对应的数据
可以发现,在会话1中,无论会话2提不提交,查询的结果都保持不变,即使会话2提交更新操作,查询结果仍然不变。
此时,除非对会话1也进行提交,才可以看到改动,就相当于在事务的一开始对数据进行了快照,无论如何改动,都不影响读取的结果。
2.3 串行化
l 最高级别的隔离,确保事务之间没有任何的交错或者是并发问题
l 通过锁定数据来实现,防止任何形式的并发问题,包括脏读、不可重复读、幻读
l 性能最差,因为他几乎完全禁止并发
l 适用场景:对数据一致性要求非常高,可以容忍较低的性能.
l 事务2会进行回滚,提交失败以后,事务的回滚其实就相当于没有执行
案例:
会话1:
开始串行化隔离,插入一条新的数据,查询现在的价格
会话2;
执行与会话1同样的操作,其中开启事务的语句是同时执行的,然后再分别插入数据。
可以看到,两个会话中新插入的语句只有在自己的事务中才可以查看到
此时将会话1事务进行提交,然后将会话2事务进行提交,可以看到,会话1提交成功,而会话2会提交失败,且会话2中的事务发生了回滚,舍弃了之前的插入操作,
重新查询两个会话中表的信息,发现都只能查询到会话1提交的事务。