事务隔离级别(5):Mysql 与Oracle事务隔离级别对比分析2
上一篇概括的比较了Oracle与Mysql的各个事务隔离级别,本篇将通过示例来加深理解。
1.Mysql各事务隔离级别
a)同时打开4个连接,分别设置隔离级别为读未提交(sess1_ru),读提交(sess2_rc),可重复读(sess3_rr)与串行化(sess4_serial),并开始事务(begin;)
-- 连接1
sess1_ru > select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED |
+-------------------------+
1 row in set (0.00 sec)
sess1_ru > begin;
Query OK, 0 rows affected (0.00 sec)
-- 连接2
sess2_rc > select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED |
+-------------------------+
1 row in set (0.00 sec)
sess2_rc > begin;
Query OK, 0 rows affected (0.00 sec)
-- 连接3
sess3_rr > select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)
sess3_rr > begin;
Query OK, 0 rows affected (0.00 sec)
sess3_rr >
-- 连接4
sess4_serial > select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| SERIALIZABLE |
+-------------------------+
1 row in set (0.00 sec)
sess4_serial > begin;
Query OK, 0 rows affected (0.00 sec)
b)打开第五个连接,在缺省可重复读(RR)隔离级别下进行更新操作
dept表有两条记录,将dept=10的记录loc改为'SZ',但暂不提交
-- 连接5
sess5_rr > select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)
sess5_rr > begin;
Query OK, 0 rows affected (0.00 sec)
sess5_rr > select * from dept;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SH |
+--------+----------+------+
2 rows in set (0.00 sec)
--更新
sess5_rr > update dept set loc='SZ' where deptno=10;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
c)观察前4个连接
连接1“读未提交”可以看到未提交的数据'SZ';
连接2“读提交”及连接3“可重复读”还是看到之前提交的'SH';
连接4“串行”,在此隔离级别下,读数据前要加读锁,被连接5的更新语句所持有的锁阻塞,timeout后报错。
-- ru看到未提交数据
sess1_ru > select * from dept;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SZ |
+--------+----------+------+
2 rows in set (0.00 sec)
-- rc看不到未提交数据
sess2_rc > select * from dept;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SH |
+--------+----------+------+
2 rows in set (0.00 sec)
-- rr看不到未提交数据
sess3_rr > select * from dept;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SH |
+--------+----------+------+
2 rows in set (0.00 sec)
-- serilizable被连接5阻塞
sess4_serial > select * from dept;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
sess4_serial >
d)连接5提交后,前4个连接再进行查询
连接2“读提交”可以看到更改后的数据'SZ'了;
连接3“可重复读”的快照读看到的仍是事务开始后第一个read操作时的快照点'SH',而紧接着进行当前读'select * from dept for update'被连接4阻塞(阻塞的原因后续文章再做分析),而连接4回滚后,连接3的当前读返回deptno=10的loc为'SZ',再做一次一致性读,返回结果为'SH',验证了mysql在RR隔离级别,同一条记录快照读和当前读可能返回不同的结果。
--连接5提交
sess5_rr > commit;
Query OK, 0 rows affected (0.00 sec)
sess1_ru > select * from dept;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SZ |
+--------+----------+------+
2 rows in set (0.00 sec)
-- rc此时看到已提交的'SZ'
sess2_rc > select * from dept;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SZ |
+--------+----------+------+
2 rows in set (0.00 sec)
-- rr快照读仍然看到'SH',可重复读
sess3_rr > select * from dept;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SH |
+--------+----------+------+
2 rows in set (0.01 sec)
-- rr当前读被连接4阻塞,session4回滚
sess3_rr > select * from dept for update; -- 阻塞
sess4_serial > rollback; -- session4回滚
Query OK, 0 rows affected (0.00 sec)
-- rr当前读看到'SZ'
sess3_rr > select * from dept for update;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SZ |
+--------+----------+------+
2 rows in set (33.10 sec)
--rr快照读与当前读不一致
sess3_rr > select * from dept;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SH |
+--------+----------+------+
2 rows in set (0.00 sec)
-- 事务结束后看到连接5的修改
sess3_rr > rollback;
Query OK, 0 rows affected (0.00 sec)
sess3_rr > select * from dept;
+--------+----------+------+
| deptno | deptname | loc |
+--------+----------+------+
| 5 | Finance | BJ |
| 10 | RD | SZ |
+--------+----------+------+
2 rows in set (0.00 sec)
2.Mysql RR隔离级别下快照读与Oracle只读事务快照点的区别
之前提到过,Mysql RR隔离级别下快照读的快照点是第一个read操作的时刻,而Oracle只读事务快照点是事务开始时刻。
下面的例子显示,Mysql连接3(可重复读)开始一个事务,而连接5(sess5_rr)将deptno=10的loc改为'GZ'并提交,之后连接3进行快照读,作为事务第一个read操作可以看到事务开始后提交的修改'GZ';而连接5紧接着将deptno=10的loc改为'BJ'并提交,而连接3重复读到的结果仍然是'GZ',连接3回滚结束事务后可看到'BJ'。
而Oracle只读事务开始后,另一连接将deptno=50的loc改为'LA'并提交,但只读事务第一个read看到的仍然是事务开始前的值'SF'。
Mysql 快照点为事务开始后第一个read
连接3开始新事务
sess3_rr > begin;
Query OK, 0 rows affected (0.00 sec)
--连接5进行修改并提交
sess5_rr > select * from dept;
+--------+----------+------+
deptno | deptname | loc |
+--------+----------+------+
5 | Finance | BJ |
10 | RD | SZ |
+--------+----------+------+
2 rows in set (0.00 sec)
sess5_rr > update dept set loc='GZ' where deptno=10;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
sess5_rr > commit;
Query OK, 0 rows affected (0.00 sec)
作为第一个read操作,rr可看到刚刚修改的 'GZ'
sess3_rr > select * from dept;
+--------+----------+------+
deptno | deptname | loc |
+--------+----------+------+
5 | Finance | BJ |
10 | RD | GZ |
+--------+----------+------+
2 rows in set (0.00 sec)
连接5再次做修改并提交
sess5_rr > update dept set loc='BJ' where deptno=10;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
sess5_rr > commit;
Query OK, 0 rows affected (0.00 sec)
sess5_rr > select * from dept;
+--------+----------+------+
deptno | deptname | loc |
+--------+----------+------+
5 | Finance | BJ |
10 | RD | BJ |
+--------+----------+------+
2 rows in set (0.00 sec)
----- 连接3再进行读,重复读结果不变仍为'GZ'
+--------+----------+------+
deptno | deptname | loc |
+--------+----------+------+
5 | Finance | BJ |
10 | RD | GZ |
+--------+----------+------+
2 rows in set (0.00 sec)
回滚结束事务后看到数据变化
sess3_rr > rollback;
Query OK, 0 rows affected (0.00 sec)
sess3_rr > select * from dept;
+--------+----------+------+
deptno | deptname | loc |
+--------+----------+------+
5 | Finance | BJ |
10 | RD | BJ |
+--------+----------+------+
2 rows in set (0.00 sec)
Oracle
连接1开始只读事务
sess1 > set transaction read only;
Transaction set.
------ 另一连接进行修改并提交
select * from scott.dept;
DEPTNO DNAME LOC
-------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 HR SF
update scott.dept set loc='LA' where deptno=50;
1 row updated.
commit;
------ 即使是只读事务开始后的第一个read操作,仍看不到修改
sess1 > select * from scott.dept;
DEPTNO DNAME LOC
-------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 HR SF
事务结束后看到更新的数据
sess1 > rollback;
Rollback complete.
sess1 > select * from scott.dept;
DEPTNO DNAME LOC
-------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 HR LA
3. Oracle serializable 隔离级别的写冲突检测
Oracle的serializable进行的是快照读,连接1开启serializable隔离级别后,看不到另一事务进行的修改,但当写冲突(修改同一数据)时,报'can't serialize access for this transaction'错误。
连接1 开启serializable隔离级别
sess1 > set transaction isolation level serializable;
Transaction set.
sess1 > select * from dept;
DEPTNO DNAME LOC
-------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 HR LA
-- 另一连接修改数据并提交
select * from dept;
DEPTNO DNAME LOC
-------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 HR LA
'SF' where deptno=50; =
1 row updated.
commit;
Commit complete.
-- 连接1看不到修改,但当修改发生冲突时报错
sess1 > select * from dept;
DEPTNO DNAME LOC
-------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 HR LA
sess1 > update dept set loc='NY' where deptno=50;
update dept set loc='NY' where deptno=50
*
ERROR at line 1:
can't serialize access for this transaction :
由以上示例可以看出:
对于Mysql:
“读未提交”可以读到脏数据;
“读提交”可能发生“不可重复读”及“幻读”现象;
“可重复读”的快照读基于事务开始后第一个read操作时刻,而当前读可能返回与快照读不同的结果;
“串行化”的读操作都是当前读并加锁;
对于Oracle:
“只读事务”快照点是事务开始时刻;
“串行化”隔离级别基于快照读,并检测写冲突。