有道无术,术尚可求,有术无道,止于术。
本系列Spring Boot 版本 3.1.0
本系列Seata 版本 2.0.0
源码地址:https://gitee.com/pearl-organization/study-seata-demo
文章目录
- 1. 概述
- 2. ACID 模型
- 2.1 原子性
- 2.2 一致性
- 2.3 隔离性
- 2.3.1 事务并发异常
- 2.3.1.1 第一类更新丢失(回滚丢失)
- 2.3.1.2 第二类更新丢失(覆盖丢失)
- 2.3.1.3 脏读
- 2.3.1.4 不可重复读
- 2.3.1.5 幻读
- 2.3.2 隔离级别
- 2.3.2.1 读未提交 Read uncommitted
- 2.3.2.2 读已提交 Read committed
- 2.3.2.3 可重复度 Repeatable read
- 2.3.2.4 序列化 Serializable
- 2.4 持久性
1. 概述
在计算机术语中,事务(Transaction
)是指多个数据库操作组成一个不可分割的执行单元,这些操作要么全部成功,要么全部失败,不会结束在中间的某个环节,同时在这些操作执行后,需要保证数据可靠且一致。
例如在MySql
中使用事务:
-- 开启事务
START TRANSACTION;
-- 一些列操作
INSERT INTO user(username,password) VALUES ('root','123456'); -- 插入
UPDATE t_user SET username=’cangJJ’ WHERE id=1; -- 更新-- 提交事务
COMMIT;
2. ACID 模型
ACID
模型是指数据库中的事务必须满足原子性(Atomicity
)、一致性(Consistency
)、隔离性(Isolation
)、持久性(Durability
)四大特性,是数据库设计的基本原则,以保证数据的可靠一致,即便在软件崩溃、硬件故障的情况下,数据也不会被破坏。
其中原子性、隔离性、持久性是为一致性服务的,即AID
是手段,C
是最终实现的目标。
2.1 原子性
原子性也可称为不可分割性,是说事务操作必须是原子工作单元,一个事务中的操作要么全部都执行成功,要么全部都回滚撤销执行,不会存在部分成功或失败的情况。
例如经典的银行转账场景中,转账操作可以分为以下两步:
- 从
A
账户中扣除金额 - 将扣除金额存入到
B
账户
整个转账操作过程可以看作是一个事务,原子性要求成功则全部成功,失败则需要全部撤消。如果第一步成功,第二步失败,整个操作应该取消,若不取消第一步的操作,则会发生丢失款项的问题。
2.2 一致性
一致性是指一个事务执行之前和执行之后都必须处于一致性状态。
2.3 隔离性
隔离性是指当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
2.3.1 事务并发异常
数据库是要被共享访问的,那么在并发操作数据库过程中很可能出现以下很多异常情况。
2.3.1.1 第一类更新丢失(回滚丢失)
比如下图中,T1
、T2
同时执行,由于没有事务隔离,虽然T1
更新成功,但是T2
进行了回滚,余额却没有变化,这种因为回滚导致另外一个事务操作丢失的情况叫做回滚丢失。
2.3.1.2 第二类更新丢失(覆盖丢失)
比如下图中,T1
、T2
同时执行,由于没有事务隔离,T1
、T2
都提交了事务,但是由于T2
后执行,将T1
事务的操作覆盖了,导致更新丢失问题。
2.3.1.3 脏读
脏读是因为一个事务读取了另一个事务修改了但是未提交的数据。
比如下图中,T1
修改余额为1100
,此时T2
开始事务查询到的结果为1100
,由于读取到了未提交的事务,当T1
回滚时,T2
还在脏数据上操作,会导致结果错误。
2.3.1.4 不可重复读
不可重复读是指一个事务对同一行数据执行了两次或更多次查询,但是却得到了不同的结果。
比如下图中,在T2
中,多次查询的不一致,如果这个时候修改,就会造成数据错误。
2.3.1.5 幻读
幻读和不可重复读有点像,两者都表现为两次读取的结果不一致,只是针对的不是数据的值而是数据的数量。不可重复读重点在于update
和delete
,而幻读的重点在于insert
。
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql
第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert
的数据,所以当事务A
先前读取了数据,或者修改了全部数据,事务B
还是可以insert
数据提交,这时事务A
就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。
2.3.2 隔离级别
数据并发访问所产生的问题,在有些场景下可能是允许的,但是有些场景下可能就是致命的,数据库通常会通过锁机制来解决数据并发访问问题,
直接使用锁是非常麻烦的,为此数据库为用户提供了自动锁机制,只要用户指定会话的事务隔离级别,数据库就会通过分析SQL
语句然后为事务访问的资源加上合适的锁,此外,数据库还会维护这些锁通过各种手段提高系统的性能,这些对用户来说都是透明的。ANSI/ISO SQL 92标准
定义了4
个等级的事务隔离级别,如下表所示:
事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。
可以通过以下SQL 查询和修改会话级别:
-- 查询数据库版本
SELECT VERSION();
-- 查询当前会话事务界别
SELECT @@session.tx_isolation;
-- 设置当前会话事务隔离级别,仅仅该窗口会话有效
set session tx_isolation='read-uncommitted';
2.3.2.1 读未提交 Read uncommitted
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据,这个级别,几乎没有任何隔离性,一般不会采用这个隔离级别。
2.3.2.2 读已提交 Read committed
读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。另外一个事务不能读取该事务未提交的数据。
在Navicat 中,打开两个会话窗口,修改隔离级别为读已提交:
-- 设置当前会话事务隔离级别,仅仅该窗口会话有效
set session tx_isolation='Read-committed';
开启事务,将当前账户余额扣掉100,不提交事务:
START TRANSACTION;
UPDATE account_tbl
SET money = money - 100
WHEREid = "11111111";
在另外一个窗口,查询当余额:
SELECT*
FROMaccount_tbl
WHEREid = "11111111";
可以看到读取到的余额仍为1000,说明该隔离级别下,事务读取不到另外一个事务未提交的数据。
分析:这就是读已提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但是存在不可重复读问题,一旦事务提交后,另外的事务再次查询,查询结果会不一致。
2.3.2.3 可重复度 Repeatable read
可重复读,就是在开始读取数据(事务开启)时,多次读取到的结果是一致的,这是Mysql 默认的事务隔离级别。
比如我们先开启一个事务,读取数据,此时查询结果为1000:
另外一个事务,修改当前数据,并提交事务:
查询事务中,再次查询,发现余额没有变,这就是可重复读,Repeatable Read的确可以解决“不可重复读”的问题。
2.3.2.4 序列化 Serializable
Serializable是最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。
虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。
2.4 持久性
持久性是指事务一旦被提交,对数据库中的数据的改变就是永久性的,不会因为任何情况回到原来的状态。