Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1423964
  • 博文数量: 176
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 3871
  • 用 户 组: 普通用户
  • 注册时间: 2011-03-30 13:00
个人简介

About me:Oracle ACE pro,optimistic,passionate and harmonious. Focus on ORACLE,MySQL and other database programming,peformance tuning,db design, j2ee,Linux/AIX,Architecture tech,etc

文章分类

全部博文(176)

文章存档

2025年(4)

2024年(27)

2023年(28)

2022年(43)

2020年(62)

2014年(3)

2013年(9)

分类: Mysql/postgreSQL

2025-02-18 22:03:41

MySQL的RR模式下,事务开始后,遇到{BANNED}中国第一条普通SELECT开始创建快照,事务期间,快照读都是读取这个快照,重而实现可重复读,另外通过nextkey lock实现防止幻读。
快照读又叫一致性读(consistent read),unlock read,不加锁(元数据锁还是有的,行锁没有,就是普通SELECT)。
 DML包括FOR UPDATE,FOR SHARED是当前读,会读取新提交快照的数据。
  普通SELECT在RR隔离级别下都是可重复读,快照读,除非混合了UPDATE,那么之后的SELECT会读取到本事务修改的数据,像普通的FOR UPDATE,FOR SHARE,
  他们会读取到新提交的数据,但是不影响后续的普通SELECT还是快照读,还是看不到新提交的数据。
  
  
  ###
  也即就算RR模式里混合了当前读,但是整体的快照其实并没有改变,只不过有当前读,当前读本新快照,如果它改变了数据,
  那么本事务是可见的,只是可见改变的部分,其它session在当前事务开始后新提交的其他数据还是不可见的。
  本质上没有改变事物的快照,只是当前读本身是按照RC模式读取,然后它修改的本事务后续快照读都可见,符合事务的定义,
  但是注意,这可能破坏不可重复读,或者导致幻读的发生。
  
  MySQL的RR通过MVCC和锁实现,锁有nextkey lock,gap lock,record lock等。
  和PG的RR使用SI实现不一样,PG的RR里DML也是快照读,所以完全解决不可重复读和幻读问题,当然,没有解决write skew问题,
  write skew需要通过serializable实现,它通过SSI,谓词锁,读写依赖实现。


  1.1)如果RR下,当前事务查询的数据没有被当前事务的DML语句改变,则普通SELECT是可重复读,还是用事物本身快照。
       如果当前事务执行UPDATE,这个UPDATE会执行当前读,会读新提交的数据,如果修改了前面SELECT快照读没有的数据,则再次读,会读到本事务改变的数据,
       但是未改变的别的session提交的数据,还是可重复读。
  
  1.2)RR下,session1(S1) 如果select之后,其他session2(S2)的事务可能插入或删除对应条件的行并提交(对S1的快照读不可见,但是对S1的当前读可见),
  而之后S1对应的行发生当前读(DML或for share,for update),则会读取S2修改的新数据,但是不影响后续普通select,普通select还是快照读。
  如果发生的当前读是修改语句(UPDATE,DELETE,INSERT),修改数据的范围覆盖了select的条件,而且字段内容发生修改,则后续select是不可重复读,如果没有修改字段内容或
  数据量,则还是可重复读,主要看修改语句的nextkey lock锁定的范围与select条件是否重合。
  
  如果S1是普通当前读,也就是for share,for update,只是读取新数据,不发生修改的话,后续普通select(快照读)是可重复读。


2.RR模式下,当前读如果是update,delete等修改数据,修改对当前事务可见,则可能破坏可重复读(只能读取本事务UPDATE的数据,DELETE数据删除了,本来就看不到),
  如果没有对对应条件数据的修改,不会破坏可重复读(要看
  修改对应的nextkey lock范围的数据改变和select条件是否重合)。
  
  RR模式下,幻读是保证的,用nextkey lock实现阻止insert。
  
  
drop table if exists isolation_test;
create table isolation_test(id int(10),name varchar(10),ext int(10),primary key(id),key idx_isolation_test(name));
insert into isolation_test values(1,'a',1),(2,'b',2),(3,'c',3);
commit;


(root@localhost) [employees]> show variables like '%isolation%';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+


select * from isolation_test;
+----+------+------+
| id | name | ext  |
+----+------+------+
|  1 | a    |    1 |
|  2 | b    |    2 |
|  3 | c    |    3 |
+----+------+------+
3 rows in set (0.00 sec)




======================================sample 1:都是快照读,可重复读
s1:
begin;
select * from isolation_test where name='b'; /*t1*/
select sleep(10); /*t2*/
-- t3时刻读取的还是和t1时刻一样,因为t1和t3都是快照读,虽然会话s2在t2时刻提交了一条where name='b'
select * from isolation_test where name='b'; /*t3*/
--提交
commit;
--新事务,自动commit,读取新提交的数据,和t2,t3的不一样,s2的提交这里读到
select * from isolation_test where name='b'; /*t4*/


s2:
begin;
insert into isolation_test values(4,'b',4);/*t2*/
commit;/*t2*/


s1里都是快照读,因此,t1和t3读取的数据一致,虽然s2在t3之前插入数据,在RR下s1没有发生数据修改,s2插入的数据对s1不可见。
t4是在t2之后,且之前有commit;所以重新开启快照,则t4查询的数据是2条。


s1的t1和t3读取的都是(快照读):
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
+----+------+------+


s1的t4读取的数据是(快照读,新开启的事务):
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
|  4 | b    |    4 |----s2的t2新提交的数据
+----+------+------+
2 rows in set (0.00 sec)






=======================================sample2:快照读混合当前读,是for update,for share不会破坏普通select(unlock read)的可重复读
s1:
begin;
select * from isolation_test where name='b'; /*t1*/
select sleep(10); /*t2*/
select * from isolation_test where name='b' for update; /*t3*/
select * from isolation_test where name='b' ; /*t4*/
commit;


s2:
begin;
insert into isolation_test values(4,'b',4);/*t2*/
commit;/*t2*/


s1的t3是当前读(for update)要读取新提交的数据,s2的t2发生在t3之前,因此s1的当前读看到的是新修改的数据,所以s1的t1和t3结果不一样:


s1的t1是(快照读):
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
+----+------+------+


s1的t3是(当前读):
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
|  4 | b    |    4 |---- s2的t2新提交的数据
+----+------+------+


s1的t4(快照读),因为前面的当前读是for update,并没有修改数据,所以后续普通select还是可重复读,还是事务开始的快照。
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
+----+------+------+
1 row in set (0.00 sec)




======================================sample 3:当前读之后有快照读,快照读还是按照之前的快照读取数据,除非当前事务有修改,修改对当前事务的SQL可见。
s1:
begin;
select * from isolation_test where name='b'; /*t1_1*/
select * from isolation_test where name='c'; /*t1_2*/
select sleep(10); /*t2*/
select * from isolation_test where name='b' for update; /*t3_1*/
select * from isolation_test where name='b'; /*t3_2*/
select * from isolation_test where name='c';  /*t3_3*/
commit;


s2:
begin;
insert into isolation_test values(4,'b',4);/*t2*/
insert into isolation_test values(5,'c',5);/*t2*/
commit;/*t2*/


s1的t3期间三条语句,select * from isolation_test where name='b' for update;是当前读,所以读取新数据有2条和t1_1不一样,
select * from isolation_test where name='b'; /*t3_2*/ 还是快照读,沿用原来快照,所以还是1条数据,不是2条。
select * from isolation_test where name='c'; 还是快照读,读取的数据和t1_2一样,还是一条。如下所示:


select * from isolation_test where name='b'; /*t1_1*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
+----+------+------+
1 row in set (0.01 sec)


select * from isolation_test where name='c'; /*t1_2*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  3 | c    |    3 |
+----+------+------+


-- 当前读,可以获取其他事务提交的数据,两条数据
select * from isolation_test where name='b' for update; /*t3_1*/  
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
|  4 | b    |    4 |
+----+------+------+


-- 快照读,还是沿用前面的快照,一条数据
select * from isolation_test where name='b'; /*t3_2*/  
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
+----+------+------+


-- 快照读,还是沿用前面的快照,一条数据
select * from isolation_test where name='c';  /*t3_3*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  3 | c    |    3 |
+----+------+------+






===============================================sample 4:有修改(update,delete,replace),修改部分对当前事务可见,因此,可能破坏可重复读,要看修改的nextkey覆盖的范围对应数据是否变动
如果RR模式下混合当前读,当前读是UPDATE,UPDATE可见事务开始后别的会话提交的数据,如果修改了新数据,则修改的数据当前事务后续普通
SELECT是可见的(快照读),只仅限于本事务修改的数据,未被修改的别的会话新提交的数据,普通SELECT还是不可见。
所以RR下混合当前读,本质上没有改变事物的快照,只是当前读本身是按照RC模式读取,然后它修改的本事务后续快照读都可见,符合事务的定义,
但是注意,这可能破坏不可重复读,或者导致幻读的发生。
如果普通SELECT和SELECT FOR UPDATE混合用,幻读可能发生。
/*重置数据*/
delete from isolation_test where id>=4;
commit;


s1:
begin;
select * from isolation_test where name='b'; /*t1*/
select sleep(10); /*t2*/
update isolation_test set name='b1' where name='b';/*t3_1*/
select * from isolation_test where name='b';  /*t3_2*/
select * from isolation_test where name='b1';  /*t3_3*/
select * from isolation_test where name='c' for update;  /*t3_4*/
select * from isolation_test where name='c';  /*t3_5*/
commit;


s2:
begin;
insert into isolation_test values(4,'b',4);/*t2_1*/
insert into isolation_test values(5,'c',5);/*t2_2*/
commit;/*t2*/


s1包含当前读且修改where name='b'的数据:update isolation_test set name='b1' where name='b';/*t3_1*/
因为t3_1发生在s2的t2之后,因此修改了2条数据,修改对当前session是可见的,这样后续where name='b'就查询不到数据了,
按照where name='b1'能够查询到2条数据。


select * from isolation_test where name='c' for update;  /*t3_4*/ 是当前读,查询到s2的修改,是2条


select * from isolation_test where name='c';  /*t3_5*/ 快照读,还是和t1时刻快照一样,与前面的相比好像产生了幻读。


具体结果如下:


select * from isolation_test where name='b'; /*t1*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
+----+------+------+


已经被本事务修改,本事务修改是可见的,因此查询不到。
select * from isolation_test where name='b';  /*t3_2*/
Empty set (0.00 sec)


被本事务修改为name='b1',可以看到有2条记录,不是1条,因为update是当前读,可以看到s2的insert,这条s1 update后是2条,s1后续查询可见。
select * from isolation_test where name='b1';  /*t3_3*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b1   |    2 |
|  4 | b1   |    4 |
+----+------+------+
2 rows in set (0.00 sec)


当前读,能够看到s2的insert:
select * from isolation_test where name='c' for update;  /*t3_4*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  3 | c    |    3 |
|  5 | c    |    5 |
+----+------+------+
2 rows in set (0.00 sec)


没有对name='c'的修改记录,所以快照读还是与首次select一致,只看到一条
select * from isolation_test where name='c';  /*t3_5*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  3 | c    |    3 |
+----+------+------+




===============================================sample 5:rr模式下,如果当前事务的dml修改覆盖了对应的select条件数据,则可能发生不可重复读。
/*重置数据*/
begin;
delete from isolation_test where id>=4;
update isolation_test set name='b' where id=2;
commit;


s1:
begin;
select * from isolation_test where name='b'; /*t1_1*/
select * from isolation_test where name='c'; /*t1_2*/
select * from isolation_test where id<=3;  /*t1_3*/
select sleep(10); /*t2*/
update isolation_test set name='c1' where id>=3;/*t3_1*/
select * from isolation_test where name='b';  /*t3_2*/
select * from isolation_test where name='c' for update;  /*t3_3*/
select * from isolation_test where name='c';  /*t3_4*/
select * from isolation_test where name='c1';  /*t3_5*/
select * from isolation_test where id<=3; /*t3_6*/
commit;


s2:
begin;
insert into isolation_test values(4,'b',4);/*t2_1*/
insert into isolation_test values(5,'c',5);/*t2_2*/
commit;/*t2*/


t1_1是快照读,没有数据改变:
select * from isolation_test where name='b'; /*t1_1*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
+----+------+------+


t1_2是快照读,没有数据改变:
select * from isolation_test where name='c'; /*t1_2*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  3 | c    |    3 |
+----+------+------+


t1_3是快照读,没有数据改变:
select * from isolation_test where id<=3;  /*t1_3*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  1 | a    |    1 |
|  2 | b    |    2 |
|  3 | c    |    3 |
+----+------+------+




s2的t2阶段插入2条数据,s1的t3_1按照where id>=3更新update isolation_test set name='c1' where id>=3;
因为是当前读,所以可以看到s2的t2阶段插入的2条数据。因此s1的当前事务更新了3条数据(id=3,4,5),这个3条数据,对s1 t3_1后续的SQL可见。


因此,t3_2按照where name='b'查询,数据范围不在update where id>=3范围内,所以还是和t1的结果一样,返回1行。
select * from isolation_test where name='b';  /*t3_2*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  2 | b    |    2 |
+----+------+------+
1 row in set (0.00 sec




t3_3是当前读,读取新数据,因为where name='c'的已经全被更新成c1,所以查询不到数据:
select * from isolation_test where name='c' for update;  /*t3_3*/
Empty set (0.01 sec)




t3_4是快照读,但是因为条件是where name='c',前面的update更新的行覆盖此条件,而且是当前事务的更新,当前事务快照读可见,因此返回0条:
select * from isolation_test where name='c';  /*t3_4*/
Empty set (0.01 sec)








t3_5是where name='c1',前面的update覆盖此条件的有3条记录,因此返回3条:
select * from isolation_test where name='c1';  /*t3_5*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  3 | c1   |    3 |
|  4 | c1   |    4 |
|  5 | c1   |    5 |
+----+------+------+
3 rows in set (0.00 sec)




t3_6对应的是id=1,2,3,只有id=3的被update修改,因为是当前事务的修改,数据对快照读可见,因此,返回3条,其中id=3的name变为c1。


导致不可重复读,t3_6与t3_1有1行发生改变,主要是本事务的update修改了对应条件的行,如果没有修改id<=3的,也是可重复读的:


select * from isolation_test where id<=3; /*t3_6*/
+----+------+------+
| id | name | ext  |
+----+------+------+
|  1 | a    |    1 |
|  2 | b    |    2 |
|  3 | c1   |    3 |
+----+------+------+
3 rows in set (0.00 sec)

阅读(24) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~