1.开始与结束事务
(1)开始事务postgres=# \h beginCommand: BEGINDescription: start a transaction blockSyntax:BEGIN [ WORK | TRANSACTION ] [ transaction_mode [, ...] ]where transaction_mode is one of:ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }READ WRITE | READ ONLY[ NOT ] DEFERRABLE(2)结束事务# end与commit效果相同,用于提交完成当前事务;rollback用于停止并回滚当前事务postgres=# \endInvalid command \end. Try \? for help.postgres=# \h endCommand: ENDDescription: commit the current transactionSyntax:END [ WORK | TRANSACTION ]postgres=# \h commit;Command: COMMITDescription: commit the current transactionSyntax:COMMIT [ WORK | TRANSACTION ]postgres=# \h rollbackCommand: ROLLBACKDescription: abort the current transactionSyntax:ROLLBACK [ WORK | TRANSACTION ]
2.保存点
(1)事务处理
postgres=# begin;BEGINpostgres=# select version();version-----------------------------------------------------------------------------------------------------------PostgreSQL 9.6.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-16), 64-bit(1 row)postgres=# commit;COMMITpostgres=# begin;BEGINpostgres=# select version();version-----------------------------------------------------------------------------------------------------------PostgreSQL 9.6.14 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-16), 64-bit(1 row)postgres=# rollback;ROLLBACKpostgres=# begin;BEGINpostgres=# select version;ERROR: column "version" does not existLINE 1: select version;^postgres=# select version();ERROR: current transaction is aborted, commands ignored until end of transaction blockpostgres=# commit;ROLLBACK
3.DDL事务
# 开启事务并创建t2表及插入数据test2=> begin;BEGINtest2=> create table t2(id int);CREATE TABLEtest2=> insert into t2 values (1),(2),(3),(4),(5);INSERT 0 5# 查看数据test2=> select * from t2;id----12345(5 rows)# 执行rollback后再次查看t2表数据,可以看到此时表已经不存在test2=> rollback;ROLLBACKtest2=> select * from t2;ERROR: relation "t2" does not existLINE 1: select * from t2;
4.保存点(SAVEPOINT)
test2=> begin;BEGINtest2=> select * from t1;id----123(3 rows)# 插入新数据并创建savepointtest2=> insert into t1 values(4);INSERT 0 1test2=> savepoint a;SAVEPOINTtest2=> insert into t1 values(5);INSERT 0 1test2=> savepoint b;SAVEPOINT# 故意写错SQL创造错误test2=> insert into t1 values(6;test2(> );ERROR: syntax error at or near ";"LINE 1: insert into t1 values(6;^# 此时如果没有savepoint就只能rollback整个事务,否则无法继续进行下去test2=> insert into t1 values(6);ERROR: current transaction is aborted, commands ignored until end of transaction blocktest2=> rollback to savepoint b;ROLLBACK# rollback到保存点b,继续进行插入数据成功test2=> insert into t1 values(6);INSERT 0 1test2=> select * from t1;id----123456(6 rows)test2=> commit;COMMIT# 注意:rollback到保存点,只能在事务块中实现,也就是说随着事务提交/结束,该事务中的保存点寿命也将被终止test2=> rollback to savepoint b;ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks
5.事务的隔离级别
一般来说,数据库有以下4种隔离级别:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| 读未提交 | Y | Y | Y |
| 读已提交 | N | Y | Y |
| 可重复读 | N | N | Y |
| 串行化 | N | N | N |
注:Y代表可能,N代表不可能
二、MVCC的实现
MVCC的实现方法,下面引用《PostgreSQL修炼之道》来说明:
MVCC的实现方法有两种:
1.写新数据时,把旧数据移到一个单独的地方,如回滚段中,其他人读数据时,从回滚段中把旧的数据读出来;2.写数据时,旧数据不删除,而是把新数据插入。PostgreSQL数据库使用第二种方法,而ORACLE数据库和MySQL中的innodb引擎使用的是第一种方法。与ORACLE数据库和MySQL中的innodb引擎相比较,PostgreSQL的MVCC实现方式的优缺点如下。优点:1.事务回滚可以立即完成,无论事务进行了多少操作;2.数据可以进行很多更新,不必像ORACLE和MySQL的Innodb引擎那样需要经常保证回滚段不会被用完,也不会像oracle数据库那样经常遇到“ORA-1555”错误的困扰;缺点:1.旧版本数据需要清理。PostgreSQL清理旧版本的命令成为Vacuum;2.旧版本的数据会导致查询更慢一些,因为旧版本的数据存在于数据文件中,查询时需要扫描更多的数据块。
关于MVCC实现,下面有个小实验可以侧面证明
# 创建表及测试数据,test2=> create table t2 (id int);CREATE TABLEtest2=> insert into t2 select * from generate_series(1,10000);INSERT 0 10000# 查看表大小test2=> select pg_size_pretty(pg_relation_size('t2'));pg_size_pretty----------------360 kB(1 row)# 修改全表数据,再次查看大小test2=> update t2 set id=id *10;UPDATE 10000test2=> select pg_size_pretty(pg_relation_size('t2'));pg_size_pretty----------------712 kB(1 row)# 可以看到update这种更新/修改数据的操作,在PostgreSQL中是不清除旧数据,而是复制行数据进行修改实现的。
三、锁
在PostgreSQL中,存在4种锁机制:表级锁,行级锁,页级锁和advisory锁。
1.表级锁
PostgreSQL中一共提供了8种表级锁,它们之间的冲突关系如下图。

下面是这8种锁模式的简单说明:
(1)ACCESS SHARE:select命令可在查询中引用的表上加这个类型的锁。一般情况下,是所有的操作中只有读表操作才加上这个类型的锁。
(2)ROW SHARE:select for update/select for share命令会使得目标表上获得该锁。
(3)ROW EXCLUSIVE:update/insert/delete命令会使被修改的目标表加上这类型的锁,一般来说,表数据修改都是使用这个类型的锁。
(4)SHARE UPDATE EXCLUSIVE:CREATE INDEX CONCURRENTLY、ANALYZE、VACUUM(不含FULL)和VALIDATE等这些命令获得此类锁。
(5)SHARE:CREATE INDEX命令可以在表上获得该锁。
(6)SHARE ROW EXCLUSIVE:不能被任何PostgreSQL命令自动获取。
(7)EXCLUSIVE:不能被任何PostgreSQL命令自动获取。
(8)ACCESS EXCLUSIVE:ALTER TABLE、DROP TABLE、TRUNCATE、REINDEX、CLUSTER和VACUUM FULL命令在获得该锁。它是lock table命令的默认锁模式。
2.行级锁
FOR KEY SHARE | FOR SHARE | FOR NO KEY UPDATE | FOR UPDATE | |
FOR KEY SHARE | ✔ | ✔ | ✔ | ✖ |
| FOR SHARE | ✔ | ✔ | ✖ | ✖ |
FOR NO KEY UPDATE | ✔ | ✖ | ✖ | ✖ |
| FOR UPDATE | ✖ | ✖ | ✖ | ✖ |
(1)FOR UPDATE:该操作将导致SELECT语句检索的行被锁定。这可以防止它们被其他事务锁定,修改或删除,直到当前事务结束。
(2)FOR NO KEY UPDATE:该锁行为类似于FOR UPDATE,只是获得的锁比较弱:
(3)FOR SHARE:该锁行为类似于FOR NO KEY UPDATE,只是它在每个行上获得一个共享锁,而不是排它锁。
(4)FOR KEY SHARE:该锁行为类似于FOR SHARE,只是获得的锁比较弱。
3.页级锁
4.advisory锁
| 时刻 | 会话1 | 会话2 |
| 1 | begin; | |
| 1 | select pg_advisory_lock(6); | begin; |
| 2 | select pg_advisory_lock(6); | |
| 3 | 其他操作 | 等待 |
| 4 | commit; | 等待 |
| 5 | select pg_advisory_unlock(6); | 等待 |
| 6 | 获得锁 |
5.查看锁
与ORACLE和MySQL一样,PostgreSQL数据库也提供了查看锁的视图,那就是:pg_locks
# 会话1:test2=> select pg_backend_pid();pg_backend_pid----------------2447(1 row)test2=> begin;BEGINtest2=> update t1 set id=id+1 where id=6;UPDATE 1# 会话2:test2=> select pg_backend_pid();pg_backend_pid----------------4434(1 row)test2=> begin;BEGINtest2=> update t1 set id=id+1 where id=6;--此时update处于等待状态# 会话3:test2=# select locktype, relation::regclass as rel,page||','||tuple as ctid,virtualxid as vxid,transactionid as xid,virtualtransaction as vxid2, pid,mode,granted from pg_locks where pid in (2447,4434) order by pid;locktype | rel | ctid | vxid | xid | vxid2 | pid | mode | granted---------------+-----+------+--------+------+--------+------+------------------+---------relation | t1 | | | | 3/1414 | 2447 | RowExclusiveLock | tvirtualxid | | | 3/1414 | | 3/1414 | 2447 | ExclusiveLock | ttransactionid | | | | 1841 | 3/1414 | 2447 | ExclusiveLock | tvirtualxid | | | 2/4141 | | 2/4141 | 4434 | ExclusiveLock | ttransactionid | | | | 1841 | 2/4141 | 4434 | ShareLock | ftransactionid | | | | 1842 | 2/4141 | 4434 | ExclusiveLock | ttuple | t1 | 0,10 | | | 2/4141 | 4434 | ExclusiveLock | trelation | t1 | | | | 2/4141 | 4434 | RowExclusiveLock | t(8 rows)# 定位锁test2=# select * from t1 where ctid='(0,10)';id----6(1 row)locktype:锁对象的类型,如relation(表),tuple(行数据),virtualxid,transactionid等rel:对象为表则显式表名,否则为空ctid:表的ctid字段mode:锁模式名称granted:持有锁为“t”(ture),否则w诶“f”(false)进程2447即会话1获取了t1表上的RowExclusive Lock进程4434即会话2实际上此时是被阻塞的状态,这是因为行锁会在数据行上加上自己的xid,当另一个会话读到这一行时,如果发现其上有行锁,此时会在该事务的xid上申请一个share锁,而由于此时上面已经有exclusive,故会话被阻塞。




