浅谈Mysql事务隔离级别

一.先说一下什么是事务?

事务就是要保证一组数据库操作,要么全部成功,要么全部失败。

最经典的例子就是银行转账的例子,具体就不展开了。

二.Mysql中的事务隔离级别

在 MySQL 中,事务支持是在引擎层实现的。你现在知道,MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。

你肯定会想到 ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)

这次整理的主要内容是关于mysql的事务隔离级别,那隔离级别都有哪些?

MySQL 中事务的隔离级别一共分为四种,分别如下:

  • 序列化(SERIALIZABLE)
  • 可重复读(REPEATABLE READ)
  • 提交读(READ COMMITTED)
  • 未提交读(READ UNCOMMITTED)

1.查看mysql的事务隔离级别

# MySQL 8.0 之前
SELECT @@global.tx_isolation;
SELECT @@session.tx_isolation;
SELECT @@tx_isolation;

# MySQL8.0
SELECT @@global.transaction_isolation;
SELECT @@session.transaction_isolation;
SELECT @@transaction_isolation;

通过如下命令可以修改隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

注:

如果只是修改了当前 session 的隔离级别,则换一个 session 之后,隔离级别又会恢复到默认的隔离级别,建议开发者在修改时修改当前 session 隔离级别即可,不用修改全局的隔离级别。

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。

  • “读未提交”隔离级别下直接返回记录上的最新值,没有视图概念
  • “串行化”隔离级别下直接用加锁的方式来避免并行访问
  • “可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
  • “读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的

2.数据准备

创建一张测试表

CREATE TABLE `user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(20) NOT NULL COMMENT '姓名',
`balance` int default 0 NOT NULL COMMENT '账户余额',
`create_time` datetime DEFAULT now()  NOT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT now() ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '事务隔离级别测试表';

简单生成两条初始化数据

insert into user(name,balance) values('张三',1000);
insert into user(name,balance) values('李四',1000);

3.1读未提交(READ UNCOMMITTED)

在该隔离级别下,可能读到其他会话未提交事务修改的数据,存在脏读、不可重读读、幻读的问题。

现在看下在读未提交的事务隔离级别下会出现的几种异常读取情况。

3.1.1脏读

一个事务读到另外一个事务还没有提交的数据,称之为脏读

将事务隔离级别调整到READ UNCOMMITTED

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE user set balance=balance+100 where name='张三';
UPDATE user set balance=balance-100 where name='李四';
commit;

A 窗口中的事务,虽然还未提交,但是 B 窗口中已经可以查询到数据的相关变化了,这就是脏读问题。3在B窗口的事务还未提交的时候就可以查到。

3.1.2 不可重复读

不可重复读是指一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。

和脏读的区别在于,脏读是看到了其他事务未提交的数据,而不可重复读是看到了其他事务已经提交的数据(由于当前 SQL 也是在事务中,因此有可能并不想看到其他事务已经提交的数据)。

浅谈Mysql事务隔离级别_第1张图片

3.1.3 幻读

幻读指的是在第一个事务新增一行数据,还没提交事务的时候,第二个事务脏读到这条数据,对数据进行操作后,在第一个事务提交前无法操作成功,以为产生了幻觉。幻读仅专指“新插入的行”,修改的数据不能成为幻读。

浅谈Mysql事务隔离级别_第2张图片

在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。

for update, lock in share mode,update,delete,insert都是当前读的规则,就是读取最新的已经提交的数据。

3.2 读已提交 (READ UNCOMMITTED)

读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据。和 READ UNCOMMITTED 相比,READ COMMITTED 主要解决了脏读的问题,对于不可重复读和幻象读则未解决。

3.3 可重复读 (REPEATABLE READ)

可重复是对比不可重复而言的,上面说不可重复读是指同一事物不同时刻读到的数据值可能不一致。而可重复读是指,事务不会读到其他事务对已有数据的修改,及时其他事务已提交,也就是说,事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。

那么如何解决幻读问题呢?

产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB 引入新的锁,也就是间隙锁 (Gap Lock)。

我们知道innodb是支持行锁的,实际上行锁就是对主键或者索引进行上锁,如果一个update操作的条件是唯一索引或者主键,那么只会进行一个行级锁。

如果update的操作是普通索引,再加上可重复读的事务隔离级别,结合我们说的间隙锁,也就是说这时候,在一行行扫描的过程中,不仅将给行加上了行锁,还给行两边的空隙,也加上了间隙锁。

在实现上,间隙锁是一个前开后闭的区间锁,只要在当前事务中,任何一个在这区间对数据进行的操作都会被锁住。

虽然幻读可以用以上方式解决,但是会带来性能上的损耗,最直观的就是在事务提交前的等待,整个区间内的数据操作都会被锁住,这对于业务来说很明显是难以接受的。间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。

间隙锁是在可重复读隔离级别下才会生效的。所以,你如果把隔离级别设置为读提交的话,就没有间隙锁了。但同时,你要解决可能出现的数据和日志不一致问题,需要把 binlog 格式设置为 row。这也是现在不少公司使用的配置组合。

如果读提交隔离级别够用,也就是说,业务不需要可重复读的保证,这样考虑到读提交下操作数据的锁范围更小(没有间隙锁),这个选择是合理的。

3.4串行化 SERIALIZABLE

SERIALIZABLE 提供了事务之间最大限度的隔离,在这种隔离级别中,事务一个接一个顺序的执行,不会发生脏读、不可重复读以及幻象读问题,最安全。

如果设置当前事务隔离级别为 SERIALIZABLE,那么此时开启其他事务时,就会阻塞,必须等当前事务提交了,其他事务才能开启成功,因此前面的脏读、不可重复读以及幻象读问题这里都不会发生。

3.5 小结

以下是对不同事务隔离级别对脏读、不可重复度、幻读的整理。

隔离级别

脏读

不可重复读

幻读

读未提交

允许

允许

允许

读已提交

不允许

允许

允许

可重复度

不允许

不允许

允许

串行化

不允许

不允许

不允许

以上内容,希望可以对你有所帮助。

你可能感兴趣的:(Java基础,mysql,数据库,java)