分布式事物-全面详解(学习总结---从入门到深化)(1)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 事务就是针对数据库的一组操作,它可以由一条或多条SQL语句组 成,同一个事务的操作具备同步的特点,事务中的语句要么都执 行,要么都不执行。

分布式事物处理_认识本地事物

2345_image_file_copy_241.jpg

什么是事物

事务就是针对数据库的一组操作,它可以由一条或多条SQL语句组 成,同一个事务的操作具备同步的特点,事务中的语句要么都执 行,要么都不执行。

举个栗子:

你去小卖铺买东西,一手交钱,一手交货就是一个事务的例子,交钱和交货必须全部成功,事务才算成功,任一个活动失败,事务将撤销所有已成功的活动。

什么是本地事物

在计算机系统中,更多的是通过关系型数据库来控制事务,这是利 用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用 主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。

2345_image_file_copy_242.jpg

2345_image_file_copy_243.jpg

数据库事务的四大特性ACID

2345_image_file_copy_244.jpg

总结

数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个 不可分割的执行单元,该执行单元中的所有操作要么都成功,要么 都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。

关系型数据库事务基础_并发事务带来的问题

2345_image_file_copy_245.jpg

并发事务带来的问题

数据库一般会并发执行多个事务,而多个事务可能会并发地对相同 的数据进行增加、删除、修改和查询操作,进而导致并发事务问 题。

2345_image_file_copy_246.jpg

脏写

当两个或两个以上的事务选择数据库中的同一行数据,并基于最初 选定的值更新该行数据时,因为每个事务之间都无法感知彼此的存 在,所以会出现最后的更新操作覆盖之前由其他事务完成的更新操 作的情况。也就是说,对于同一行数据,一个事务对该行数据的更新操作覆盖了其他事务对该行数据的更新操作。

2345_image_file_copy_247.jpg

解决方案: 让每个事物按照顺序串行的方式执行,按照一定的顺序一次进行写操作。

脏读

一个事务正在对数据库中的一条记录进行修改操作,在这个事务完 成并提交之前,当有另一个事务来读取正在修改的这条数据记录 时,如果没有对这两个事务进行控制,则第二个事务就会读取到没 有被提交的脏数据,并根据这些脏数据做进一步的处理,此时就会 产生未提交的数据依赖关系。我们通常把这种现象称为脏读,也就是一个事务读取了另一个事务未提交的数据。

2345_image_file_copy_248.jpg

解决方案: 先写后读,也就是写完之后再读。

不可重复读

一个事务读取了某些数据,在一段时间后,这个事务再次读取之前 读过的数据,此时发现读取的数据发生了变化,或者其中的某些记录已经被删除,这种现象就叫作不可重复读。

2345_image_file_copy_249.jpg

解决方案: 先读后写,也就是读完之后再写。

幻读

一个事务按照相同的查询条件重新读取之前读过的数据,此时发现 其他事务插入了满足当前事务查询条件的新数据,这种现象叫作幻读。

2345_image_file_copy_250.jpg

解决方案: 先读后写,也就是读完之后再写。

关系型数据库事务基础_MySQL事务隔离级别

2345_image_file_copy_251.jpg

MySQL中的InnoDB储存引擎提供SQL标准所描述的4种事务隔离级 别,分别为

读未提交 (Read Uncommitted)

读已提交 (ReadCommitted)

可重复读(Repeatable Read)

串行化 (Serializable)。

2345_image_file_copy_252.jpg

1、读未提交(Read Uncommitted):事务可以读取未提交的数据,也称作脏读(Dirty Read)。一 般很少使用。

2、读已提交(Read Committed):是大都是 DBMS (如:Oracle, SQLServer)默认事务隔离。执行两次同意的查询却有不同的结果,也叫不可重复读。

3、可重复读(Repeable Read):是 MySQL 默认事务隔离级别。能确保同一事务多次读取同一数据 的结果是一致的。可以解决脏读的问题,但理论上无法解决幻读(Phantom Read)的问题。

4、可串行化(Serializable):是最高的隔离级别。强制事务串行执行,会在读取的每一行数据上加锁,这样虽然能避免幻读的问题,但也可能导致大量的超时和锁争用的问题。很少会应用到这种级别,只有在非常需要确保数据的一致性且可以接受没有并发的应用场景下才会考虑。

MySQL事务隔离级别_模拟异常发生之脏读

2345_image_file_copy_253.jpg

前置知识

# 查看 MySQL 版本
select version();
# 开启事务
start transaction;
# 提交事务
commit;
# 回滚事务
rollback;

查看连接的客户端详情

每个 MySQL 命令行窗口就是一个 MySQL 客户端,每个客户端都可 以单独设置(不同的)事务隔离级别,这也是演示 MySQL 并发事务的基础。

show processlist;

2345_image_file_copy_254.jpg

新建数据库和测试数据

-- 创建数据库
drop database if exists testdb;
create database testdb;
use testdb;
-- 创建表
create table userinfo(
 id int primary key auto_increment,
 name varchar(250) not null,
 balance decimal(10,2) not null default 0
);
-- 插入测试数据
insert into userinfo(id,name,balance)
values(1,'Java',100),(2,'MySQL',200);

查询事务的隔离级别

select
@@global.transaction_isolation,@@transaction_is
olation;

设置客户端的事务隔离级别

通过以下 SQL 可以设置当前客户端的事务隔离级别:

set session transaction isolation level 事务隔离 级别;

2345_image_file_copy_255.jpg

脏读

一个事务读到另外一个事务还没有提交的数据,称之为脏读。脏读 演示的执行流程如下:

2345_image_file_copy_256.jpg

脏读演示步骤1

设置窗口 2 的事务隔离级别为读未提交,设置命令如下:

set session transaction isolation level read
uncommitted;

2345_image_file_copy_257.jpg

注意: 事务隔离级别读未提交存在脏读的问题。

脏读演示步骤2

窗口2开启事务,查询用户表如下图所示:

start transaction;
select * from userinfo;

2345_image_file_copy_258.jpg

注意: 从上述结果可以看出,在窗口 2 中读取到了窗口 1 中事务未提 交的数据,这就是脏读。

脏读演示步骤3

在窗口 1 中开启一个事务,并给 Java 账户加 50 元,但不提交事 务,执行的 SQL 如下:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update userinfo set balance=balance+50
where name="java";
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

2345_image_file_copy_259.jpg

脏读演示步骤4

在窗口 2 中再次查询用户列表,执行结果如下:

2345_image_file_copy_260.jpg

注意: 从上述结果可以看出,在窗口 2 中读取到了窗口 1 中事务未提交的数据,这就是脏读。

不可重复读

不可重复读是指一个事务先后执行同一条 SQL,但两次读取到的数据不同,就是不可重复读。不可重复读演示的执行流程如下:

2345_image_file_copy_261.jpg

不可重复读演示步骤1

设置窗口 2 的事务隔离级别为读已提交

set session transaction isolation level read
committed;

2345_image_file_copy_262.jpg

注意: 读已提交可以解决脏读的问题,但存在不可重复读的问题。

不可重复读演示步骤2

在窗口 2 中开启事务,并查询用户表,执行结果如下

2345_image_file_copy_263.jpg

不可重复读演示步骤3

在窗口 1 中开启事务,并给 Java 用户添加 20 元,但不提交事务, 再观察窗口 2 中有没有脏读的问题,具体执行结果如下图所示:

2345_image_file_copy_264.jpg

从上述结果可以看出,当把窗口的事务隔离级别设置为读已提交, 已经不存在脏读问题了。接下来在窗口 1 中提交事务,执行结果如下图所示:

2345_image_file_copy_265.jpg

不可重复读演示步骤4

切换到窗口 2 中再次查询用户列表,执行结果如下:

2345_image_file_copy_266.jpg

不可重复读和脏读的区别:

脏读可以读到其他事务中未提交的数据,而不可重复读是读取到了其他事务已经提交的数据,但前后两次读取的结果不同。

幻读

幻读名如其文,它就像发生了某种幻觉一样,在一个事务中明明没 有查到主键为 X 的数据,但主键为 X 的数据就是插入不进去,就像某种幻觉一样。幻读演示的执行流程如下:

2345_image_file_copy_267.jpg

幻读演示步骤1

在窗口1和窗口2修改事务隔离级别为可重复读。

set session transaction isolation level
repeatable read;

幻读演示步骤2

设置窗口 2 为可重复读,可重复有幻读的问题,查询编号为 3 的用户,具体执行 SQL 如下:

start transaction;
select * from userinfo where id=3;

2345_image_file_copy_268.jpg

注意:从上述结果可以看出,查询的结果中 id=3 的数据为空。

幻读演示步骤3

开启窗口 1 的事务,插入用户编号为 3 的数据,然后成功提交事务,执行 SQL 如下:

start transaction;
insert into userinfo(id,name,balance)
values(3,'Spring',100);
commit;

2345_image_file_copy_269.jpg

幻读演示步骤4

在窗口 2 中插入用户编号为 3 的数据,执行 SQL 如下:

insert into userinfo(id,name,balance)
values(3,'Spring',100);

2345_image_file_copy_270.jpg

注意: 添加用户数据失败,提示表中已经存在了编号为 3 的数据,且 此字段为主键,不能添加多个。

幻读演示步骤5

在窗口 2 中,重新执行查询:

select * from userinfo where id=3;

2345_image_file_copy_271.jpg注意: 在此事务中查询明明没有编号为 3 的用户,但插入的时候却却 提示已经存在了,这就是幻读。

不可重复读和幻读的区别

二者描述的则重点不同,不可重复读描述的侧重点是修改操作,而 幻读描述的侧重点是添加和删除操作。

MySQL中锁的分类

2345_image_file_copy_272.jpg

从本质上讲,锁是一种协调多个进程或多个线程对某一资源的访问 的机制,MySQL使用锁和MVCC机制实现了事务隔离级别。

锁的分类

2345_image_file_copy_273.jpg

悲观锁和乐观锁

悲观锁

顾名思义,悲观锁对于数据库中数据的读写持悲观态度,即在整个数据处理的过程中,它会将相应的数据锁定。在数据库中,悲观锁的实现需要依赖数据库提供的锁机制,以保证对数据库加锁后,其他应用系统无法修改数据库中的数据。

2345_image_file_copy_274.jpg

注意:

在悲观锁机制下,读取数据库中的数据时需要加锁,此时不能对这些数据进行修改操作。修改数据库中的数据时也需要加锁,此时不能对这些数据进行读取操作。

乐观锁

悲观锁会极大地降低数据库的性能,特别是对长事务而言,性能的损耗往往是无法承受的。乐观锁则在一定程度上解决了这个问题。

2345_image_file_copy_275.jpg

注意:

实现乐观锁的一种常用做法是为数据增加一个版本标识,如果是通过数据库实现,往往会在数据表中增加一个类似version的版本号字段。

读锁和写锁

读锁

读锁又称为共享锁,共享锁就是多个事务对于同一数据可以共享一 把锁,都能访问到数据,但是只能读不能修改。

写锁

写锁又称为排他锁,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁, 包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

注意:

需要注意的是,对同一份数据,如果加了读锁,则可以继续为 其加读锁,且多个读锁之间互不影响,但此时不能为数据增加 写锁。一旦加了写锁,则不能再增加写锁和读锁。因为读锁具有共享性,而写锁具有排他性。

表锁、行锁和页面锁

表锁

表锁也称为表级锁,就是在整个数据表上对数据进行加锁和释放锁。典型特点是开销比较小,加锁速度快,一般不会出现死锁,锁定的粒度比较大,发生锁冲突的概率最高,并发度最低。

手动增加表锁

mysql> lock table userinfo read;
Query OK, 0 rows affected (0.00 sec)
mysql> lock table userinfo write;
Query OK, 0 rows affected (0.00 sec)

查看数据表上增加的锁

show open tables;

2345_image_file_copy_276.jpg删除表锁

mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

行锁

行锁也称为行级锁,就是在数据行上对数据进行加锁和释放锁。典 型特点是开销比较大,加锁速度慢,可能会出现死锁,锁定的粒度最小,发生锁冲突的概率最小,并发度最高。

2345_image_file_copy_277.jpg

页面锁

页面锁也称为页级锁,就是在页面级别对数据进行加锁和释放锁。 对数据的加锁开销介于表锁和行锁之间,可能会出现死锁,锁定的粒度大小介于表锁和行锁之间,并发度一般。

2345_image_file_copy_278.jpg

间隙锁和临键锁

间隙锁

在MySQL中使用范围查询时,如果请求共享锁或排他锁,InnoDB 会给符合条件的已有数据的索引项加锁。如果键值在条件范围内, 而这个范围内并不存在记录,则认为此时出现了“间隙(也就是 GAP)”。InnoDB存储引擎会对这个“间隙”加锁,而这种加锁机制就是间隙锁(GAP Lock)。

 例如,userinfo数据表中存在如下数据。

2345_image_file_copy_279.jpg

解释:

此时,userinfo数据表中的间隙包括id为(3,15]、(15,20]、 (20,正无穷]的三个区间。如果执行如下命令,将符合条件的用 户的账户余额增加100元。 update userinfo set balance = balance + 100 where id > 5 and id <16; 则其他事务无法在(3,20]这个区间内插入或者修改任何数据。 这里需要注意的是,间隙锁只有在可重复读事务隔离级别下才 会生效。

临键锁

临键锁(Next-Key Lock)是行锁和间隙锁的组合,例如上面例子中 的区间(3,20]就可以称为临键锁。

MySQL中的死锁问题

2345_image_file_copy_280.jpg

什么是死锁

死锁是并发系统中常见的问题,同样也会出现在数据库MySQL的并 发读写请求场景中。当两个及以上的事务,双方都在等待对方释放 已经持有的锁或因为加锁顺序不一致造成循环等待锁资源,就会出 现“死锁”。

Deadlock found when trying to get lock...

举例来说 A 事务持有 X1 锁 ,申请 X2 锁,B事务持有 X2 锁,申请 X1 锁。A 和 B 事务持有锁并且申请对方持有的锁进入循环等待,就造成了死锁。

2345_image_file_copy_282.jpg

第一步

打开终端A,登录MySQL,将事务隔离级别设置为可重复读,开启 事务后为userinfo数据表中id为1的数据添加排他锁,如下所示。

mysql> set session transaction isolation level
repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from userinfo;
+----+-------+---------+
| id | name  | balance |
+----+-------+---------+
|  1 | Java  |  100.00 |
|  2 | MySQL |  200.00 |
+----+-------+---------+
2 rows in set (0.00 sec)

第二步

打开终端B,登录MySQL,将事务隔离级别设置为可重复读,开启事务后为userfinfo数据表中id为2的数据添加排他锁,如下所示。

mysql> set session transaction isolation level
repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from userinfo where id = 2;
+----+-------+---------+
| id | name  | balance |
+----+-------+---------+
|  2 | MySQL |  200.00 |
+----+-------+---------+
1 row in set (0.00 sec)

第三步

在终端A为userinfo数据表中id为2的数据添加排他锁,如下所示。

mysql> select * from userinfo where id =2 for
update;

注意: 此时,线程会一直卡住,因为在等待终端B中id为2的数据释放排他锁。

第四步

在终端B中为userinfo数据表中id为1的数据添加排他锁,如下所示。

mysql> select * from userinfo where id =1 for
update;
ERROR 1213 (40001): Deadlock found when trying
to get lock; try restarting transaction

通过如下命令可以查看死锁的日志信息。

show engine innodb status\G

注意:

通过命令行查看LATEST DETECTED DEADLOCK选项相关的信 息,可以发现死锁的相关信息,或者通过配置 innodb_print_all_deadlocks(MySQL 5.6.2版本开始提供)参数为ON,将死锁相关信息打印到MySQL错误日志中。

如何避免死锁

2345_image_file_copy_283.jpg

MySQL事务的实现原理_什么是redo log

2345_image_file_copy_284.jpg

MySQL的事务实现离不开Redo Log和Undo Log。从某种程度上 说,事务的隔离性是由锁和MVCC机制实现的,原子性和持久性是 由Redo Log实现的,一致性是由Undo Log实现的。

2345_image_file_copy_285.jpg

什么是redo log

redo log叫做重做日志,是用来实现事务的持久性。该日志文件由 两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件 (redo log),前者是在内存中,后者在磁盘中。当事务提交之后会 把所有修改信息都会存到该日志中。

2345_image_file_copy_286.jpg

注意:

先写日志,再写磁盘的技术就是 MySQL 里经常说到的 WAL(Write-Ahead Logging) 技术。

Redo Log刷盘规则

在计算机操作系统中,用户空间( user space )下的缓冲区数据一般情况 下是无法直接写入磁盘的,中间必须经过操作系统内核空间( kernel space )缓冲区( OS Buffer )。因此, redo log buffer 写入 redo log file 实际上是先 写入 OS Buffer ,然后再通过系统调用 fsync() 将其刷到 redo log file 中。

2345_image_file_copy_287.jpg

 mysql 支持三种将 redo log buffer 写入 redo log file 的时机。 innodb_flush_log_at_trx_commit。

2345_image_file_copy_288.jpg


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
5月前
|
存储 SQL 分布式数据库
OceanBase 入门:分布式数据库的基础概念
【8月更文第31天】在当今的大数据时代,随着业务规模的不断扩大,传统的单机数据库已经难以满足高并发、大数据量的应用需求。分布式数据库应运而生,成为解决这一问题的有效方案之一。本文将介绍一款由阿里巴巴集团自主研发的分布式数据库——OceanBase,并通过一些基础概念和实际代码示例来帮助读者理解其工作原理。
452 0
|
7月前
|
机器学习/深度学习 分布式计算 算法
联邦学习是保障数据隐私的分布式机器学习方法
【6月更文挑战第13天】联邦学习是保障数据隐私的分布式机器学习方法,它在不暴露数据的情况下,通过在各设备上本地训练并由中心服务器协调,实现全局模型构建。联邦学习的优势在于保护隐私、提高训练效率和增强模型泛化。已应用于医疗、金融和物联网等领域。未来趋势包括更高效的数据隐私保护、提升可解释性和可靠性,以及与其他技术融合,有望在更多场景发挥潜力,推动机器学习发展。
135 4
|
3月前
|
消息中间件 关系型数据库 Java
‘分布式事务‘ 圣经:从入门到精通,架构师尼恩最新、最全详解 (50+图文4万字全面总结 )
本文 是 基于尼恩之前写的一篇 分布式事务的文章 升级而来 , 尼恩之前写的 分布式事务的文章, 在全网阅读量 100万次以上 , 被很多培训机构 作为 顶级教程。 此文修改了 老版本的 一个大bug , 大家不要再看老版本啦。
|
7月前
|
消息中间件 NoSQL Java
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
Redis系列学习文章分享---第六篇(Redis实战篇--Redis分布式锁+实现思路+误删问题+原子性+lua脚本+Redisson功能介绍+可重入锁+WatchDog机制+multiLock)
257 0
|
4月前
|
Dubbo Java 应用服务中间件
分布式-dubbo的入门
分布式-dubbo的入门
|
4月前
|
机器学习/深度学习 算法 自动驾驶
深度学习之分布式智能体学习
基于深度学习的分布式智能体学习是一种针对多智能体系统的机器学习方法,旨在通过多个智能体协作、分布式决策和学习来解决复杂任务。这种方法特别适用于具有大规模数据、分散计算资源、或需要智能体彼此交互的应用场景。
205 4
|
5月前
|
机器学习/深度学习 并行计算 PyTorch
PyTorch与DistributedDataParallel:分布式训练入门指南
【8月更文第27天】随着深度学习模型变得越来越复杂,单一GPU已经无法满足训练大规模模型的需求。分布式训练成为了加速模型训练的关键技术之一。PyTorch 提供了多种工具来支持分布式训练,其中 DistributedDataParallel (DDP) 是一个非常受欢迎且易用的选择。本文将详细介绍如何使用 PyTorch 的 DDP 模块来进行分布式训练,并通过一个简单的示例来演示其使用方法。
602 2
|
8月前
|
并行计算 算法 物联网
LLM 大模型学习必知必会系列(七):掌握分布式训练与LoRA/LISA微调:打造高性能大模型的秘诀进阶实战指南
LLM 大模型学习必知必会系列(七):掌握分布式训练与LoRA/LISA微调:打造高性能大模型的秘诀进阶实战指南
LLM 大模型学习必知必会系列(七):掌握分布式训练与LoRA/LISA微调:打造高性能大模型的秘诀进阶实战指南
|
7月前
|
存储 搜索推荐 Java
微服务SpringCloud ES分布式全文搜索引擎简介 下载安装及简单操作入门
微服务SpringCloud ES分布式全文搜索引擎简介 下载安装及简单操作入门
95 2
|
7月前
|
负载均衡 NoSQL 关系型数据库
Redis分布式锁学习总结
Redis分布式锁学习总结
42 0