论Postgres的“已提交的而且 xmin’比当前事务的XID小的记录对当前事务才是可见的”

简介: PostgreSQL

1.阐述

最近在网上看到这样一句话Postgres“ 已提交的而且 xmin 比当前事务的XID小的记录对当前事务才是可见的”。先不评断这句话的正确性;看下这句话的结构,因果关系;
按照此话的意思;要postgres中的数据可见必须满足两个必要条件:
  1. 事务已经提交(commit);
  2. 提交时插入记录的xmin 小于 当前current_txid(事务id)。
而网上对这句话的解释:“这意味着,你可以开始一个新事务然后插入一行记录,直到你提交(COMMIT)之前,你插入的这行记录对其他事务永远都是不可见的。等到提交以后,其他后创建的新事务就可以看到这行新记录了,因为他们满足了 xmin < XID 条件,而且创建哪一行记录的事务也已经完成”。看起来挺合理的,无懈可击似的。接下来我们来推敲推敲。要说记录的可见性;这还得从事务的隔离级别说起。

2. 举例验证

根据《PostgreSQL9.4.4-CN-v1.0.pdf》文档介绍: SQL标准定义了四个级别的事务隔离 { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }; postgres目前只实现了 {SERIALIZABLE | REPEATABLE READ | READ COMMITTED }这三种。详细大家去看文档;这里不做介绍。
  • 事务隔离级别READ COMMITTED
事务隔离级别:读已提交(READ COMMITTED)这是postgres,greenplum默认的事务隔离级别。若从先事务隔离级别(读已提交)来解释:就是读已经提交的记录;是不是这样呢? 来验证下。
--session A  事务id为1844;
postgres=# begin;
BEGIN
postgres=# select txid_current();
 txid_current 
--------------
         1844
         
--session B 事务id为1845;并在插入一条记录在lottu05表(未提交)
postgres=# begin;
BEGIN
postgres=# select txid_current();
 txid_current 
--------------
         1845

postgres=# insert into lottu05 values (1001,'lottu');
INSERT 0 1

--在session A/B查看记录; session A读不到记录; session B可以读到记录。
postgres=# select * from lottu05;
 id | name 
----+------
(0 rows)

--在session B提交插入的记录;在查看session A是否可以看到记录。
postgres=# select xmin,* from lottu05;
 xmin |  id  | name  
------+------+-------
 1845 | 1001 | lottu
--表明session A(当前事务为ID:1844)可以读 插入记录事务id为1845 已经提交的记录。
--总结: 事务隔离级别为读已提交(READ COMMITTED)就是读已经提交的记录。
由此可见,对读已提交隔离级别而言"已提交的而且 xmin’比当前事务的XID小的记录对当前事务才是可见的"是不正确的。
而网上的解释:也是必要不充分条件。那该如何诠释这说话呢?请看下文讲解
是根据当前postgres系统的当前事务ID相比;目前系统下一个事务ID为1846
-- 我们现在看下当前postgres系统 下一个事务id
[postgres@localhost ~]$ pg_controldata |grep  NextXID
Latest checkpoint's NextXID:          0/1846
--意思是说这条记录后面开启会话从事务id:1846是可见的。不充分的是事务ID:1844也可以读到该记录。
--然而这句话来源何处;我想是有依据的。接下来我们做一个实验。模拟postgrs穿越到过去。 
                        
--session C 现在插入1002-1008条记录;结果如下:
postgres=# select xmin,id,name from lottu05;
 xmin |  id  |  name   
------+------+---------
 1845 | 1001 | lottu
 1846 | 1002 | lottu02
 1847 | 1003 | lottu03
 1848 | 1004 | lottu04
 1849 | 1005 | lottu05
 1850 | 1006 | lottu06
 1851 | 1007 | lottu07
 1852 | 1008 | lottu08   

--我们现在使用将数据库postgres回到 txid 为1849。注意:该动作不建议操作;
[postgres@localhost ~]$ pg_stop
waiting for server to shut down.......... done
server stopped
[postgres@localhost ~]$ pg_resetxlog -x 1849 $PGDATA
Transaction log reset
[postgres@localhost ~]$ pg_start
server starting
[postgres@localhost ~]$ psql
psql (9.5.0)
Type "help" for help.

postgres=# select xmin,id,name from lottu05;
 xmin |  id  |  name   
------+------+---------
 1845 | 1001 | lottu
 1846 | 1002 | lottu02
 1847 | 1003 | lottu03
 1848 | 1004 | lottu04
 1849 | 1005 | lottu05
--可以看到上面的xmin:(1850-1852)是不可见的。
--等数据库的事务ID超过1852;这些数据可以展示出来。
postgres=# select txid_current();
 txid_current 
--------------
         1850

postgres=# select txid_current();
 txid_current 
--------------
         1851

postgres=# select txid_current();
 txid_current 
--------------
         1852

postgres=# select xmin,id,name from lottu05;
 xmin |  id  |  name   
------+------+---------
 1845 | 1001 | lottu
 1846 | 1002 | lottu02
 1847 | 1003 | lottu03
 1848 | 1004 | lottu04
 1849 | 1005 | lottu05
 1850 | 1006 | lottu06
 1851 | 1007 | lottu07
 1852 | 1008 | lottu08 
 
--从这个实验看来 确实是需要满足网上所说的两个条件。上面也提过;该操作不建议操作。设想;当前时代若可以穿越到历史上各个时代;那历史不乱套了吗?同理如此。
所以说对隔离级别为READ COMMITTED而言;如同它字面解释一样;只要记录COMMITTED;就可以读到。
 注意:
--1.该操作不等同 oracle的flashback操作;虽然回到了历史;历史上已经发生的还是会发生。
--2.该操作并不能做数据恢复操作。若对数据做删除进行恢复;可以参考--http://www.cnblogs.com/lottu/p/5761885.html  

总结:对隔离级别为READ COMMITTED而言;如同它字面解释一样;只要记录COMMITTED;就可以读到
  • 事务隔离级别:REPEATABLE READ
事务隔离级别:REPEATABLE READ;是不是如同它而言呢?接下来拭目以待吧。
--开启SESSION A; ctid为1857。
postgres=# truncate table lottu05;
TRUNCATE TABLE
postgres=# begin;
BEGIN
postgres=# select txid_current();
 txid_current 
--------------
         1857

--开启session B;隔离级别为REPEATABLE READ。事务id为:1858
postgres=# begin ISOLATION LEVEL REPEATABLE READ;
BEGIN
postgres=# select txid_current();
 txid_current 
--------------
         1858  

--在session A插入 10条记录并提交
postgres=# insert into lottu05 select generate_series(1001,1010),'lottu'||generate_series(1,10);
INSERT 0 10
postgres=# commit;
COMMIT

--在session B查看是否可以读到记录
postgres=# select * from lottu05;
 id | name 
----+------
(0 rows)       

--结果表明session B 读不到 已经提交且 事务ID:1857比session B的事务ID为1858要小的记录。

3.总结

对Postgres记录的可见性;对网上这句话“ 已提交的而且 xmin 比当前事务的XID小的记录对当前事务才是可见的”可以修正为 “已提交的而且 xmin 比当前系统事务的XID小或者等于的记录对当前事务才是可见的”。而对它的解释(这意味着,你可以开始一个新事务然后插入一行记录,直到你提交(COMMIT)之前,你插入的这行记录对其他事务永远都是不可见的。等到提交以后,其他后创建的新事务就可以看到这行新记录了,因为他们满足了 xmin < XID 条件,而且创建哪一行记录的事务也已经完成”)是充分不必要;
--参考文献


目录
相关文章
|
6月前
|
SQL 关系型数据库 MySQL
MySQL数据库——事务操作-begin-commit-rollback
MySQL数据库——事务操作-begin-commit-rollback
62 1
|
存储 SQL 关系型数据库
【Mysql-InnoDB 系列】事务提交过程
MySQL InnoDB的事务模型、锁机制等设计,都是InnoDB架构设计的一部分。要全面了解它的设计实践,就必须从头看起。只有这样,才能够弄清楚它的设计思想,理解其实现上的精妙之处。
599 1
|
安全 关系型数据库 MySQL
MySQL事务隔离级别:读未提交、读已提交、可重复读和串行
MySQL事务隔离级别:读未提交、读已提交、可重复读和串行
455 0
|
SQL 关系型数据库 MySQL
MySQL 下事务的开启、提交、回滚语句应用|学习笔记
快速学习 MySQL 下事务的开启、提交、回滚语句应用
MySQL 下事务的开启、提交、回滚语句应用|学习笔记
|
SQL 运维 数据库
事务手动提交和XA事务问题及思考
事务手动提交和XA事务问题及思考
224 0
|
MySQL 关系型数据库
MySQL:begin后事务为什么不提交
今天顺便看了一下,主要流程就是跟踪为什么begin后事物不会提交,最后发现在:MYSQL_BIN_LOG::commit 函数中包含这个判断 if (!cache_mngr->trx_cache.is_binlog_empty() && ending_trans(thd, all) && !trx_stuff_logged) 如果begin的话ending_trans(thd, all) 将会返回为false,也就不会调用 order_commit流程了。
4273 0
|
SQL 关系型数据库 MySQL
MySQL:Innodb 关于Handler_commit每次DML增加2的原因
简单描述一下,也是本人的问的,水平有限,如果有误请谅解。 原问题如下@mysqDBA: 请教一个问题。我每次insert一条语句,查询show global status like 'Handler_commit'; 发现每次增加值是2,难道不应该是1吗? 最简单的insert into tab.
2147 0