mysql数据库事务及隔离级别详解

今天我们分享 mysql数据库事务及隔离级别:

一、事务特性:

首先,事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。

原子性(atomicity):undo log(MVCC)

一致性(consistency):最核心和最本质的要求

隔离性(isolation):锁,mvcc(多版本并发控制)

持久性(durability):redo log    

二、隔离性(isolation)详解:

1、隔离级别:

一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。(对数据库的并行执行,应该像串行执行一样)

未提交读(READ UNCOMMITED)脏读

已提交读 (READ COMMITED)不可重复读

可重复读(REPEATABLE READ)

可串行化(SERIALIZABLE)

数据库的事务隔离级别有四种,分别是读未提交、读已提交、可重复读、序列化,不同的隔离级别下会产生脏读、幻读、不可重复读等相关问题,因此在选择隔离级别的时候要根据应用场景来决定,使用合适的隔离级别。各种隔离级别和数据库异常情况对应情况如下:

mysql数据库事务及隔离级别详解_第1张图片

mysql 默认的事务隔离级别为: 可重复读(REPEATABLE READ)

show variables like '%tx_isolation%';

2、SQL 标准定义了四个隔离级别:

- READ-UNCOMMITTED(读取未提交): 事务的修改,即使没有提交,对其他事务也都是可见的。事务能够读取未提交的数据,这种情况称为脏读。


- READ-COMMITTED(读取已提交): 事务读取已提交的数据,大多数数据库的默认隔离级别。当一个事务在执行过程中,数据被另外一个事务修改,造成本次事务前后读取的信息不一  样,这种情况称为不可重复读。


- REPEATABLE-READ(可重复读): 这个级别是MySQL的默认隔离级别,它解决了脏读的问题,同时也保证了同一个事务多次读取同样的记录是一致的,但这个级别还是会出现幻读的    情况。幻读是指当一个事务A读取某一个范围的数据时,另一个事务B在这个范围插入行,A事务再次读取这个范围的数据时,会产生幻读。


- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重  复读以及幻读。

事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。

三、事务并发问题

脏读:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据。

不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果 不一致。

幻读:系统管理员 A 将数据库中所有学生的成绩从具体分数改为 ABCDE 等级,但是系统管理员 B 就在这个时候插入了一条具体分数的记录,当系统管理员 A 改结束后发现

还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。(事务A按照一定条件进行数据读取,期间事务B插入了相同搜索条件的新数据,事务A再次按照原先条件进行修改数据,发现修改了事物B刚才提交的数据,而且再次读取时,发现了事务B新插入的数据称之为幻读。)

不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件就行,解决幻读需要锁表(当前读和快照读共同参与时才可能发生幻读)。

1、未提交读( READ UNCOMMITED )脏读
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL read UNCOMMITTED;//设置隔离属性
1)一个 session
start TRANSACTION
update account set balance = balance -50 where id = 1
2)另外一个 session 中查询
select * from account
3)回到第一个 session 中 回滚事务
ROLLBACK
4)在第二个 session
select * from account
在另外一个 session 中读取到了未提交的数据,这部分的数据为脏数据。
5)表格实现:
mysql数据库事务及隔离级别详解_第2张图片

 6)流程总结

读未提交:
     A 开启一个事务
     B开启一个事务
     B事务DML一条数据
     A事务可以查到最新数据
     B提交事务
     A事务依然查到最新的数据
     
    总结,A、B开启事务,不分开启先后顺序,其中一个进行DML操作(还没进行提交操作),另外一个事务就可以看到最新数据(此现象就是传说中的脏读)!
2、已提交读 ( READ COMMITED )不可重复读
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL read committed;//设置隔离属性
1)一个 session
start TRANSACTION
update account set balance = balance -50 where id = 1
2)另外一个 session 中查询 ( 数据并没改变 )
select * from account where id = 1
3)回到第一个 session 中 回滚事务
commit
在第二个 session
select * from account where id = 1 (数据已经改变 )
详情参考
3、可重复读( REPEATABLE READ
  1)  多种查询数据库的隔离级别

 show variables like '%tx_isolation%';

 show variables like 'tx_isolation';
 show variables like 'transaction_isolation';
 SELECT @@tx_isolation;
 SELECT @@transaction_isolation;

 2)如果不是可重复读隔离级别,可以修改隔离级别:

set SESSION TRANSACTION ISOLATION LEVEL repeatable read;

3)A session 中,一个数据库窗口即一个数据库事务中

第一种情况:

start TRANSACTION;
update account set balance = balance -50 where id = 1;

另外一个B session 中查询 (数据并没改变)

select * from account where id = 1

回到第A session 中 提交事务

COMMIT

在B session

select * from account where id = 1

数据已经改变。

第二种情况:演示简略
start TRANSACTION
update account set balance = balance -50 where id = 1
另外一个B  session 中查询 ( 数据并没改变 )
select * from account where id = 1
回到第A  session 中 回滚事务
ROLLBACK
在B  session
select * from account where id = 1(数据没有改变 )
4、可串行化( SERIALIZABLE
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL serializable;
1) 开启一个事务
begin
select * from account 发现 3 条记录
2) 开启另外一个事务
begin
select * from account 发现 3 条记录 也是 3 条记录
insert into account VALUES(4,'deer',500) 发现根本就不让插入
3)  回到第一个事务 commit
4)表格整理:
mysql数据库事务及隔离级别详解_第3张图片

5)流程总结:

可串行化:
     A 开启一个事务
     B开启一个事务
     B执行DML操作,阻塞
     A执行DML操作,操作成功
     A提交或者回滚
     B进行DML操作,可以成功
     
    总结,依次进行,谁先开启事务,谁先进行DML操作,然后提交后,其他事务才可以进行DML操作! 

5、间隙锁( gap 锁)
其实在 mysql 中,可重复读加锁后就解决了幻读问题(借助的就是间隙锁)
mysql数据库事务及隔离级别详解_第4张图片
实验 1
select @@tx_isolation;
create table t_lock_1 (a int primary key);
insert into t_lock_1 values(10),(11),(13),(20),(40);
begin
select * from t_lock_1 where a <= 13 for update;
在另外一个会话中
insert into t_lock_1 values(21) 成功
insert into t_lock_1 values(19) 阻塞
在 RR   隔离级别中者个会扫描到当前值( 13 )的下一个值( 20 , 并把这些数据全部加锁。
实验: 2:
create table t_lock_2 (a int primary key,b int, key (b));
insert into t_lock_2 values(1,1),(3,1),(5,3),(8,6),(10,8);
会话 1
BEGIN
select * from t_lock_2 where b=3 for update;
1 3 5 8 10
1 1 3 6 8
会话 2
select * from t_lock_2 where a = 5 lock in share mode; -- 不可执行,因为 a=5 上有一把记录锁
insert into t_lock_2 values(4, 2); -- 不可以执行,因为 b=2 (1, 3]
insert into t_lock_2 values(6, 5); -- 不可以执行,因为 b=5 (3, 6)
insert into t_lock_2 values(2, 0); -- 可以执行, (2, 0) 均不在锁住的范围内
insert into t_lock_2 values(6, 7); -- 可以执行, (6, 7) 均不在锁住的范围内
insert into t_lock_2 values(9, 6); -- 可以执行
insert into t_lock_2 values(7, 6); -- 不可以执行
二级索引锁住的范围是 (1, 3], 3 6 ),主键索引只锁住了 a=5 的这条记录 [5]

你可能感兴趣的:(mysql)