有道无术,术尚可求,有术无道,止于术。
本系列Spring Boot 版本 3.1.0
本系列Seata 版本 2.0.0
源码地址:https://gitee.com/pearl-organization/study-seata-demo
文章目录
- 数据库事务
- 什么是事务
- 事务的作用
- 数据库事务模型
- 显式事务
- 隐式事务
- 自动事务
- 事务的4大特性
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
- 事务并发异常
- 1.第一类更新丢失(回滚丢失)
- 2.第二类更新丢失(覆盖丢失)
- 3.脏读
- 4.不可重复读
- 5.幻读
- 事务隔离级别
- 1. 读未提交 Read uncommitted
- 2. 读已提交 Read committed
- 3. 可重复度 Repeatable read
- 4. 序列化 Serializable
数据库事务
什么是事务
事务(Transaction)
一般是指要做的或所做的事情。数据库事务( transaction)
是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
事务
由事务开始
与事务结束
之间执行的全部数据库操作组成。在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序流程。
事务
保证一系列数据库操作要么全部成功,要么全部失败,如果某一步出现了异常,数据就会回滚,把之前的操作撤销。
比如,我们去银行转账,操作可以分为下面两个环节:
- 从第一个账户划出款项。
- 将款项存入第二个账户。
在这个过程中,两个环节是关联的。第一个账户划出款项必须保证正确的存入第二个账户,如果第二个环节没有完成,整个的过程都应该取消,否则就会发生丢失款项的问题。整个交易过程,可以看作是一个事务,成功则全部成功,失败则需要全部撤消,这样可以避免当操作的中间环节出现问题时,产生数据不一致的问题。
在MYSQL 中,可以查看哪些引擎是支持事务的。
事务的作用
一个数据库事务通常包含了一个序列的对数据库的读/写操作。它的存在包含有以下两个目的:
- 为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
- 当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
当事务被提交给了DBMS(数据库管理系统),则DBMS(数据库管理系统)需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。
但在现实情况下,失败的风险很高。在一个数据库事务的执行过程中,有可能会遇上事务操作失败、数据库系统/操作系统失败,甚至是存储介质失败等情况。这便需要DBMS对一个执行失败的事务执行恢复操作,将其数据库状态恢复到一致状态(数据的一致性得到保证的状态)。为了实现将数据库状态恢复到一致状态的功能,DBMS通常需要维护事务日志以追踪事务中所有影响数据库数据的操作。
数据库事务模型
显式事务
显式事务
又称自定义事务,是指用显式的方式定义其开始和结束的事务,当使用start transaction
和 commit
语句时则表示发生显式事务。
隐式事务
隐式事务
是指每一条数据操作语句都自动地成为一个事务,事务的开始是隐式的,事务的结束有明确的标记。即当用户进行数据操作时,系统自动开启一个事务,事务的结束则需手动调用 commit或 rollback语句来结束当前事务,在当前事务结束后又自动开启一个新事务。
自动事务
自动事务
是指能够自动开启事务并且能够自动结束事务。在事务执行过程中,如果没有出现异常,事务则自动提交;当执行过程产生错误时,则事务自动回滚。
事务的4大特性
事务需要满足四大特性(简称ACID),才能保证数据正确性。
原子性(Atomicity)
事务必须是原子工作单元,对于其数据修改,要么全都执行,要么全都不执行。如果只执行一半,则之前对数据库所做的操作将不会执行。
一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,发生数据库因为故障而没有执行事务的重大错误。
事务并发异常
数据库是要被共享访问的,那么在并发操作数据库过程中很可能出现以下几种异常情况。
1.第一类更新丢失(回滚丢失)
比如下图中,T1、T2同时执行,由于没有事务隔离,虽然T1更新成功,但是T2进行了回滚,余额却没有变化,这种因为回滚导致另外一个事务操作丢失的情况叫做回滚丢失。
2.第二类更新丢失(覆盖丢失)
比如下图中,T1、T2同时执行,由于没有事务隔离,T1、T2都提交了事务,但是由于T2后执行,将T1事务的操作覆盖了,导致更新丢失问题。
3.脏读
脏读是因为一个事务读取了另一个事务修改了但是未提交的数据。
比如下图中,T1修改余额为1100,此时T2开始事务查询到的结果为1100,由于读取到了未提交的事务,当T1回滚时,T2还在脏数据上操作,会导致结果错误。
4.不可重复读
不可重复读是指一个事务对同一行数据执行了两次或更多次查询,但是却得到了不同的结果。
比如下图中,在T2中,多次查询的不一致,如果这个时候修改,就会造成数据错误。
5.幻读
幻读和不可重复读有点像,两者都表现为两次读取的结果不一致,只是针对的不是数据的值而是数据的数量。不可重复读重点在于update和delete,而幻读的重点在于insert。
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。
事务隔离级别
数据并发访问所产生的问题,在有些场景下可能是允许的,但是有些场景下可能就是致命的,数据库通常会通过锁机制来解决数据并发访问问题,
直接使用锁是非常麻烦的,为此数据库为用户提供了自动锁机制,只要用户指定会话的事务隔离级别,数据库就会通过分析SQL语句然后为事务访问的资源加上合适的锁,此外,数据库还会维护这些锁通过各种手段提高系统的性能,这些对用户来说都是透明的。ANSI/ISO SQL 92标准
定义了4个等级的事务隔离级别,如下表所示:
事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。所以要根据具体的应用来确定合适的事务隔离级别,这个地方没有万能的原则。
可以通过以下SQL 查询和修改会话级别:
-- 查询数据库版本
SELECT VERSION();
-- 查询当前会话事务界别
SELECT @@session.tx_isolation;
-- 设置当前会话事务隔离级别,仅仅该窗口会话有效
set session tx_isolation='read-uncommitted';
1. 读未提交 Read uncommitted
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据,这个级别,几乎没有任何隔离性,一般不会采用这个隔离级别。
2. 读已提交 Read committed
读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。另外一个事务不能读取该事务未提交的数据。
在Navicat 中,打开两个会话窗口,修改隔离级别为读已提交:
-- 设置当前会话事务隔离级别,仅仅该窗口会话有效
set session tx_isolation='Read-committed';
开启事务,将当前账户余额扣掉100,不提交事务:
START TRANSACTION;
UPDATE account_tbl
SET money = money - 100
WHERE
id = "11111111";
在另外一个窗口,查询当余额:
SELECT
*
FROM
account_tbl
WHERE
id = "11111111";
可以看到读取到的余额仍为1000,说明该隔离级别下,事务读取不到另外一个事务未提交的数据。
分析:这就是读已提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但是存在不可重复读问题,一旦事务提交后,另外的事务再次查询,查询结果会不一致。
3. 可重复度 Repeatable read
可重复读,就是在开始读取数据(事务开启)时,多次读取到的结果是一致的,这是Mysql 默认的事务隔离级别。
比如我们先开启一个事务,读取数据,此时查询结果为1000:
另外一个事务,修改当前数据,并提交事务:
查询事务中,再次查询,发现余额没有变,这就是可重复读,Repeatable Read的确可以解决“不可重复读”的问题。
4. 序列化 Serializable
Serializable是最严格的隔离级别。在Serializable隔离级别下,所有事务按照次序依次执行,因此,脏读、不可重复读、幻读都不会出现。
虽然Serializable隔离级别下的事务具有最高的安全性,但是,由于事务是串行执行,所以效率会大大下降,应用程序的性能会急剧降低。如果没有特别重要的情景,一般都不会使用Serializable隔离级别。