MySql数据库事务隔离级别

104 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情

简介

在进入正题之前我们先简单了解一下什么是数据库事务,数据库事务是指在数据库一系列SQL操作中,这个流程中的任务要么全部完成,要么全部失败,并且数据库事务必须满足ACID特性。

事务的特性

数据库事务存在四种特性,分别为:原子性(Atomic)、一致性(Consistent)、隔离性(Isolation)、持久性(Durable),即ACID特性。

原子性:事务即是一个工作单元,在数据库事务中需要保证当前事务的数据要么全部提交,要么全部回滚。

例如网上购买一个商品,商家收到订单后开始发货,经过一系列的物流运输最终到客户手里,客户可以选择签收或者拒收,拒收货物就会退回到商家,那么这整个过程要么交易完成,要么拒收退回。

一致性:事务必须保证数据从一个状态转变至另一个状态时,开始的数据与最终的数据必须是一致的(这里的一致指的不是数据一样,参考以下例子)

例如有一个彩票站一共有100张刮刮乐,我们去购买10张,那么就会有一笔出售10张的订单,库存就剩下90张刮刮乐,也就是这个购买前后刮刮乐的总量都是100一致不变的。

隔离性:在多个事务并发使用同一个数据库资源时,事务在运行过程中会根据隔离级别体现出不同的影响程度,后面我们会更详细的进行解说。

好比几个货运人员在同时搬一批货,货物搬运的过程中他们之间会不会出现小插曲,最终导致的结果也就无法定论,这就需要依靠我们的隔离级别来决定。

持久性:每一个事务操作执行完成后(提交事务),最终数据的变化结果都将持久化记录到磁盘上,不可再进行回滚操作。

事务隔离级别

数据库事务隔离级别分为四种:读未提交(Read Uncommited)、可重复读(Repeatable Read)、读已提交(Read Commited)、串行化(Serializable)。

MySql数据库默认使用的是可重复读,Oracle默认使用的是读已提交,这四种隔离级别都有什么不同,应该如何使用接下来做进一步分析。

不同隔离级别的影响程度

隔离级别\影响程度脏读不可重复读幻读
读未提交
读未提交
可重复读
串行化

脏读:事务A在执行过程中,事务B对同一数据进行更新操作,此时事务B未执行完,事务A即可读取到事务B未提交的数据进行运算,此时事务B在后面流程仍可能对数据进行修改或者回滚,最终将造成事务A读取的数据为脏数据。

不可重复度:多个事务并发处理同一数据的过程中,事务A第一次读取数据data1,事务B同时对数据data1进行更新并提交事务,事务A再次读取data1时与最初读取的数据不一致,也就是并发过程中事务的一致性被破坏。

幻读:与不可重复读类似,一个事务按照相同的条件读取数据时,发现其他事务为满足各自业务诉求插入新数据,这种情况就称为幻读。

例如一个商家系统挂单出售商品库存为100,假设被购买掉10个后库存剩余90,此时客户A再次购买一个商品,而客户B对购买的一个商品不满意进行退款,此时库存既减1又加1,最终结果库存量还是一致的,但是实际情况出现了退货的商品,这种场景就称之为幻读。

事务隔离级别配置

为了方便大家更好的理解,我们先将看看怎么配置事务隔离级别,再根据具体的隔离级别结合数据进行分析。

  • 通过配置文件配置 在我们mysql的安装目录都会有一个my.ini数据库配置文件,在文件里添加transaction-isolation = READ-COMMITTED即可配置默认隔离级别为读已提交。具体大家可以根据需要进行更改隔离级别配置:READ-UNCOMMITED、READ-COMMITED、REPEATABLE-READ、SERIALIZABLE,需注意更改配置文件后数据库必须要重启后才能生效。

image.png

  • 使用命令进行设置
-- 设置全局事务隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置当前session事务隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  • 查看当前事务隔离级别
-- 查看全局事务隔离级别
SELECT @@GLOBAL.transaction_isolation;
-- 查看当前session事务隔离级别
SELECT @@SESSION.transaction_isolation;

四种事务隔离级别

这里我们先创建一个学生信息表,并初始化数据以协助分析:

DROP TABLE IF EXISTS t_student;
CREATE TABLE `t_student`(  
  `id` BIGINT(12) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(30) COMMENT '姓名',
  `number` VARCHAR(12) COMMENT '学号',
  `height` INT COMMENT '身高cm',
  `weight` INT COMMENT '体重kg',
  PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;

INSERT INTO t_student(`name`, number, height, weight)
VALUES
('张三', '20230010001', 0, 0),
('李四', '20230010002', 0, 0),
('王五', '20230010003', 0, 0);

image.png

读未提交

修改事务隔离级别为读未提交:SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

读未提交也就是说多个事务并发的时候,事务修改数据但未进行提交的操作对其他事务来说是可见的,由此我们来验证一下。

模拟事务并发,我们使用两个数据库连接并开启事务START TRANSACTION

image.png

image.png

在事务A中查看学生信息如下:

image.png

在事务B对学生(张三)进行数据更新如下:

image.png

此时事务B数据并未进行提交,这时候我们再去事务A查看学生数据:

image.png

这时我们就会发现事务A读取到了事务B未提交的数据,这种情况就发生了读未提交。如果此时学生(张三)的数据未被事务B提交,而此时事务A将此数据输出作为学生信息的实际结果,然后事务B再将数据进行回滚,结果就导致事务A的数据出现了脏读。

image.png

image.png

读已提交

修改事务隔离级别为读已提交:SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED

同上初始化后的学生数据,开启两个事务进行分析:

image.png

image.png

同样事务B对学生(李四)进行数据更新:

image.png

在事务B未进行数据提交时,事务A读取的数据仍为原来更新前的数据:

image.png

当事务B进行commit提交数据后,事务A再次刷新查看数据:

image.png

image.png

可重复读

修改事务隔离级别为读已提交:SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ

同时开启两个事务,初始数据情况如下:

image.png

image.png

使用事务A对学生(王五)进行数据修改并进行数据提交如下:

image.png

此时在事务B中再次刷新查看学生数据:

image.png

这时候我们发现事务B查询到的数据跟原来事务开始时的一样,这就说明了在一个事务中数据是可重复读,其他事务数据提交与否并不会重新读取最新数据。

串行化

修改事务隔离级别为读已提交:SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE

可串行化指的是数据库每个事务独立执行,每个SQL的执行都会枷锁,让所有的数据操作都是串行的。

总结

不同数据库事务隔离级别产生的问题,都是由于事务并发操作同一数据出现的问题,所以在单个事务运行时或者隔离级别为串行化时就不会出现此问题,但是限制系统只允许单事务运行时将会大大降低系统并发能力,一般系统都会采用数据库连接池,所以还是得根据实际业务场景充分考虑后进行设计。