事务隔离级别(5):Mysql 与Oracle事务隔离级别对比分析2
上一篇概括的比较了Oracle与Mysql的各个事务隔离级别,本篇将通过示例来加深理解。
1.Mysql各事务隔离级别
a)同时打开4个连接,分别设置隔离级别为读未提交(sess1_ru),读提交(sess2_rc),可重复读(sess3_rr)与串行化(sess4_serial),并开始事务(begin;)
-- 连接1sess1_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)-- 连接2sess2_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)-- 连接3sess3_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 >-- 连接4sess4_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',但暂不提交
-- 连接5sess5_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 transactionsess4_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: 0sess5_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: 0sess5_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 YORK20 RESEARCH DALLAS30 SALES CHICAGO40 OPERATIONS BOSTON50 HR SFupdate scott.dept set loc='LA' where deptno=50;1 row updated.commit;------ 即使是只读事务开始后的第一个read操作,仍看不到修改sess1 > select * from scott.dept;DEPTNO DNAME LOC-------------- -------------10 ACCOUNTING NEW YORK20 RESEARCH DALLAS30 SALES CHICAGO40 OPERATIONS BOSTON50 HR SF事务结束后看到更新的数据sess1 > rollback;Rollback complete.sess1 > select * from scott.dept;DEPTNO DNAME LOC-------------- -------------10 ACCOUNTING NEW YORK20 RESEARCH DALLAS30 SALES CHICAGO40 OPERATIONS BOSTON50 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 YORK20 RESEARCH DALLAS30 SALES CHICAGO40 OPERATIONS BOSTON50 HR LA-- 另一连接修改数据并提交select * from dept;DEPTNO DNAME LOC-------------- -------------10 ACCOUNTING NEW YORK20 RESEARCH DALLAS30 SALES CHICAGO40 OPERATIONS BOSTON50 HR LA='SF' where deptno=50;1 row updated.commit;Commit complete.-- 连接1看不到修改,但当修改发生冲突时报错sess1 > select * from dept;DEPTNO DNAME LOC-------------- -------------10 ACCOUNTING NEW YORK20 RESEARCH DALLAS30 SALES CHICAGO40 OPERATIONS BOSTON50 HR LAsess1 > 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:
“只读事务”快照点是事务开始时刻;
“串行化”隔离级别基于快照读,并检测写冲突。
