如前所述,PostgreSQL使用一种称为快照隔离Snapshot Isolation (SI)实现并发控制,下面简单介绍PostgreSQL中与之相关的快照和隔离级别等基本概念。
注意:
1.简单起见,本节暂不讨论事务回滚的情况.
2.如无说明,默认的事务隔离级别为READ COMMITTED
10年积累的成都网站建设、网站设计经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先网站制作后付款的网站建设流程,更有深圳免费网站建设让你可以放心的选择与我们合作。
事务快照(简称Snapshot)存储所有事务在某个时间点是否处于活动状态(正在进行或尚未启动)的相关信息.
通过txid_current_snapshot()函数可以获取当前事务的快照信息.
testdb=# select txid_current_snapshot();
txid_current_snapshot
-----------------------
2315:2315:
(1 row)
快照的格式如下为xmin : xmax : xip_list
其中:
xmin : 最早仍活跃的事务txid,早于此txid的事务要么被提交并可见,要么回滚并废弃。
xmax :
ShmemVariableCache->latestCompletedXid + 1,即最后已完结事务(COMMITTED/ABORTED)的txid + 1
。在”拍摄”快照时,所有大于或等于此txid的事务尚未启动,因此不可见。
xip_list : 在”拍摄”快照时处于活动状态的事务txid。该列表包含xmin和xmax之间的活动txid。
总结一下,简单来说,对于给定的事务txid:
txid ∈ [2,xmin),是过去的事务,对此快照均可见;
txid ∈ [xmin,xmax),txid仍处于IN_PROGRESS状态,则不可见;COMMITED状态,则可见;ABORTED的事务,亦不可见;
txid ∈ [xmax,∞),是未来的事务,对此快照均不可见;
启动三个客户端(psql),执行begin启动事务:
session 1
11:36:04 (xdb@[local]:5432)testdb=# begin;
BEGIN
11:36:09 (xdb@[local]:5432)testdb=#* select 1;
?column?
----------
1
(1 row)
11:36:09 (xdb@[local]:5432)testdb=#* select txid_current();
txid_current
--------------
2327
(1 row)
11:36:09 (xdb@[local]:5432)testdb=#* select txid_current_snapshot();
txid_current_snapshot
-----------------------
2327:2327:
(1 row)
session 2
11:36:02 (xdb@[local]:5432)testdb=# begin;
BEGIN
11:36:17 (xdb@[local]:5432)testdb=#* select 1;
?column?
----------
1
(1 row)
11:36:17 (xdb@[local]:5432)testdb=#* select txid_current();
txid_current
--------------
2328
(1 row)
11:36:17 (xdb@[local]:5432)testdb=#* select txid_current_snapshot();
txid_current_snapshot
-----------------------
2327:2327:
(1 row)
session 3
11:35:59 (xdb@[local]:5432)testdb=# begin;
BEGIN
11:36:18 (xdb@[local]:5432)testdb=#* select 1;
?column?
----------
1
(1 row)
11:36:18 (xdb@[local]:5432)testdb=#* select txid_current();
txid_current
--------------
2329
(1 row)
11:36:18 (xdb@[local]:5432)testdb=#* select txid_current_snapshot();
txid_current_snapshot
-----------------------
2327:2327:
(1 row)
session 1/2/3的txid分别是2327/2328/2329,快照均为2327:2327:,即小于2327的事务可见,大于等于2327的不可见.
session 2创建数据表,插入数据,提交,重新开启事务,插入数据.
11:36:17 (xdb@[local]:5432)testdb=#*
11:37:24 (xdb@[local]:5432)testdb=#* create table t_session2 (id int);
CREATE TABLE
11:37:28 (xdb@[local]:5432)testdb=#* insert into t_session2 values(1);
INSERT 0 1
11:37:28 (xdb@[local]:5432)testdb=#* commit;
COMMIT
11:37:28 (xdb@[local]:5432)testdb=# begin;
BEGIN
11:38:25 (xdb@[local]:5432)testdb=#* insert into t_session2 values(2);
INSERT 0 1
11:38:31 (xdb@[local]:5432)testdb=#*
查看session 1/2/3的快照信息变化.
session 1
11:38:03 (xdb@[local]:5432)testdb=#* select txid_current_snapshot();
txid_current_snapshot
-----------------------
2327:2329:
(1 row)
session 1的快照信息变为2327:2329:,即小于2327的事务可见,大于等于2329的事务不可见.
session 2
11:39:02 (xdb@[local]:5432)testdb=#* select txid_current();
txid_current
--------------
2330
(1 row)
11:39:14 (xdb@[local]:5432)testdb=#* select txid_current_snapshot();
txid_current_snapshot
-----------------------
2327:2329:2327
(1 row)
session 2的快照信息变为2327:2329:2327,即小于2327的事务可见,大于等于2329的事务不可见,2327事务进行中,亦不可见.
session 3
11:36:18 (xdb@[local]:5432)testdb=#* select txid_current_snapshot();
txid_current_snapshot
-----------------------
2327:2329:2327
(1 row)
11:39:57 (xdb@[local]:5432)testdb=#*
session 3的快照信息与session 2一致.
session 1创建数据表并插入数据
11:40:36 (xdb@[local]:5432)testdb=#* create table t_session1 (id int);
CREATE TABLE
11:45:10 (xdb@[local]:5432)testdb=#* insert into t_session1 values(1);
INSERT 0 1
11:45:19 (xdb@[local]:5432)testdb=#*
session 2查询数据表t_session1
11:39:17 (xdb@[local]:5432)testdb=#* select * from t_session1;
ERROR: relation "t_session1" does not exist
LINE 1: select * from t_session1;
11:49:58 (xdb@[local]:5432)testdb=#! select txid_current_snapshot(); --> 一旦出错,就要回滚事务了
ERROR: current transaction is aborted, commands ignored until end of transaction block
11:50:28 (xdb@[local]:5432)testdb=#! commit; --> 虽然是commit,按提示信息,其实是rollback.
ROLLBACK
报错,提示关系不存在.
注意:事务处理过程中,如出错,Oracle仍可提交先前成功执行的操作,但PG要求回滚事务
在上述案例中,在同一个事务中,执行新的SQL时事务快照是会变化的,这是因为默认的隔离级别READ COMMITTED的机制使然,如隔离级别为REPEATABLE READ则事务快照只会获取一次并记录获取快照时事务的状态.
在PostgreSQL中,有四种隔离级别,分别是READ UNCOMMITTED/READ COMMITTED/REPEATABLE READ/SERIALIZABLE.
其中READ UNCOMMITTED与READ COMMITTED一致,没有区别;SERIALIZABLE级别较少使用,这里不作叙述.
READ COMMITTED
沿用第1小节的session 1/2.
session 2重新启动事务
12:06:17 (xdb@[local]:5432)testdb=#* select txid_current();
txid_current
--------------
2331
(1 row)
12:06:23 (xdb@[local]:5432)testdb=#* select txid_current_snapshot();
txid_current_snapshot
-----------------------
2327:2331:2327,2329
(1 row)
session 1提交事务
11:40:36 (xdb@[local]:5432)testdb=#* create table t_session1 (id int);
CREATE TABLE
11:45:10 (xdb@[local]:5432)testdb=#* insert into t_session1 values(1);
INSERT 0 1
11:45:19 (xdb@[local]:5432)testdb=#* commit;
COMMIT
12:07:21 (xdb@[local]:5432)testdb=#
这时候session 2可读取session 1中的t_session1数据表
12:08:21 (xdb@[local]:5432)testdb=#* select * from t_session1;
id
----
1
(1 row)
REPEATABLE READ
session 1,隔离级别为READ COMMITTED
12:12:14 (xdb@[local]:5432)testdb=# START TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION
session 2,隔离级别为REPEATABLE READ
12:12:41 (xdb@[local]:5432)testdb=# START TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION
12:12:43 (xdb@[local]:5432)testdb=#* select * from t_session1;
id
----
1
(1 row)
仍使用上述场景,session 1插入数据,提交事务,这时候新插入的数据对session 2仍不可见.
session 1
12:12:18 (xdb@[local]:5432)testdb=#* insert into t_session1 values(2);
INSERT 0 1
12:13:22 (xdb@[local]:5432)testdb=#* commit;
COMMIT
12:13:25 (xdb@[local]:5432)testdb=#
session 2
12:13:38 (xdb@[local]:5432)testdb=#* select * from t_session1;
id
----
1 --> 新插入的数据2不可见
(1 row)
12:13:58 (xdb@[local]:5432)testdb=#* select txid_current_snapshot();
txid_current_snapshot
-----------------------
2329:2332:2329
(1 row)
session 2的事务快照只会获取一次.
获取事务快照的逻辑以及如何确定heap tuple是否可见的逻辑后续在源码解读中将作具体解析.
Concurrency Control
打错字,PG也要求回滚事务?
12:06:25 (xdb@[local]:5432)testdb=#* selet * from t_session1;
ERROR: syntax error at or near "selet"
LINE 1: selet * from t_session1;
^
12:07:56 (xdb@[local]:5432)testdb=#! select * from t_session1;
ERROR: current transaction is aborted, commands ignored until end of transaction block
12:08:02 (xdb@[local]:5432)testdb=#! commit;
ROLLBACK