先复习一些概念
- 脏读:指当前事务读取到另外一个事务未提交的数据
- 不可重复读:指在一个事务中对同一条数据读取两次但是结果不同
- 幻读/续读:在一个事务中同样的查询条件,查出了不同条数的数据
接下来说事务隔离级别
- RU Read—Uncommitted(读未提交)
可以读到其他事务未提交的数据,隔离性差,会出现脏读(当前内存读)、不可重复读、幻读。
- RC Read—Committed(读已提交)
可以读到其他事务已提交的数据,隔离性一般,不会出现脏读问题,但是会出现不可重复读、幻读。
- RR Repeatable—Read(可重复读)(MySQL默认级别)
可以防止脏读(当前内存读),防止不可重复读问题,防止会出现的幻读问题,但是并发能力较差;
会使用next lock锁进制,来防止幻读问题,但是引入锁进制后,锁的代价会比较高,比较耗费CPU资源,占用系统性能;
- SR Serializable (串行化/序列化)
隔离性比较高,可以实现串行化读取数据,但是事务的并发度就没有了;
这是事务的最高级别,在每条读的数据上,加上锁,使之不可能相互冲突。
一些验证:
在解释分析说明相应的隔离级别名词前,需要对数据库事务隔离级别进行调整,以及关闭自动提交功能:
SELECT @@transaction_isolation; -- 查看当前事务隔离级别
SELECT @@autocommit; -- 查看当前是否自动提交:1是0否
show variables like 'autocommit'; -- 查看当前是否自动提交:ON是OFF否
set GLOBAL autocommit=1;
-- 注意上面这条命令如果是在一些数据库连接工具使用这条命令的话有可能没效果因为这些工具需要去设置中更改,另外不加global也行,确保本次会话不自动提交就行了
set GLOBAL transaction_isolation='READ-UNCOMMITTED';
set GLOBAL transaction_isolation='READ-COMMITTED';
set GLOBAL transaction_isolation='REPEATABLE-READ';
set GLOBAL transaction_isolation='SERIALIZABLE';
数据准备:
use test;
create table t1 ( id int not null primary key auto_increment,
a int not null,
b varchar(20) not null,
c varchar(20) not null) charset = utf8mb4 engine = innodb;
begin;
insert into t1(a,b,c)
values
(5,'a','aa'),
(7,'c','ab'),
(10,'d','ae'),
(13,'g','ag'),
(14,'h','at'),
(16,'i','au'),
(20,'j','av'),
(22,'k','aw'),
(25,'l','ax'),
(27,'o','ay'),
(31,'p','az'),
(50,'x','aze'),
(60,'y','azb');
commit;
-- 最后确认一下两个SQL会话窗口,即不同的事务查看的数据、事务隔离级别、自动提交设置是否一致
开始验证:
-
脏读
-
RU级别下
-- 数据库A会话窗口操作 mysql> begin; mysql> update t1 set a=10 where id=1;-- 只是在内存层面进行数据页中数据修改 -- 此时进行B会话操作 mysql> rollback;-- 进行事务回滚操作 -- 数据库B会话窗口操作 mysql> begin; mysql> select * from t1 where id=1; +----+----+---+----+ | id | a | b | c | +----+----+---+----+ | 1 | 10 | a | aa | +----+----+---+----+ 1 row in set (0.01 sec)-- 在A会话窗口没提交的事务修改,被B会话窗口查询到了 -- 此时进行A会话回滚 mysql> select * from t1 where id=1; +----+----+---+----+ | id | a | b | c | +----+----+---+----+ | 1 | 5 | a | aa | +----+----+---+----+ 1 row in set (0.01 sec)-- 在A会话窗口进行回滚后,在B窗口查询的数据又恢复了
-
-
不可重复读
-
RU级别下(同RU级别下的脏读)
-
RC级别下
-- 数据库A会话窗口操作 mysql> use test; mysql> begin;mysql> select * from t1 where id=1; +----+---+---+----+ | id | a | b | c | +----+---+---+----+ | 1 | 5 | a | aa | +----+---+---+----+ 1 row in set (0.00 sec) -- 此时进行B会话操作 A窗口事务查询信息 = B窗口事务查询信息 mysql> update t1 set a=10 where id=1;-- A窗口事务进行修改 -- 此时进行B会话操作 mysql> commit;-- A窗口事务进行提交# -- 数据库B会话窗口操作 mysql> use test; mysql> begin;mysql> select * from t1 where id=1; +----+---+---+----+ | id | a | b | c | +----+---+---+----+ | 1 | 5 | a | aa | +----+---+---+----+ 1 row in set (0.00 sec)-- A窗口事务查询信息 = B窗口事务查询信息 mysql> select * from t1 where id=1; +----+---+---+----+ | id | a | b | c | +----+---+---+----+ | 1 | 5 | a | aa | +----+---+---+----+ 1 row in set (0.00 sec)-- B窗口事务查询信息,不能看到A窗口事务未提交的数据变化,避免了脏数据问题; mysql> select * from t1 where id=1; +----+---+---+----+ | id | a | b | c | +----+---+---+----+ | 1 | 10 | a | aa | +----+---+---+----+ 1 row in set (0.00 sec)-- A窗口事务提交之后,B窗口事务查询信息和之前不同了
-
RR级别下
-- 数据库A会话窗口操作 mysql> use test; mysql> begin; mysql> select * from t1;-- 确认初始数据信息 mysql> update t1 set a=10 where id=1;-- A窗口事务进行修改 -- 此时进行B会话操作 mysql> commit;-- A窗口事务进行提交 -- 此时进行B会话操作 -- 数据库B会话窗口操作 mysql> use test; mysql> begin; mysql> select * from t1;-- 确认初始数据信息 mysql> select * from t1 where id=1; +----+---+---+----+ | id | a | b | c | +----+---+---+----+ | 1 | 5 | a | aa | +----+---+---+----+ 1 row in set (0.00 sec)-- B窗口事务查询信息,不能看到A窗口事务未提交的数据变化,避免了脏数据问题; mysql> select * from t1 where id=1; +----+---+---+----+ | id | a | b | c | +----+---+---+----+ | 1 | 5 | a | aa | +----+---+---+----+ 1 row in set (0.00 sec)-- A窗口事务提交之后,B窗口事务查询信息和之前是相同的; -- 在RR级别状态下,同一窗口的事务生命周期下,每次读取相同数据信息是一样,避免了不可重复读问题 mysql> commit; mysql> select * from t1 where id=1;-- 在RR级别状态下,同一窗口的事务生命周期结束后,看到的数据信息就是修改的了
-
-
-
幻读
-
RU/RC级别下
-- 数据库A会话窗口操作 mysql> use test; mysql> select * from t1; +----+----+---+-----+ | id | a | b | c | +----+----+---+-----+ | 1 | 10 | a | aa | | 2 | 7 | c | ab | | 3 | 10 | d | ae | | 4 | 13 | g | ag | | 5 | 14 | h | at | | 6 | 16 | i | au | | 7 | 20 | j | av | | 8 | 22 | k | aw | | 9 | 25 | l | ax | | 10 | 27 | o | ay | | 11 | 31 | p | az | | 12 | 50 | x | aze | | 13 | 60 | y | azb | +----+----+---+-----+ 13 rows in set (0.00 sec)-- 查看获取A窗口表中数据 mysql> alter table t1 add index idx(a);-- 在A窗口中,添加t1表的a列为索引信息 mysql> begin; mysql> update t1 set a=20 where a<20;-- 在A窗口中,将a<20的信息均调整为20 -- 此时进行B会话操作 mysql> commit;-- 在A窗口中,进行事务提交操作,是在B窗口事务没有提交前 mysql> mysql> select * from t1;-- 在A窗口中,查看数据信息,希望看到的a是没有小于20的,但是结果看到了a存在等于10的(即出现了幻读) -- 数据库B会话窗口操作 mysql> use test; mysql> select * from t1; +----+----+---+-----+ | id | a | b | c | +----+----+---+-----+ | 1 | 10 | a | aa | | 2 | 7 | c | ab | | 3 | 10 | d | ae | | 4 | 13 | g | ag | | 5 | 14 | h | at | | 6 | 16 | i | au | | 7 | 20 | j | av | | 8 | 22 | k | aw | | 9 | 25 | l | ax | | 10 | 27 | o | ay | | 11 | 31 | p | az | | 12 | 50 | x | aze | | 13 | 60 | y | azb | +----+----+---+-----+ 13 rows in set (0.00 sec)-- 查看获取B窗口表中数据 mysql> begin; mysql> insert into t1(a,b,c) values(10,'A','B')-- 在B窗口中,插入一条新的数据信息 a=10 mysql> commit;-- 在B窗口中,进行事务提交操作
-
RR级别下
-- 数据库A会话窗口操作 mysql> use test; mysql> select * from t1;-- 查看获取A窗口表中数据 mysql> alter table t1 add index idx(a);-- 在A窗口中,添加t1表的a列为索引信息 mysql> begin; mysql> update t1 set a=20 where a>20;-- 在A窗口中,将a>20的信息均调整为20 -- 数据库B会话窗口操作 mysql> use test; mysql> select * from t1;-- 查看获取B窗口表中数据 mysql> begin; mysql> insert into t1(a,b,c) values(30,'sss','bbb');-- 在B窗口中,插入一条新的数据信息 a=30,但是语句执行时会被阻塞,没有反应; -- 数据库C会话窗口操作 mysql> show processlist;-- 在C窗口中,查看数据库连接会话信息,insert语句在执行,等待语句超时(默认超时时间是50s) -- 因为此时在RR机制下,创建了行级锁(阻塞修改)+间隙锁(阻塞区域间信息插入)=next lock -- 区域间隙锁 < 左闭右开(可用临界值) ; 区域间隙锁 > 左开右闭(不可用临界值)
-