一、什么是 ACID¶
- 原子性
- 一致性
- 隔离性
- 持久性
二、DDL 事务¶
在PostgreSQL中,与其他数据库最大的不同是,大多数DDL也是可以包含在一个事务中的,而且也是可以回 滚的
适合场景:PostgreSQL作为Sharding的分布式数据系统的底层数据库
原因:Sharding中,常常需要在多个节点中建相同的表,此时可以考虑把建表语句放在同一个事务中,这样就 可以在各个节点上先启动一个事务,然后再执行建表语句,保证整个集群的一致性
三、事务的使用方法¶
在psql的默认配置下,自动提交功能“AUTOCOMMIT”是打开 可以通过设置psql中的内置变量“AUTOCOMMIT”来关闭自动提交功能: 比如:
\set AUTOCOMMIT off;
begin;
四、SAVEPOINT¶
PostgreSQL支持保存点(SAVEPOINT)的功能,在一个大的事务中,可以把操作过程分成几个部分,第一个 部分执行成功后可以建一个保存点,若后面的部分执行失败,则回滚到此保存点,而不必回滚整个事务
案例:
-- 开启事务
begin;
insert into testtab01 values(1);
insert into testtab01 values(2);
-- 创建保存点
savepoint my_savepoint01;
-- 这两条插入会导致主键/唯一约束报错
insert into testtab02 values(1);
insert into testtab02 values(1);
-- 报错后,回滚到保存点(不会影响 testtab01 的插入)
rollback to SAVEPOINT my_savepoint01;
-- 此时 testtab02 无数据,可继续执行其他操作
select * from testtab02;
-- 最后提交/回滚整个事务
-- commit;
-- rollback;
五、事务隔离级别¶
数据库的事务隔离级别有以下4种。
- READ UNCOMMITTED:读未提交。
- READ COMMITTED:读已提交。
- REPEATABLE READ:重复读。
- SERIALIZABLE:串行化。
对于并发事务,我们不希望发生不一致的情况,这类情况的级别从高到低排序如下
- 脏读
- 不可重复读
- 幻读
postgresql中只支持三种隔离级别:
- 读已提交
- 可重复读
- 串行化
需要说明:在PostgreSQL的一 个事务中不可能读到其他事务中未提交的数据。在选择可重复读级别的时候,实际上仍是可串行化,所以实际 的隔离级别可能比你选择的更加严格。
六、两阶段提交¶
多台数据库之间的原子性就需要通过两阶段提交来实现了,两阶段提交是实现分布式事务的关键 PostgreSQL数据库支持两阶段提交协议
两阶段提交协议有如下5个步骤。

1、应用程序调用事务协调器
2、通知准备提交事务(PostgreSQL中一般是调用PREPARE TRANSACTION命令)
3、接收到PREPARE TRANSACTION命令,保证将自己置于准备提交中的状态
4、事务协调器接收所有数据库的响应
5、如果任何一个数据库在第一阶段返回失败,则事务协调器将会发一个回滚命令“ROLLBACK PREPARED”给 各台数据库
6.1 PostgreSQL 持久化事务案例¶
前提:max_prepared_transactions”设置成一 个大于0的数字,不然会报错
set max_prepared_transactions = 10; 报错
postgresql.conf文件中的“max_prepared_transactions”为“10”
create table testtab01(id int primary key);
#启动事务
begin;
insert into testtab01 values(1);
PREPARE TRANSACTION 'global_trans_1'; #全局事务的ID
pg_ctl stop -D $PGDATA
pg_ctl start -D $PGDATA
#连接数据库提交两阶段事务
COMMIT PREPARED 'global_trans_1';
查询数据
select * from testtab01;
七、postgresql 锁机制¶
7.1 表级锁¶
表级锁只有 SHARE 和 EXCLUSIVE 这两种,表级锁共有八种模式,其存在于 PG 的共享内存中,可以通过 pg_locks 系统视图查询。
| 锁模式 | 解释 |
|---|---|
| ACCESS SHARE 访问共享 | SELECT 命令在被引用的表上会获得一个这种模式的锁。通常,任何只读取表而不修改它的查询都将获取这种表模式。 |
| ROW SHARE 行共享 | SELECT FOR UPDATE 和 SELECT FOR SHARE 命令在目标表上会获得一个这种模式的锁。(加上在被引用但没有选择 FOR UPDATE / FOR SHARE 的任何其他表上的 ACCESS SHARE 锁。) |
| ROW EXCLUSIVE 行独占 | UPDATE、DELETE 和 INSERT 命令在目标表上会获得一个这种模式的锁。 |
| SHARE UPDATE EXCLUSIVE | ALTER INDEX 和 ALTER TABLE 命令的变体会获得。这种模式保护一个表不受并发模式改变。 |
| SHARE 共享 | CREATE INDEX(不带 CONCURRENTLY)命令会获得。这种模式保护一个表不受并发数据改变的影响。 |
| SHARE ROW EXCLUSIVE 共享行独占 | CREATE TRIGGER 命令和某些形式的 ALTER TABLE 命令会获得。 |
| EXCLUSIVE 排他 | REFRESH METERIALIZED VIEW CONCURRENTLY 命令会获得。 |
7.2 表锁特点¶
锁粒度非常大
通过类似于mysql意向锁的方式,PostgreSQL中也是这样实现的,如ROWSHARE、ROW EXCLUSIVE这两个 锁
7.3 行级锁¶
行级锁模式比较简单,只有两种,即“共享锁”“排它锁”, 在PostgreSQL中由于有多版本的实现,所以实际读取行数据时,并不会在行上执行任何锁
7.4 表级锁命令 LOCK TABLE¶
LOCK [ TABLE ] [ ONLY ] name [, ...] [ IN lockmode MODE ] [ NOWAIT ]
--NOWAIT:表示不等待
7.5 行级锁命令¶
SELECT ..... FOR { UPDATE | SHARE } [ OF table_name [, ...] ] [ NOWAIT ] [...] ]
此命令中的NOWAIT关键字与在LOCK TABLE中是相同的,加了NOWAIT关键字后,如果无法获得锁则直接报 错,而不会一直等待
7.6 锁的查看¶
pg_locks 可以查询到当前锁的信息 比如:哪个事务被哪个事务阻塞了,若执行一条SQL语句时阻塞住了,需 要查询为什么阻塞,是谁阻塞住的

7.6.1 表锁查询案例¶
session1
-- 1. 创建测试表
create table testtab02(id int primary key);
insert into testtab02 values(1);
-- 2. 开启事务并锁表
begin;
lock table testtab02;
-- 3. 查询数据库锁信息
select
locktype,
relation::regclass as rel,
virtualxid as vxid,
transactionid as xid,
virtualtransaction as vxid2,
pid,
mode,
granted
from pg_locks;
结果解释:
其中一行显示的是事务在自己的“virtualxid”上 加的ExclusiveLock锁,这是必定会加上的。 另一行才是我们实际在表上加的锁“AccessExclusiveLock”
session2
begin;
lock table testtab02;
查询锁
select locktype, relation::regclass as rel, virtualxid as vxid, transactionid as xid ,
virtualtransaction as vxid2, pid, mode, granted from pg_locks;
7.6.2 行锁查询案例¶
session1
-- 窗口1:普通查询(无锁)
select * from testtab02 where id=1;
-- 窗口2:开启事务 + 行锁
begin;
select * from testtab02 where id=1 for update;
-- 窗口3:查看锁信息
select
locktype,
relation::regclass as rel,
virtualxid as vxid,
transactionid as xid,
virtualtransaction as vxid2,
pid,
mode,
granted
from pg_locks;
问题1:
为什么没有发现行锁?
答案:
实际上pg_locks并不能显示出每个行锁的信息,原因也很简单,行锁信息并不会记录到共享内存中
问题2
在pg_locks中没有行锁信息,如何知道一个进程被另一个进程的行锁阻塞了呢?
session2
begin;
select * from testtab02 where id=1 for update;
想查看哪个进程被阻塞住了,只需要查看“granted”字段值为“False”的PID
查看session的 bid
select pg_backend_pid();