[PGSQL]PostgreSQL数据库中的两阶段事务提交

PostgreSQL数据库支持两阶段提交协议。

在分布式系统中,事务往往包含有多台数据库上的操作,单台数据库的操作是能够保证原子性的,而多台数据库之间的原子性的保证则需要通过两阶段提交来实现,两阶段提交是实现分布式事务的关键。

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

  1. 应用程序先调用各台机数据库做一些操作,但不提交事务。然后应用程序调用事务协调器(这个协调器可能也是由应用自己实现)中的提交方法。
  2. 事务协调器将联络事务中涉及的每台数据库,并通知它们准备提交事务,这是第一阶段的开始。在PostgreSQL一般是调用“PREPARE TRANSACTION”命令。
  3. 各台数据库接收到“PREPARE TRANSACTION”命令后,如果要返回成功,则数据库必须将自己置于以下状态:确保能在被要求提交事务时提交事务,或在被要求回滚事务时回滚事务。所以PostgreSQL会将已准备好提交的信息写入持久存储区中。如果数据库无法完成此事务,它会直接返回失败给事务协调器。
  4. 事务协调器接收到了所有数据库的响应。
  5. 在第二阶段,如果任一数据库在第一阶段返回失败,则事务协调器会将发一个回滚命令(ROLLBACK PREPARED)给各台数据库。如果所有数据库的响应都是成功的,则向各台数据库发送“COMMIT PREPARED”命令,通知各台数据库事务成功。

下面用实际来演示两阶段提交。 演示前,需要把参数“max_prepared_transactions”设置成一个大于零的数字,以便我们能使用两阶段提交的这个功能,如果没有设置这个参数,会显示如下错误:

ERROR:  prepared transactions are disabled
HINT:  Set max_prepared_transactions to a nonzero value.

注意设置这个参数需要重启数据库,直接设置这个参数是不能成功的:

postgres=# set max_prepared_transactions = 10;
ERROR:  parameter "max_prepared_transactions" cannot be changed without restarting the server

我们修改postgresql.conf文件中的max_prepared_transactions为10:

max_prepared_transactions = 10

我们建一张表做测试:

postgres=# create table testtab01(id int primary key);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "testtab01_pkey" for table "testtab01"
CREATE TABLE

我们启动一个事务,插入一条记录:

postgres=# begin;
BEGIN
postgres=# insert into testtab01 values(1);
INSERT 0 1

然后使用“PREPARE TRANSACTION”命令准备好一事务提交(第一阶段):

postgres=# PREPARE TRANSACTION 'osdba_global_trans_0001';
PREPARE TRANSACTION

命令中“osdba_global_trans_0001”是两阶段提交中全局事务的ID,由事务协调器生成(事务协调器也可能是应用来实现的,事务协调器会持久化这个全局事务ID。PostgreSQL数据库一旦成功执行这条命令,也会把此事务持久化。此事务持久化的意思就是即使数据库重启,此事务也不会回滚,也不会丢失)。 我们停止数据库,然后再启动:

osdba@osdba-laptop:~$ pg_ctl stop -D $PGDATA
waiting for server to shut down.... done
server stopped
osdba@osdba-laptop:~$ pg_ctl start -D $PGDATA
server starting

重启完数据库后,可以使用“COMMIT PREPARED”真正提交这个事务提交(第二阶段):

osdba@osdba-laptop:~/pgdata$ psql postgres
psql (9.2.4)
Type "help" for help.
postgres=# COMMIT PREPARED  'osdba_global_trans_0001';
COMMIT PREPARED

然后可以看以前面插入的数据:

postgres=# select * from testtab01;
 id 
----
  1
(1 row)

从上面的例子可以看出,一旦我们成功执行完“PREPARE TRANSACTION”命令后,事务就被持久化了,即使我们重启完数据库,然后可以提交这个事务,然后事务中的操作不会丢失。