首页 » MySQL/TiDB/GoldenDB, ORACLE 9i-23ai, PostgreSQL/GaussDB » multi-version read consistency in Oracle、MySQL、PostGreSQL(数据库比较系列十二)

multi-version read consistency in Oracle、MySQL、PostGreSQL(数据库比较系列十二)

在多人同时访问与修改数据时, 最大的难题之一是:一方面要力争最大的并发访问,与此同时还要确保每个用户能以一致的方式读取和修改数据。ANSI/ISO SQL 标准定义了4 种事务隔离级别,对于相同的事务,采用不同的隔离级别分别有不同的结果。这些隔离级别是根据3 个“现象”定义的,如dirty read、nonrepeatable read、phantom read。Oracle 明确地支持READ COMMITTED(读已提交)和SERIALIZABLE(可串行化)隔离级别,在Oracle 中READ COMMITTED 则有得到读一致查询所需的所有属性,在其他数据库中的读READ COMMITTED 可能会有不同的答案, 最近有个客户在测试migrate oracle to postgreSQL测试发现一个批处理的结果并非一致,于是做一个小小的测试验证一下。

# ORACLE

SQL> CREATE TABLE test (id INT PRIMARY KEY);
Table created.

SQL> INSERT INTO test VALUES (1);
1 row created.

SQL> INSERT INTO test VALUES (2);
1 row created.

SQL> alter table test add ctime date;
Table altered.

SQL> select * from test;
        ID CTIME
---------- -------------------
         1
         2

SQL> update test set ctime=sysdate;
2 rows updated.

## session 1

SQL> set time on
12:22:03 SQL>
12:22:03 SQL>
12:22:03 SQL>
begin
DELETE FROM test WHERE id=1;
INSERT INTO test VALUES (1,sysdate);
end;
/
PL/SQL procedure successfully completed.

12:22:06 SQL> select * from test;
        ID CTIME
---------- -------------------
         2 2022-08-19 12:21:31
         1 2022-08-19 12:22:06

## session 2
SQL> set time on
12:21:47 SQL>
12:22:10 SQL>
begin
DELETE FROM test WHERE id=1;
INSERT INTO test VALUES (1,sysdate);
12:22:11   4  end;
12:22:12   5  /

--   hang
## session 1 
12:22:24 SQL> commit;
Commit complete.

## session 2
12:22:10 SQL>
begin
DELETE FROM test WHERE id=1;
INSERT INTO test VALUES (1,sysdate);
12:22:11   4  end;
12:22:12   5  /
PL/SQL procedure successfully completed.

12:23:00 SQL> select * from test;
             ID CTIME
--------------- -----------------
              1 20220819 12:22:36
              2 20220819 12:21:31

SELECT s.sid, s.serial#,decode(bitand(flag,power(2,28)),0,'READ COMMITTED',1,'SERIALIZABLE')  isolation_level
FROM v$transaction t
JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID');
       SID    SERIAL# ISOLATION_LEVE
---------- ---------- --------------
         1      34211 READ COMMITTED

Note:
默认系统事务隔离级别是READ COMMITTED,也就是读已提交, Oracle 可以在begin end 匿名块中做到以事务开始时间的一致性,session 1更新 delete insert 作为一个独立的事务,session 2在session 1后发起事务(begin),oracle虽然是在读已提交事务隔离级别,但是一样可以解决了不可重复读与幻读在读一致性上的实现, session 2可以在begin 事务种读到一致性数据也可以读到session 1 insert的新数据,并delete后重新insert了新数据。

# POSTGRESQL

## SESSION1
sdbo=# CREATE TABLE test (id INT PRIMARY KEY);
CREATE TABLE
sdbo=# INSERT INTO test VALUES (1);
INSERT 0 1
sdbo=# INSERT INTO test VALUES (2);
INSERT 0 1

# session 1

sdbo=# alter table test add ctime time;
ALTER TABLE
sdbo=# update test set ctime=now();
UPDATE 2

sdbo=# select * from test;
 id |      ctime
----+-----------------
  2 | 14:47:37.415516
  1 | 14:47:37.415516
(2 行记录)

sdbo=# begin;
BEGIN
sdbo=*# DELETE FROM test WHERE id=1;
DELETE 1
sdbo=*# INSERT INTO test VALUES (1,now());
INSERT 0 1
sdbo=*# select * from test;
 id |      ctime
----+-----------------
  2 | 14:47:37.415516
  1 | 14:49:49.151784
(2 行记录)


# session 2

begin;
DELETE FROM test WHERE id=1;
DELETE FROM test WHERE id=1;
INSERT INTO test VALUES (1,now());

sdbo=# begin;
BEGIN
sdbo=*# DELETE FROM test WHERE id=1;
-- hang

# session 1
sdbo=*# commit;
COMMIT
sdbo=#

# session 2
sdbo=# begin;
BEGIN
sdbo=*# DELETE FROM test WHERE id=1;
DELETE 0
sdbo=*# DELETE FROM test WHERE id=1;
DELETE 1
sdbo=*# INSERT INTO test VALUES (1,now());
INSERT 0 1
sdbo=*# select * from test;
 id |      ctime
----+-----------------
  2 | 14:47:37.415516
  1 | 14:50:13.062228
(2 行记录)


sdbo=# show transaction_isolation
sdbo-# ;
 transaction_isolation
-----------------------
 read committed
(1 行记录)

Note:
注意在postgresql中和oracle是不同的行为, 在同样读已提交事务隔离级别下, session 2的第一条delete在session 1 提交后,显示delete 0条记录,第二条delete在同一个begin 事务中显示delete 1条记录。同一个事务中相同SQL显示了不同的结果, 可见在一些应用中如果把多个SQL放在一个事务begin中,如果多用户并发,最终会导致和oracle不一样的结果。

Table 13.1. Transaction Isolation Levels

Isolation Level Dirty Read Nonrepeatable Read Phantom Read Serialization Anomaly
Read uncommitted Allowed, but not in PG Possible Possible Possible
Read committed Not possible Possible Possible Possible
Repeatable read Not possible Not possible Allowed, but not in PG Possible
Serializable Not possible Not possible Not possible Not possible

观察快照

# session 1
[local]:5432 postgres@anbob=# update test set ctime=now();
UPDATE 2
[local]:5432 postgres@anbob=# select xmin,xmax,t.* from test t;
 xmin | xmax | id |     ctime
------+------+----+----------------
  623 |    0 |  2 | 21:05:43.59544
  623 |    0 |  1 | 21:05:43.59544
(2 rows)

[local]:5432 postgres@anbob=# begin;
BEGIN
[local]:5432 postgres@anbob=#* select xmin,xmax,t.* from test t;
 xmin | xmax | id |     ctime
------+------+----+----------------
  623 |    0 |  2 | 21:05:43.59544
  623 |    0 |  1 | 21:05:43.59544
(2 rows)

[local]:5432 postgres@anbob=#* DELETE FROM test WHERE id=1;
DELETE 1
[local]:5432 postgres@anbob=#* select xmin,xmax,t.* from test t;
 xmin | xmax | id |     ctime
------+------+----+----------------
  623 |    0 |  2 | 21:05:43.59544
(1 row)

[local]:5432 postgres@anbob=#* INSERT INTO test VALUES (1,now());
INSERT 0 1
[local]:5432 postgres@anbob=#* select xmin,xmax,t.* from test t;
 xmin | xmax | id |      ctime
------+------+----+-----------------
  623 |    0 |  2 | 21:05:43.59544
  624 |    0 |  1 | 21:06:03.277602
(2 rows)

# session 2
[local]:5432 postgres@anbob=#
[local]:5432 postgres@anbob=# begin;
BEGIN
[local]:5432 postgres@anbob=#* select txid_current();
 txid_current
--------------
          625
(1 row)
[local]:5432 postgres@anbob=#* select xmin,xmax,t.* from test t;
 xmin | xmax | id |     ctime
------+------+----+----------------
  623 |    0 |  2 | 21:05:43.59544
  623 |  624 |  1 | 21:05:43.59544
(2 rows)

[local]:5432 postgres@anbob=#* DELETE FROM test WHERE id=1;
-- hang

# session 1
[local]:5432 postgres@anbob=#* commit;
COMMIT

# session 2
[local]:5432 postgres@anbob=#* DELETE FROM test WHERE id=1;
DELETE 0
[local]:5432 postgres@anbob=#* select txid_current();
 txid_current
--------------
          625
(1 row)

[local]:5432 postgres@anbob=#* select xmin,xmax,t.* from test t;
 xmin | xmax | id |      ctime
------+------+----+-----------------
  623 |    0 |  2 | 21:05:43.59544
  624 |    0 |  1 | 21:06:03.277602
(2 rows)

[local]:5432 postgres@anbob=#* DELETE FROM test WHERE id=1;
DELETE 1
[local]:5432 postgres@anbob=#* select txid_current();
 txid_current
--------------
          625
(1 row)

[local]:5432 postgres@anbob=#* INSERT INTO test VALUES (1,now());
INSERT 0 1
[local]:5432 postgres@anbob=#* select xmin,xmax,t.* from test t;
 xmin | xmax | id |      ctime
------+------+----+-----------------
  623 |    0 |  2 | 21:05:43.59544
  625 |    0 |  1 | 21:06:36.750481
(2 rows)

# session 1
[local]:5432 postgres@anbob=# select xmin,xmax,t.* from test t;
 xmin | xmax | id |      ctime
------+------+----+-----------------
  623 |    0 |  2 | 21:05:43.59544
  624 |  625 |  1 | 21:06:03.277602
(2 rows)

[local]:5432 postgres@anbob=#

Note:
在postgresql中即使在begin同一个事务中,不同的SQL执行时,每个SQL会取一次当前数据的快照, 像上面session 2的事务块第1个delete执行前快照记录忆被session delete ,insert 虽已提交,但是postgresql中无法幻读,也就是无法像oracle一样根据事务开始时间保证一致性, 就读取不到insert的新数据。 第二个delete sql执行前的快照读取到了session 1 inserted的数据。

# mysql

mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| SERIALIZABLE            |
+-------------------------+
设置隔离级别
方式1:通过set命令
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
其中level有4种值:
level: {
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE
}

# session 1
MYSQL_root@localhost [anbob]> select * from test;
+----+----------+
| id | ctime    |
+----+----------+
|  1 | 15:31:15 |
|  2 | 15:31:15 |
+----+----------+
2 rows in set (0.00 sec)

MYSQL_root@localhost [anbob]>
MYSQL_root@localhost [anbob]> begin;
Query OK, 0 rows affected (0.00 sec)

MYSQL_root@localhost [anbob]> DELETE FROM test WHERE id=1;
Query OK, 1 row affected (0.00 sec)

MYSQL_root@localhost [anbob]> INSERT INTO test VALUES (1,now());
Query OK, 1 row affected (0.00 sec)

# session 2

begin;
DELETE FROM test WHERE id=1;
INSERT INTO test VALUES (1,now());

MYSQL_root@localhost [anbob]> begin;
Query OK, 0 rows affected (0.00 sec)

MYSQL_root@localhost [anbob]> DELETE FROM test WHERE id=1;
-- hang 

# session 1
MYSQL_root@localhost [anbob]> commit;
Query OK, 0 rows affected (0.01 sec)

MYSQL_root@localhost [anbob]> select * from test;
+----+----------+
| id | ctime    |
+----+----------+
|  1 | 15:31:40 |
|  2 | 15:31:15 |
+----+----------+
2 rows in set (0.00 sec)

# session 2
Database changed
MYSQL_root@localhost [anbob]> begin;
Query OK, 0 rows affected (0.00 sec)

MYSQL_root@localhost [anbob]> DELETE FROM test WHERE id=1;
Query OK, 1 row affected (7.87 sec)

MYSQL_root@localhost [anbob]> INSERT INTO test VALUES (1,now());
Query OK, 1 row affected (0.00 sec)

MYSQL_root@localhost [anbob]> select * from test;
+----+----------+
| id | ctime    |
+----+----------+
|  1 | 15:31:56 |
|  2 | 15:31:15 |
+----+----------+
2 rows in set (0.00 sec)


MYSQL_root@localhost [anbob]> commit;
Query OK, 0 rows affected (0.01 sec)

MYSQL_root@localhost [anbob]> select * from test;
+----+----------+
| id | ctime    |
+----+----------+
|  1 | 15:31:56 |
|  2 | 15:31:15 |
+----+----------+
2 rows in set (0.00 sec)

# session 1
MYSQL_root@localhost [anbob]> select * from test;
+----+----------+
| id | ctime    |
+----+----------+
|  1 | 15:31:56 |
|  2 | 15:31:15 |
+----+----------+
2 rows in set (0.00 sec)

MYSQL_root@localhost [anbob]> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+
1 row in set (0.01 sec)

NOTE:
默认mysql是可重复读隔离级别,可见行为和oracle基本一致, 但是该模式下有间隙锁问题,建议修改为读已提交;


MYSQL_root@localhost [anbob]> set TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)

修改隔离级别后 该问题现象一致;

Summary:
在某些事务应用中begin delete xxx; insert ; end类似多条DML的事务中,oracle和mysql基本一致的形为,可以以begin 事务时间保持一致性, 而在postgresql中虽是begin 事务,但是不同的SQL是在执行时取数据的快照,产生了不致的数据, 可能这是undo 与 postgresql中的mvcc的实现方式不同的原因吧。

打赏

对不起,这篇文章暂时关闭评论。