目录
- 深入理解分布式事务① ---->分布式事务基础(四大特性、五大类型、本地事务、MySQL并发事务问题、MySQL事务隔离级别命令设置)详解
- 事务的基本概念
- 1、什么是事务?
- 2、事务的四大特性
- 2-1:原子性(Atomic)
- 2-2:一致性(Consistency)
- 2-3:隔离性(Isolation)
- 2-4:持久性(Durability)
- 3、事务的五大类型
- 3-1:扁平事务
- 3-2:带有保存点的扁平事务
- 3-3:链式事务
- 3-4:嵌套事务
- 3-5:分布式事务
- 4、本地事务
- 4-1:什么是本地事务?
- 4-2:本地事务的典型特征
- 4-3:本地事务的执行流程
- 4-4:本地事务的优缺点
- 优点
- 缺点
- 5、MySQL 事务基础
- 5-1:并发事务带来的问题
- 5-1-1:更新丢失(脏写)
- 5-1-2:脏读
- 5-1-3:不可重复读
- 5-1-4:幻读
- 5-2:不可重复读和幻读的区别
- 5-3:MySQL 的 4 种事务隔离级别
- 5-3-1:设置事务隔离级别
- 配置文件设置
- 命令设置
- 注意点
- session 会话级别的设置演示
- global 全局级别的设置演示
- 具体设置命令
- 5-3-2:查询当前数据库的事务隔离级别
- 查询 MySQL 的事务隔离级别的命令
- 5-3-3:MySQL 中 4 种事务隔离级别的区别
深入理解分布式事务① ---->分布式事务基础(四大特性、五大类型、本地事务、MySQL并发事务问题、MySQL事务隔离级别命令设置)详解
事务的基本概念
1、什么是事务?
事务一般指的是逻辑上的一组操作,或者作为单个逻辑单元执行的一系列操作。
同属于一个事务的操作会作为一个整体提交给系统,这些操作要么全部执行成功,要么全部执行失败。
数据库的事务在实现时,会将一次事务中包含的所有操作全部封装成一个不可分割的执行单元,这个单元中的所有操作要么全部执行成功,要么全部执行失败。
只要其中任意一个操作执行失败,整个事务就会执行回滚操作。
2、事务的四大特性
原子性、一致性、隔离性、持久性
2-1:原子性(Atomic)
事务的原子性指的是构成事务的所有操作要么全部执行成功,要么全部执行失败,不可能出现部分执行成功,部分执行失败的情况。
例如:在转账业务中,小明 向 小白转账100元,那么小明账户会减少100元,小白账户会多出100元。在开启事务的情况下,这两个操作要么全部执行成功,要么全部执行失败。不能说小明账户减少100元,但是小白账户没有多出100元的操作。
2-2:一致性(Consistency)
事务的一致性指的是在事务执行之前和执行之后,数据始终处于一致的状态。
例如:在转账业务中,小明有500元,小白有200元,小明向小白转账100元,无论是转账前还是转账后,两人的总额一定得是700元,这个就是数据处于一致的状态。
2-3:隔离性(Isolation)
事务的隔离性指的是并发执行的两个事务之间互不干扰。也就是说,一个事务在执行过程中不能看到其他事务运行过程中的中间状态。
例如,在转账业务中,小明向小白转账的业务场景中,存在两个并发执行的 事务A 和 事务B ,事务A 执行扣减小明账户余额的操作和增加小白账户余额的操作;事务B 执行查询小明账户余额的操作。
在事务A完成之前,事务B读取的小明的账户余额仍然为扣减之前的账户余额500元,不会读取到扣减后的账户余额400元。
就是只有事务A执行完,事务B才能读取到小明最新的账户余额情况,否则只能读取到事务A执行之前的账户余额情况。
2-4:持久性(Durability)
事务的持久性指的是事务提交完成后,此事务对数据的更改操作会被持久化到数据库中,并且不会被回滚。
例如,在转账业务中,小明向小白转100元,在整个转账事务提交完成后,这种对数据的修改操作(两人的最新余额数据)就会被持久化到数据库中,且不会被回滚。
3、事务的五大类型
事务主要分为五大类,分别为:扁平事务、带有保存点的扁平事务、链式事务、嵌套事务、分布式事务。
3-1:扁平事务
扁平事务是事务操作中最常见的,也是最简单的事务。
在数据库中,扁平事务通常由 begin 或者 start transaction 字段开始,由 commit 或者 rollback 字段结束。
在这之间的所有操作要么全部执行成功,要么全部执行失败(回滚)。
当今主流的数据库都支持扁平事务。
扁平事务虽然是最常见、最简单的事务,但是无法提交或者回滚整个事务中的部分事务,只能把整个事务全部提交或者回滚。
为了解决这个问题,带有保存点的扁平事务出现了。
3-2:带有保存点的扁平事务
简单来说,内部设置了保存点的扁平事务,就是带有保存点的扁平事务。
带有保存点的扁平事务通过在事务内部的某个位置设置保存点(savepoint),达到将当前事务回滚到此位置的目的。
示例:
从本质上将,普通的扁平事务也是有保存点的,只是普通的扁平事务只有一个隐式的保存点,并且这个隐式的保存点会在事务启动的时候,自动设置在当前事务的开始位置。
也就是说,普通的扁平事务具有保存点,而且默认是存在事务的开始位置。
3-3:链式事务
链式事务是在带有保存点的扁平事务的基础上,自动将当前事务的上下文隐式的传递给下一个事务。
也就是说,一个事务的提交操作和下一个事务的开始操作具备原子性,上一个事务的处理结果对下一个事务是可见的,事务与事务之间就像链条一样传递下去。
注意:
链式事务在提交的时候,会释放要提交的事务中的所有锁和保存点,也就是说,链式事务的回滚操作只能回滚到当前所在事务的保存点,而不能回滚到已提交事务的保存点。
3-4:嵌套事务
嵌套事务就是有多个事务处于嵌套状态,共同完成一项任务的处理,整个任务具备原子性。
嵌套事务最外层有一个顶层事务,这个顶层事务控制着所有的内部子事务,内部子事务提交完成后,整体事务并不会提交,只有最外层的顶层事务提交完成后,整体事务才算提交完成。
注意:
1、回滚嵌套事务内部的子事务时,会将事务回滚到外部顶层事务的开始位置。
2、嵌套事务的提交是从内部的子事务向外依次进行的,直到最外层的顶层事务提交完成。
3、回滚嵌套事务最外层的顶层事务时,会回滚嵌套事务包含的所有事务,包括已提交的内部子事务。
在主流的关系型数据库中,MySQL 不支持原生的嵌套事务,而 SQL Server 支持。
一般不建议用嵌套事务。
3-5:分布式事务
分布式事务指的是事务的参与者、事务所在的服务器、涉及的资源服务器以及事务管理器等分别位于不同分布式系统的不同服务或数据库节点上。
简单来说,分布式事务就是一个在不同环境(比如不同的数据库、不同的服务器)下运行的整体事务。
这个整体事务包含一个或者多个分支事务,并且整体事务中的所有分支事务要么全部提交成功,要么全部提交失败。
例如:
在电商系统的下单减库存业务中,订单业务所在的数据库为事务A的节点,库存业务所在的数据库为事务B的节点。
事务A和事务B组成了一个具备ACID特性的分布式事务,要么全部提交成功,要么全部提交失败。
4、本地事务
4-1:什么是本地事务?
在常见的计算机系统和应用系统中,很多事务是通过关系型数据库进行控制的。
这种控制事务的方式利用数据库本身的事务特性来实现,而在这种实现方式中,数据库和应用通常会被放在同一台服务器中,因此,这种基于关系型数据库的事务也可以称作本地事务或者传统事务。
4-2:本地事务的典型特征
1、一次事务过程中只能连接一个支持事务的数据库,这里的数据库一般指的是关系型数据库。
2、事务的执行结果必须满足 ACID 特性。
3、事务的执行过程会用到数据库本身的锁机制。
4-3:本地事务的执行流程
资源管理器: 是指负责管理数据库或系统中各种资源(内存、磁盘、锁、日志)的组件或模块。
如图:
1、客户端开始事务操作之前,需要开启一个连接会话;
2、开始会话后,客户端发起开启事务的指令;
3、事务开启后,客户端发送各种 SQL 语句处理数据;
4、正常情况下,客户端会发起提交事务的指令,如果发生异常情况,客户端会发起回滚事务的指令;
5、上述流程执行完成后,关闭会话。
本地事务是由资源管理器在本地(指单个节点)进行管理的。
比如:分布式系统由多个节点组成,每个节点都有自己的数据库实例和资源管理器,那么在每个节点上执行的事务就是本地事务。
4-4:本地事务的优缺点
优点
1、支持严格的 ACID 特性,这也是本地事务得以实现的基础。
2、事务可靠,一般不会出现异常情况。
3、本地事务的执行效率比较高。
4、事务的状态可以只在数据库中进行维护,上层的应用不必理会事务的具体状态。
5、应用的编程模型比较简单,不会涉及复杂的网络通信。
缺点
1、不具备分布式事务的处理能力。
2、一次事务过程中只能连接一个支持事务的数据库,即不能用于多个事务性数据库。
5、MySQL 事务基础
在互联网领域,MySQL 数据库是使用最多的关系型数据库之一,也是一种典型的支持事务的关系型数据库。
5-1:并发事务带来的问题
数据库一般会并发执行多个事务,而多个事务可能会并发的对相同的数据进行增、删、改、查的操作,进而导致并发事务问题。
并发事务带来的问题包括:更新丢失(脏写)、脏读、不可重复读、幻读。
5-1-1:更新丢失(脏写)
简单来说,就是并发情况下,后提交的事务可能会覆盖前一个事务提交的数据。
详细解释:
当两个或两个以上的事务选择数据库中的同一行数据,并基于最初选定的值更新该行数据时,因为每个事务之间都无法感知彼此的存在,所以会出现最后的更新操作覆盖之前由其他事务完成的更新操作的情况。
也就是说,对于同一行数据,一个事务对该行数据的更新操作覆盖了其他事务对该行数据的更新操作。
例如:
小白有500元,此时有 事务A 和 事务B 两个事务,事务A 要给小白转账300元,事务B 要给小白转账200元。一开始,两个事务读取到小白的余额都是500元,然后分别执行转账操作,假如 事务A 比 事务B 先提交事务,但是 事务A 和 事务B 都提交后的结果,是小白的账户余额只有 700 元。
也就是说,后提交的事务B 覆盖了 事务A 的更新操作
更新丢失(脏写)本质上是写操作的冲突,解决方法是让每个事务按照串行的方式执行,按照一定的顺序依次进行写操作
5-1-2:脏读
简单来说,就是一个事务读取了另一个事务未提交的数据。
详细解释:
一个 事务A 正在对数据库中的一条记录进行修改操作,在这个 事务A 完成并提交之前,当有另一个 事务B 来读取正在修改的这条数据记录时,如果没有对这两个事务进行控制,则 事务B 就会读取到没有被提交的脏数据,并根据这些脏数据做进一步的处理,此时就会产生未提交的数据依赖关系。
这种现象称为脏读,也就是一个事务读取了另一个事务未提交的数据。
例如:
当前有 事务A 和 事务B 两个事务,小白账户有 500 元,然后 事务A 向小白转账100元,事务B 是查询小白的账户余额。
事务A 执行转账操作,在事务A 未提交时,事务B 查询到了小白账户多了100元,也就是600元,后来事务A因为某些原因,例如 服务超时、系统异常等因素进行了回滚操作,但事务B查询到的数据并没有改变,依然是 600 元。
此时事务B查询到的600元数据就是脏数据。
脏读本质上是读写操作的冲突,解决办法是先写后读,也就是写完之后再读。
5-1-3:不可重复读
简单来说,就是只有在同一个事务内,使用相同的查询语句,在不同时刻读取到的结果是不一致的。
一个事务读取了某些数据,在一段时间后,这个事务再次读取之前读过的数据,此时发现读取的数据发生了变化,或者其中的某些记录已经被删除,这种现象就叫做不可重复读。
即同一个事务,使用相同的查询语句,在不同时刻读取到的结果是不一致的。
例如:
当前有事务A 和事务B两个事务,事务A 向小白转账100元,事务B查询小白的账户余额。
事务B第一次查询时,事务A还没有转账,第二次查询时,事务A已经转账成功,此时,就会导致事务B两次查询结果不一致。
不可重复读本质上也是读写操作的冲突,解决方法是先读后写,也就是读完之后再写。
5-1-4:幻读
简单来说,一个事务两次读取一个范围的数据记录,两次读取到的结果不同
一个事务按照相同的查询条件重新读取之前读过的数据,此时发现其他事务插入了满足当前事务查询条件的新数据,这种现象叫做幻读。
即一个事务两次读取一个范围的数据记录,两次读取到的结果不同。
例如:
当前有事务A 和事务B两个事务,事务A 向小白转账100元,事务B查询小白的账户余额。
事务B第一次查询时,事务A还没有转账,第二次查询时,事务A已经转账成功,此时,就会导致事务B两次查询结果不一致。(和不可重复读的例子一样)
幻读本质上也是读写操作的冲突,解决方法是先读后写,也就是读完之后再写。
5-2:不可重复读和幻读的区别
1、 不可重复读的重点是更新和删除操作,而幻读的重点在于插入操作。
不可重复读,两次读取到不同的数据,是因为该数据被其他事务进行了更新或者删除操作;
比如在同一个事务内,我第一次查询年龄等于20的人叫小黄,相同查询条件下,过一会再查询,发现这次查年龄等于20的人叫小小黄。两次查询中间,有其他事务修改了这个年龄等于20的人的名字。
幻读,两次读取到不同的数据,是因为该数据被其他事务进行了插入操作;
比如在同一个事务内,我第一次查询到10条数据,相同查询条件下,过一会再查询,发现这次查出了11条数据。两次查询中间,有其他事务插入了一条符合查询条件的新数据。
2、 使用锁机制实现事务隔离级别时,在可重复读隔离级别中,SQL 语句第一次读取到数据后,会将相应的数据加锁,使得其他事务无法修改和删除这些数据,此时可以实现可重复读;
注意:这种方法无法对新插入的数据加锁,因此这种加锁的方法无法阻止幻读。
如果事务A 读取了数据,或者修改和删除了数据,此时,事务B 还是依然可以进行插入操作,这就会导致事务A莫名其妙多出一条之前没有的数据,这个就是幻读。
3、 幻读无法通过行级锁来避免,需要使用串行化的事务隔离级别,但是这种事务隔离级别会极大的降低数据库的并发能力。
4、 从本质上讲,不可重复读和幻读的最大区别在于如何通过锁机制解决问题。
另外,除了可以使用悲观锁来避免不可重复读和幻读的问题之外,我们也可以使用乐观锁来处理。
例如:MySQL、Oracle 和 PostgreSQL 等数据库为了提高整体性能,就使用了基于乐观锁的 MVCC(多版本并发控制)机制来避免不可重复读和幻读。
5-3:MySQL 的 4 种事务隔离级别
MySQL 中的 InnoDB 存储引擎提供 SQL 标准所描述的 4 种事务隔离级别:
级别从低到高为:
读未提交(Read Uncommitted)-> 读已提交(Read Committed)-> 可重复读(Repeatable Read)-> 串行化(Serializable)
InnoDB 默认的事务隔离级别是:可重复读
5-3-1:设置事务隔离级别
配置文件设置
可以在命令行用 --transaction-isolation 选项或者在 MySQL 的配置文件 my.cnf、my.ini 里,为所有连接设置默认的事务隔离级别。
例如,可以在 my.cnf 或者 my.ini 文件中的 mysqld 节点下面配置如下选项:
transaction-isolation = {Read Uncommitted} #设置为读未提交的事务隔离级别
transaction-isolation = {Read Committed} #设置为读已提交的事务隔离级别
transaction-isolation = {Repeatable Read} #设置为可重复读的事务隔离级别 -- 默认的事务隔离级别
transaction-isolation = {Serializable} #设置为串行化的事务隔离级别
命令设置
也可以使用 set transaction 命令改变单个或者所有新连接的事务隔离级别,基本语法如下:
注意点
如果使用 set transaction 命令来设置事务隔离级别,需要注意如下几个点:
1、不带 session 或 global 关键字设置事务隔离级别,指的是为下一个(还未开始的)事务设置隔离级别。
2、使用 global 关键字指的是对全局设置事务隔离级别,也就是设置后的事务隔离级别对所有新产生的数据库连接生效。
3、使用 session 关键字指的是对当前的数据库连接设置事务隔离级别,此时的事务隔离级别只对当前连接的后续事务生效。
4、任何客户端都能自由改变当前会话的事务隔离级别,可以在事务中间改变,也可以改变下一个事务的隔离级别。
session 会话级别的设置演示
global 全局级别的设置演示
具体设置命令
-- session 表示对当前的数据库连接设置事务隔离级别
-- 显式地将事务隔离级别设置为读未提交
set session transaction isolation level read uncommitted;
-- 显式地将事务隔离级别设置为读已提交
set session transaction isolation level read committed;
-- 显式地将事务隔离级别设置为可重复读
set session transaction isolation level Repeatable Read;
-- 显式地将事务隔离级别设置为串行化
set session transaction isolation level Serializable;
-- global 表示对全局设置事务隔离级别
-- 显式地将事务隔离级别设置为读未提交
set global transaction isolation level read uncommitted;
-- 显式地将事务隔离级别设置为读已提交
set global transaction isolation level read committed;
-- 显式地将事务隔离级别设置为可重复读
set global transaction isolation level Repeatable Read;
-- 显式地将事务隔离级别设置为串行化
set global transaction isolation level Serializable;
5-3-2:查询当前数据库的事务隔离级别
查询 MySQL 的事务隔离级别的命令
可以看出,InnoDB 默认的事务隔离级别是:可重复读
-- 查询数据库中有哪些变量
show variables
-- 查询全局级别变量
show global variables
-- 查询会话级别变量
show session variables
-- 查询全局级别的事务隔离级别 ---->查询结果: REPEATABLE-READ 可重复读
select @@global.transaction_isolation
-- 查询会话级别的事务隔离级别 ---->查询结果: REPEATABLE-READ 可重复读
select @@session.transaction_isolation
-- 查看当前数据库连接会话的事务隔离级别 ---->查询结果: REPEATABLE-READ 可重复读
select @@transaction_isolation
5-3-3:MySQL 中 4 种事务隔离级别的区别
4 种事务隔离级别对于并发事务带来的问题的解决程度不一样。
1、读未提交允许脏读,即在读未提交的事务隔离级别下,可能读取到其他会话未提交的事务修改的数据。这种事务隔离级别下存在【脏读】、【不可重复读】和【幻读】的问题。
2、读已提交只能读取到已经提交的数据。Oracle 等数据库使用的默认事务隔离级别就是【读已提交】。这种事务隔离级别存在【不可重复读】和【幻读】的问题。
3、可重复读就是在同一个事务内,无论何时查询到的数据都与一开始查询到的数据一致,这就是 MySQL 中 InnoDB 存储引擎默认的事务隔离级别。
这种事务隔离级别下存在【幻读】的问题。
4、串行化是指完全串行的读,每次读取数据库中的数据时,都需要获得表级别的共享锁,读和写都会阻塞。
这种事务隔离级别解决了并发事务带来的问题,但是完全的串行化操作使得数据库失去了并发特性,所以这种事务隔离级别往往在互联网行业中不太常用。