看完这篇文章,你还不明白什么是 MySQL 事务吗

MySQL 事务


1、理解事务

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

在以前,只有使用 InnoDB 引擎的数据库才能支持事务,但是现在最新版的 MyISAM 也是支持事务操作


事务的 4 大特征(ACID):

  • 原子性(Atomicity):一个事务中所有的操作,要么全部完成,要么全部不完成,不会结束在中间某个环节

​ 例如: A 有 1000 元,B 有 200 元,A 给 B 转账 200 元,转账过程中服务器崩了,那么转账会失败,即 A 持有的金额还是 1000 元,B 所持有的金额说 200 元


  • 一致性(Consistency):无论执行什么操作,数据库的完整性不会被破环

​ 例如: A 有 1000 元,B 有 200 元,A 给 B 转账 200 元,转账前后 A 和 B 的总金额一直是 1200 元


  • 隔离性(Isolation):当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间

​ 例如:A 在给 B 转账,同时 B 给 A 转账,二者互不干扰


  • 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失

​ 例如:A 原本有 1000 元,B 有 200 元,A 给 B 转账 200 元,在转账完成后,服务器崩了,此时,A持有 800 元,B 持有 400 元,并不会回到转账前的状态



在这里,我们对隔离性着重介绍!

隔离性有 4 个等级(从上到下等级变高,安全性越好,但是性能越差):

  • 读未提交(read uncommitted):事务 A 和事务 B,事务 B 可以读取 事务 A 未提交的数据。会出现 “脏读” 的现象
    • 脏读:简单来说,就是事务 B 读取了事务 A 未提交的数据,这个数据称之为 “脏数据”,这种现象就称之为 “脏读”。想象一下你往购物车里放了累计价值 100万 的商品,然后平台还没等你确认,就直接发货。目前这种隔离等级的数据库基本不存在了

  • 读已提交(read committed):事务 A 和事务 B 操作数据,对于事务 A 提交的新数据,事务 B 可以读取;如果事务 A 未提交,则事务 B 读取到的还是旧数据,可以有效避免 “脏读”。但是会出现 “不可重复读取” 的现象
    • 不可重复读取:这里的 “不可重复读取” 重点在于前后读取数据的不一致性,不是说只能读一次。假设你要修改一个共享文档(事务 B),你首先读取了这个文档(旧数据),然后一个熊孩子也读取这个文档,接着他把文档内容删光并提交(事务 A),这时候你再读取这个文档(新数据),发现什么都没了,心态直接爆炸。这是因为事务 B 读取了事务 A 提交后的数据!“不可重复读取” 一般发生在 UNDATE 或者 DELETE

  • 可重复读(repeatable read):事务 A 和事务 B 操作数据,对于事务 A 提交的数据,事务 B 读取不到。哎,你可能又会好奇,为什么现在又读取不到了呢?这是为了解决 “不可重复读取” 的问题,我们不希望其他事务(例如事务B、C … N)对于数据的修改影响到当前的事务(例如事务 A)。但是还是有问题,这有可能会导致 “幻读”
    • 幻读:表现为某一次的 SELECT 操作得到的结果所表征的数据状态无法支撑后续的业务操作(细品)。打个比方我们想要往一个数据库中插入记录 A,首先我们开启事务,查询发现记录 A 不在数据库中,然后我们执行 INSERT 操作,系统却提示记录 A 已经存在,这时候就发生了 “幻读”。需要注意,“幻读” 并不是说两次读取的结果集不同,这应该属于 “不可重复读取” 。“幻读” 一般发生在 INSERT

  • 串行化(serializable):上述三种问题【脏读、不可重复读取、可重复读取】全部不会出现,但是效率极低,一般我们也不使用,基本到 rr 级别就差不多了

最后我们利用表格对隔离性做一个总结:

级别 名字 脏读 不可重复读取 幻读 数据库默认隔离级别
1 读未提交(read uncommitted)
2 读已提交(read committed) Oracle
3 可重复读(repeatable read) MySQL
4 串行化(serializable)

下面我们使用代码看看事务隔离性如何设置:

-- 首先在设置隔离性等级前,我们一般会先查看当前隔离等级
-- 需要注意,有些人可能会使用 tx_isolation,这是老版 MySQL 的写法,现在已经弃用了
SELECT @@transaction_isolation;

-- 设置隔离性等级,在 [] 里面写等级的英文名,注意需要在开启事务前设置
SET SESSION TRANSACTION ISOLATION LEVEL [repeatable read]; -- 这里设置等级为 3

2、事务控制语句

我们先来捋一下事务的流程,首先我们得 开启 一个事务,第二步执行相关 SQL 语句,这时候出现了分支,我们可以选择 提交事务,当然也可以选择不提交,就是 回滚 到执行 SQL 语句之前的状态。如果执行的步骤较多,我们又不希望一次回到解放前怎么办,我们可以 设置保存点 嘛,设置完之后,肯定也可以 回到保存点 ,当然,也可以 删除保存点,当所有的操作我们都满意后,就完成了一个事务!

实际上事务在操作的时候,基本就是按照这个思路,就是注意一点,MySQL 默认是开启事务的提交的,所以我们自己写事务的时候,需要先把自动提交给关了(否则写一句就提交,还怎么回滚),写完后再开启自动提交

事务流程(粗略):

事务流程

下面是具体的事务控制语句格式:

  • SET AUTOCOMMIT = 0:关闭 MySQL 自动提交
  • START TRANSACTION:开启事务
  • SAVEPOINT `保存点名称`:设置保存点
  • ROLLBACK TO `保存点名称`:回滚到保存点
  • RELEASE SAVEPOINT `保存点名称`:删除保存点
  • COMMIT:提交事务
  • ROLLBACK:回滚
  • SET AUTOCOMMIT = 1:开启 MySQL 自动提交
-- 首先我们纯代码创建一个数据库,并添加一些数据(咱简单点)
-- 数据库名:Bank
-- 数据表:account(id、name、phone、amount)

-- 看不懂怎么办,Navicat 可视化建表总会吧,咱可以偷懒不写代码
CREATE DATABASE IF NOT EXISTS Bank;
CREATE TABLE IF NOT EXISTS `account`(
    `id` VARCHAR(20) NOT NULL COMMENT '用户 id',
	`name` VARCHAR(20) NOT NULL COMMENT '姓名',
    `phone` BIGINT(11) COMMENT '电话',
    `amount` BIGINT(15) COMMENT '账户余额',  
    PRIMARY KEY(`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `account`(`id`, `name`, `phone`, `amount`)
VALUE('00000001', '张三', 13563475423, 1000),
('00000002', '李四', 13824697584, 600),
('00000003', '王五', 13549647862, 800),
('00000004', '赵六', 18935486254, 2000),
('00000005', '丁七', 15388572767, 300);

创建表如下:

image-20220108212646836

现在张三想要给李四转账 200 元,转账这就是一个事务,下面看代码:

-- 关闭自动提交
SET AUTOCOMMIT = 0;

-- 开启事务
START TRANSACTION;
-- 如果张三的账户余额少于 200 元,就不能进行转账
UPDATE `account` SET `amount` = `amount` - 200 WHERE `name` = '张三' && `amount` - 200 >= 0;
UPDATE `account` SET `amount` = `amount` + 200 WHERE `name` = '李四';
-- 提交事务
COMMIT;
-- 或者选择回滚
ROLLBACK

-- 重新开启自动提交
SET AUTOCOMMIT = 1;

我们执行三次,看看最后 account 表的结果(相当于张三向李四转账 600 元嘛):

image-20220108215105929

有心的人可以发现,我们并没有设置隔离性等级,这是因为 MySQL 默认为 RR 级别,我们直接使用就行

那么 MySQL 事务的介绍就这么多,想要解释清楚,还要简洁还是比较费时间,那么,下篇文章我们再见

猜你喜欢

转载自blog.csdn.net/qq_52174675/article/details/122410133