PostgreSql 事务

一、事务的 ACID 特性

在日常操作中,对于一组相关操作,通常需要其全部成功或全部失败。在关系型数据库中,将这组相关操作称为事务。事务具有的四个特性简称为 ACID。

  • 原子性(Atomicity):保证事务中的操作要么全部成功,要么全部失败,不会只成功一部分。
  • 一致性(Consistency):数据修改的有效性,并且遵循一定的业务规则。
  • 隔离性(Isolation):决定了并发事务之间的可见性和相互影响程度。
  • 持久性(Durability):确保已经提交的事务必须永久生效。

二、事务的使用

2.1 自动提交事务

sql 复制代码
postgres=# \echo :AUTOCOMMIT
on
postgres=# create table accounts(id serial primary key,user_name varchar(50),balance numeric(10,4));
CREATE TABLE
postgres=# alter table accounts add constraint bal_check check(balance >= 0);
ALTER TABLE
postgres=# insert into accounts(user_name, balance) values ('usera', 6000);
INSERT 0 1
postgres=# table accounts;
 id | user_name |  balance
----+-----------+-----------
  1 | usera     | 6000.0000
(1 row)

2.2 手动提交事务

sql 复制代码
postgres=# \set AUTOCOMMIT off
postgres=# \echo :AUTOCOMMIT
off
postgres=# begin;
BEGIN
postgres=*# insert into accounts(user_name, balance) values ('userb', 0);
INSERT 0 1
postgres=*# table accounts;
 id | user_name |  balance
----+-----------+-----------
  1 | usera     | 6000.0000
  2 | userb     |    0.0000
(2 rows)

postgres=*# commit;
COMMIT
postgres=# table accounts;
 id | user_name |  balance
----+-----------+-----------
  1 | usera     | 6000.0000
  2 | userb     |    0.0000
(2 rows)

2.3 手动回滚事务

sql 复制代码
postgres=# begin;
BEGIN
postgres=*# insert into accounts(user_name, balance) values ('userc', 2000);
INSERT 0 1
postgres=*# table accounts;
 id | user_name |  balance
----+-----------+-----------
  1 | usera     | 6000.0000
  2 | userb     |    0.0000
  3 | userc     | 2000.0000
(3 rows)

postgres=*# rollback;
ROLLBACK
postgres=# table accounts;
 id | user_name |  balance
----+-----------+-----------
  1 | usera     | 6000.0000
  2 | userb     |    0.0000
(2 rows)

2.4 手动回滚事务至保存点

sql 复制代码
postgres=# begin;
BEGIN
postgres=*# insert into accounts(user_name, balance) values ('userc', 2000);
INSERT 0 1
postgres=*# savepoint sv1;
SAVEPOINT
postgres=*# insert into accounts(user_name, balance) values ('userd', 0);
INSERT 0 1
postgres=*# table accounts;
 id | user_name |  balance
----+-----------+-----------
  1 | usera     | 6000.0000
  2 | userb     |    0.0000
  4 | userc     | 2000.0000
  5 | userd     |    0.0000
(4 rows)

postgres=*# rollback to sv1;
ROLLBACK
postgres=*# commit;
COMMIT
postgres=# table accounts;
 id | user_name |  balance
----+-----------+-----------
  1 | usera     | 6000.0000
  2 | userb     |    0.0000
  4 | userc     | 2000.0000
(3 rows)

三、并发与隔离

多个用户访问相同数据时,可能导致如下问题:

  • 脏读(dirty read):一个事务能够读取其他事务未提交的修改。
  • 不可重复读(nonrepeatable read):一个事务读取某个记录后,再次读取该记录时数据发生了改变(被其他事务修改并提交)。
  • 幻读(phantom read):一个事务按照某个条件查询一些数据后,再次执行相同查询时结果的数量发生了变化(另一个事务增加或者删除了某些数据并且完成提交)。幻读和非重复读有点类似,都是由于其他事务修改数据导致的结果变化。
  • 更新丢失(lost update):当两个事务同时读取某一记录,然后分别进行修改提交,就会造成先提交的事务的修改丢失。

为了解决并发问题, SQL 标准定义了 4 种不同的事务隔离级别(从低到高):

  • Read Uncommitted(读未提交):最低的隔离级别,实际上就是不隔离,任何事务都可以看到其他事务未提交的修改;该级别可能产生各种并发异常。不过,PostgreSQL 消除了 Read Uncommitted 级别时的脏读,因为它的实现等同于 Read Committed。
  • Read Committed(读已提交):一个事务只能看到其他事务已经提交的数据,解决了脏读问题,但是存在不可重复读、幻读和更新丢失问题。这是 PostgreSQL 的默认隔离级别。
  • Repeated Read(可重复读):一个事务对于同某个数据的读取结果不变,即使其他事务对该数据进行了修改并提交;不过如果其他事务删除了该记录,则无法再查询到数据(幻读)。SQL 标准中的可重复读可能出现幻读,但是 PostgreSQL 在可重复读级别消除了幻读。
  • Serializable(可串行化):最高的隔离级别,事务串行化执行,没有并发。
隔离级别 脏读 不可重复读 幻读 更新丢失
Read Uncommitted 可能,但 PostgreSQL 不会 可能 可能 可能
Read Committed 不可能 可能 可能 可能
Repeatable Read 不可能 不可能 可能,但 PostgreSQL 不会 不可能
Serializable 不可能 不可能 不可能 不可能

修改隔离级别示例

sql 复制代码
--显示数据库隔离级别
show transaction_isolation;

--事务中修改隔离级别
begin;
set transaction isolation level {serializable|repeatable read|read committed|read uncommitted};
......
......
commit;

四、两阶段提交

PostgreSQL 数据库支持两阶段提交协议(默认禁用此功能,仅在分布式架构中使用)。在分布式系统中,事务中往往包含了多台数据库上的操作,虽然单台数据库的操作能够保证原子性,但多台数据库之间的原子性就需要通过两阶段提交来实现了,两阶段提交是实现分布式事务的关键。

两阶段提交协议有如下 5 个步骤:

  • 应用程序先调用各台数据库做一些操作,但不提交事务。然后应用程序调用事务协调器(该协调器可能也是由应用自己实现的)中的提交方法。
  • 事务协调器将联络事务中涉及的每台数据库,并通知它们准备提交事务,这是第一阶段的开始,此时 PostgreSQL 中调用 PREPARE TRANSACTION 命令。
  • 各台数据库接收到 PREPARE TRANSACTION 命令后,PostgreSQL 会将已准备好提交的信息写入持久存储区中,如果无法完成此,会直接返回失败给事务协调器。
  • 事务协调器接收所有数据库的响应。
  • 在第二阶段,如果任何一个数据库在第一阶段返回失败,则事务协调器将会发一个回滚命令 "ROLLBACK PREPARED" 给各台数据库。如果所有数据库的响应都是成功的,则向各台数据库发送 COMMIT PREPARED 命令,通知各台数据库事务成功。

示例

sql 复制代码
--修改 max_prepared_transactions 参数,重启数据库生效
show max_prepared_transactions;
alter system set max_prepared_transactions = 10;
pg_ctl restart
show max_prepared_transactions;

--创建测试表
create table testtab01(id int primary key);

--开启事务,插入数据并进行第一阶段提交
postgres=# begin;
BEGIN
postgres=*# insert into testtab01 values(1);
INSERT 0 1
postgres=*# PREPARE TRANSACTION 'osdba_global_trans_0001';
PREPARE TRANSACTION
postgres=# table testtab01;
 id
----
(0 rows)

--重启数据库后进行第二阶段提交
[postgres@localhost ~]$ pg_ctl restart
waiting for server to shut down.... done
server stopped
waiting for server to start....2023-07-24 17:01:33.206 CST [10882] LOG:  00000: redirecting log output to logging collector process
2023-07-24 17:01:33.206 CST [10882] HINT:  Future log output will appear in directory "logs".
2023-07-24 17:01:33.206 CST [10882] LOCATION:  SysLogger_Start, syslogger.c:674
 done
server started
[postgres@localhost ~]$ psql
psql (13.6)
Type "help" for help.

postgres=# table testtab01;
 id
----
(0 rows)

postgres=# COMMIT PREPARED 'osdba_global_trans_0001';
COMMIT PREPARED
postgres=# table testtab01;
 id
----
  1
(1 row)
相关推荐
哭哭啼9 分钟前
Redis环境部署(主从模式、哨兵模式、集群模式)
数据库·redis·缓存
咕噜Yuki060922 分钟前
OCP证书如何下载?
数据库·ocp·证书查询
冬瓜3121 小时前
linux-c 使用c语言操作sqlite3数据库-1
数据库·sqlite
夜色呦1 小时前
现代电商解决方案:Spring Boot框架实践
数据库·spring boot·后端
WangYaolove13141 小时前
请解释Python中的装饰器是什么?如何使用它们?
linux·数据库·python
我是黄大仙2 小时前
利用飞书多维表格自动发布版本
运维·服务器·数据库·飞书
曾经的三心草2 小时前
Mysql之约束与事件
android·数据库·mysql·事件·约束
WuMingf_2 小时前
redis
数据库·redis
张某布响丸辣2 小时前
SQL中的时间类型:深入解析与应用
java·数据库·sql·mysql·oracle