ITPub博客

首页 > 数据库 > PostgreSQL > PostgreSQL DBA(23) - MVCC#3(事务快照和隔离级别)

PostgreSQL DBA(23) - MVCC#3(事务快照和隔离级别)

原创 PostgreSQL 作者:husthxd 时间:2019-01-09 12:21:00 0 删除 编辑

如前所述,PostgreSQL使用一种称为快照隔离Snapshot Isolation (SI)实现并发控制,下面简单介绍PostgreSQL中与之相关的快照和隔离级别等基本概念。
注意:
1.简单起见,本节暂不讨论事务回滚的情况.
2.如无说明,默认的事务隔离级别为READ COMMITTED

一、事务快照

事务快照(简称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

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/6906/viewspace-2375191/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
长期从事政务、金融等行业产品研发和架构设计工作,对Oracle、PostgreSQL以及大数据等相关技术有深入研究。现就职于广州云图数据技术有限公司,系统架构师。

注册时间:2007-12-28

  • 博文量
    1146
  • 访问量
    3619641