文章目录
- 🔥分布式事务处理_认识本地事务
- 🔥关系型数据库事务基础_并发事务带来的问题
- 🔥关系型数据库事务基础_MySQL事务隔离级别
- 🔥MySQL事务隔离级别_模拟异常发生之脏读
- 🔥MySQL事务隔离级别_模拟异常发生之不可重复读
- 🔥MySQL事务隔离级别_模拟异常发生之幻读
🔥分布式事务处理_认识本地事务
什么是事务
事务就是针对数据库的一组操作,它可以由一条或多条SQL语句组成,同一个事务的操作具备同步的特点,事务中的语句要么都执行,要么都不执行。
举个例子:
你去小卖铺买东西,一手交钱,一手交货就是一个事务的例子,交钱和交货必须全部成功,事务才算成功,任一个活动失败,事务将撤销所有已成功的活动。
什么是本地事物
在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。
注:
⭐Business∶我们具体的业务代码
⭐Storage∶ 库存业务代码;扣库存
⭐Order∶订单业务代码;保存订单
⭐Account∶账号业务代码;减账户余额
数据库事务的四大特性ACID
数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个不可分割的执行单元,该执行单元中的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚。
🔥关系型数据库事务基础_并发事务带来的问题
并发事务带来的问题
数据库一般会并发执行多个事务,而多个事务可能会并发地对相同的数据进行增加、删除、修改和查询操作,进而导致并发事务问题。
脏写
当两个或两个以上的事务选择数据库中的同一行数据,并基于最初选定的值更新该行数据时,因为每个事务之间都无法感知彼此的存在,所以会出现最后的更新操作覆盖之前由其他事务完成的更新操作的情况。也就是说,对于同一行数据,一个事务对该行数据的更新操作覆盖了其他事务对该行数据的更新操作。
解决方案:
让每个事物按照顺序串行的方式执行,按照一定的顺序一次进行写操作。
脏读
一个事务正在对数据库中的一条记录进行修改操作,在这个事务完成并提交之前,当有另一个事务来读取正在修改的这条数据记录时,如果没有对这两个事务进行控制,则第二个事务就会读取到没有被提交的脏数据,并根据这些脏数据做进一步的处理,此时就会产生未提交的数据依赖关系。我们通常把这种现象称为脏读,也就是一个事务读取了另一个事务未提交的数据。
解决方案:
先写后读,也就是写完之后再读。
不可重复读
一个事务读取了某些数据,在一段时间后,这个事务再次读取之前读过的数据,此时发现读取的数据发生了变化,或者其中的某些记录已经被删除,这种现象就叫作不可重复读。
解决方案:
先读后写,也就是读完之后再写。
幻读
一个事务按照相同的查询条件重新读取之前读过的数据,此时发现其他事务插入了满足当前事务查询条件的新数据,这种现象叫作幻读。
解决方案:
先读后写,也就是读完之后再写。
🔥关系型数据库事务基础_MySQL事务隔离级别
MySQL中的InnoDB储存引擎提供SQL标准所描述的4种事务隔离级别,分别为读未提交 (Read Uncommitted)、读已提交(ReadCommitted)、可重复读(Repeatable Read)和串行化(Serializable)。
⭐读未提交(Read Uncommitted):事务可以读取未提交的数据,也称作脏读(Dirty Read)。一般很少使用。
⭐读已提交(Read Committed):是大都是 DBMS (如:Oracle, SQLServer)默认事务隔离。执行两次同意的查询却有不同的结果,也叫不可重复读。
⭐可重复读(Repeable Read):是 MySQL 默认事务隔离级别。能确保同一事务多次读取同一数据的结果是一致的。可以解决脏读的问题,但理论上无法解决幻读(Phantom Read)的问题。
⭐可串行化(Serializable):是最高的隔离级别。强制事务串行执行,会在读取的每一行数据上加锁,这样虽然能避免幻读的问题,但也可能导致大量的超时和锁争用的问题。很少会应用到这种级别,只有在非常需要确保数据的一致性且可以接受没有并发的应用场景下才会考虑。
🔥MySQL事务隔离级别_模拟异常发生之脏读
前置知识
# 查看 MySQL 版本
select version();
# 开启事务
start transaction;
# 提交事务
commit;
# 回滚事务
rollback;
查看连接的客户端详情
每个 MySQL 命令行窗口就是一个 MySQL 客户端,每个客户端都可以单独设置(不同的)事务隔离级别,这也是演示 MySQL 并发事务的基础。
show processlist;
新建数据库和测试数据
-- 创建数据库
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 事务隔离
级别;
事务隔离级别的值有 4 个:
⭐READ UNCOMMITTED:读未提交
⭐READ COMMITTED:读已提交
⭐REPEATABLE READ:可重复读
⭐SERIALIZABLE:串行化
脏读
一个事务读到另外一个事务还没有提交的数据,称之为脏读。脏读演示的执行流程如下:
脏读演示步骤1
设置窗口 2 的事务隔离级别为读未提交,设置命令如下:
set session transaction isolation level read
uncommitted;
注意:
事务隔离级别读未提交存在脏读的问题。
脏读演示步骤2
窗口2开启事务,查询用户表如下图所示:
start transaction;
select * from userinfo;
注意:
从上述结果可以看出,在窗口 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
脏读演示步骤4
在窗口 2 中再次查询用户列表,执行结果如下:
注意:
从上述结果可以看出,在窗口 2 中读取到了窗口 1 中事务未提交的数据,这就是脏读。
🔥MySQL事务隔离级别_模拟异常发生之不可重复读
不可重复读是指一个事务先后执行同一条 SQL,但两次读取到的数据不同,就是不可重复读。不可重复读演示的执行流程如下:
不可重复读演示步骤1
设置窗口 2 的事务隔离级别为读已提交
set session transaction isolation level read
committed;
注意:
读已提交可以解决脏读的问题,但存在不可重复读的问题。
不可重复读演示步骤2
在窗口 2 中开启事务,并查询用户表,执行结果如下
不可重复读演示步骤3
在窗口 1 中开启事务,并给 Java 用户添加 20 元,但不提交事务,再观察窗口 2 中有没有脏读的问题,具体执行结果如下图所示:
从上述结果可以看出,当把窗口的事务隔离级别设置为读已提交,已经不存在脏读问题了。接下来在窗口 1 中提交事务,执行结果如下图所示:
不可重复读演示步骤4
切换到窗口 2 中再次查询用户列表,执行结果如下:
不可重复读和脏读的区别:
脏读可以读到其他事务中未提交的数据,而不可重复读是读取到了其他事务已经提交的数据,但前后两次读取的结果不同。
🔥MySQL事务隔离级别_模拟异常发生之幻读
幻读名如其文,它就像发生了某种幻觉一样,在一个事务中明明没有查到主键为 X 的数据,但主键为 X 的数据就是插入不进去,就像某种幻觉一样。幻读演示的执行流程如下:
幻读演示步骤1
在窗口1和窗口2修改事务隔离级别为可重复读。
set session transaction isolation level
repeatable read;
幻读演示步骤2
设置窗口 2 为可重复读,可重复有幻读的问题,查询编号为 3 的用户,具体执行 SQL 如下:
start transaction;
select * from userinfo where id=3;
注意:
从上述结果可以看出,查询的结果中 id=3 的数据为空。
幻读演示步骤3
开启窗口 1 的事务,插入用户编号为 3 的数据,然后成功提交事务,执行 SQL 如下:
start transaction;
insert into userinfo(id,name,balance)
values(3,'Spring',100);
commit;
幻读演示步骤4
在窗口 2 中插入用户编号为 3 的数据,执行 SQL 如下:
insert into userinfo(id,name,balance)
values(3,'Spring',100);
注意:
添加用户数据失败,提示表中已经存在了编号为 3 的数据,且此字段为主键,不能添加多个。
幻读演示步骤5
在窗口 2 中,重新执行查询:
select * from userinfo where id=3;
注意:
在此事务中查询明明没有编号为 3 的用户,但插入的时候却却提示已经存在了,这就是幻读。
不可重复读和幻读的区别
二者描述的则重点不同,不可重复读描述的侧重点是修改操作,而幻读描述的侧重点是添加和删除操作。