文章目录
什么是事务?
事务把一组SQL语句打包成为一个整体,在这组SQL的执行过程中,执行结果只有两种:要么全部成功,要么全部失败。这组SQL语句可以是一条也可以是多条。来看一个转账的例子,如图:

这个过程包含两个步骤(两个SQL语句),张三的余额减100、李四的余额加100,两个SQL语句执行完之后不能出现张三扣了100,但李四+100的SQL语句却执行失败了这样的情况,所以我们把这两个SQL语句打包为一个事务,使业务保持数据的完整性和安全性。
事务的ACID特性(重点)
事务的 ACID 特性指的是 Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)
- Atomicity(原子性)
- 是支持事务的数据库的最基本的一个特性。
- 一组 SQL 要么全都执行成功,要么全都不执行,不会出现只执行一半的情况。
- 如果事务执行过程中出现错误,事务就会回滚( rollback )到事务开始之前的状态,就像这个事务从来没有执行过一样。
依靠 undo log (回滚日志)实现
- Consistency(一致性)
- 事务执行前后,数据库从一个合法一致状态,转变为另外一个合法一致的状态,保证数据正确,满足业务约束。
例:转账前后,两个人账户总金额保持不变。一致性是事务最终要达成的目标
- 也就是事务结束之后,数据库不能出现违背业务规则的非法数据。
再比如:
数据库设置了约束,余额不能是负数。
- 如果你执行一个事务,让账户余额变成负数,就算这条 SQL 完整跑完(满足原子性),数据库也会拒绝,保证不会出现非法数据,这就是一致性。
- 其实一致性就是事务的最终目标,一致性就是由原子性、隔离性、持久性这三者来实现的。
- Isolation(隔离性)
多个事务并发执行时,事务之间相互隔离,互不干扰。
比如,如果事务的隔离性没有得到保证的话:
事务 A 修改数据,还没提交(commit);另一个事务 B 读到了这个还没提交的数据。若 A 回滚,B 读到的数据就是无效脏数据,事务之间发生干扰。
- Durability(持久性)
事务一旦提交,对数据库的修改就永久保存在存储介质中,就算数据库发生故障、宕机,已经提交的数据也不会丢失。
依靠 redo log (重做日志)实现
为什么要使用事务
- 事务拥有 ACID 四大特性,用来满足业务上的数据一致性需求。
- 数据库实现事务,简化了应用层的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题(不用我们自己再在代码底层实现一遍ACID了,只要用好事务就可以实现业务的ACID特性),因此事务本质上是数据库对 ACID 模型的一个实现,是为应用层服务的。
- 业务逻辑中一组操作,要么全部提交成功,要么全部回滚。保证业务数据不会卡在中间状态,不会出现一半成功、一半失败的脏数据。
- 一旦 commit 完成,数据库依靠 redo log 持久化保存,所以数据库内部保证提交成功的数据即使宕机也不会丢失(不过实际上应用仍然需要处理网络异常等业务层面问题)。
查看当前MySQL支持事务的存储引擎:

- 可以看到只有InnoDB支持事务(transaction)
事务的使用
事务语法
sql
-- 手动开启一个事务,方式 1:
start transaction;
-- 方式2:begin;
-- 这里写你的多条业务SQL...
-- 接下来有两种选择:
-- ①:
commit;-- SQL全部执行成功,提交(落盘),数据这才永久保存在储存介质中
-- ②:
rollback;-- 如果中间出问题,就执行回滚,所有SQL执行的修改全部撤销
-- 无论执行的是 commit,还是 rollback,当前事务都会关闭,无法再对该事务进行任何操作了
保存点
在开启一个事务之后,在事务执行的过程中设置保存点,回滚时指定回滚到保存点,可以把数据恢复到保存点的状态
sql
-- 设置保存点,名字随便取(比如 sp1)
savepoint sp1;
sql
-- 回滚到保存点 sp1,而不是回滚整个事务
rollback to sp1;
- 保存点只能在同一个事务里面生效
- rollback to 保存点 :只撤销保存点之后的修改,事务继续保持打开,你还能继续写 SQL、继续回滚别的保存点,最后依旧需要 commit 或者完整 rollback
自动/手动提交事务
- 默认情况下事务是自动开启、提交或回滚的,也就是说只要你写了一条SQL语句,InnoDB就会在底层隐式开启一个事务,此时该事务中就只包含这一条SQL语句,SQL执行成功就自动 commit,执行失败就自动 rollback
可以查看一下"自动提交设置"的系统变量:

- 可以看到
autocommit为on
可以手动设置该系统变量:

- 0或off都是关闭
autocommit,1或on都是开启autocommit - 如果关闭了自动提交,那每次写SQL语句就必须要自己commit或rollback,但是只要使用
start transaction;手动开启了一个事务,无论autocommit是否开启,都要自己手动提交
注意:
set autocommit = 0这样的设置属于会话(session)级别,只在当前这一条数据库连接里面生效。- 关闭当前这个 MySQL 窗口(断开连接)直接失效。当然重启电脑 / 重启 MySQL 服务也失效,新连接恢复默认 autocommit = 1
- 每打开一个新的 mysql 黑窗口(也就是一个新的mysql客户端连接),就是一个全新会话,默认自动提交是打开的
为什么失效?
会话变量只是保存在内存里面,不会写入数据库的配置文件
- 只要连接断开,这块内存直接释放,这个设置就消失
- 重启 MySQL 服务,服务器重新读取配置文件,全部新连接恢复默认值
所以想要永久关闭自动提交,要手动去修改 my.ini 配置文件。
(不过自动提交本来就挺好的,一般不会去关闭它)
再总结一下事务的相关注意:
- 在一个事务A中进行数据的修改,虽然其他会话中的事务B可以正常访问到此次修改后的数据,但因为事务A的修改并没有落盘,所以不知道数据会不会继续修改或者是rollback,所以事务B拿到的很可能是错误的数据
- 接下来介绍如何通过设置隔离级别来解决这类问题
隔离级别
-
事务具有隔离性,那么如何实现事务之间的隔离?隔离到什么程度?如何保证数据安全的同时也要兼顾性能?
-
事务间不同程度的隔离,称为事务的隔离级别。不同的隔离级别在性能和安全方面做了取舍,有的隔离级别注重并发性,有的注重安全性,有的则是并发和安全适中
在MySQL的InnoDB引擎中事务的隔离级别有四种,分别是(按隔离级别由低到高排序):
①:read uncommitted :读未提交
②:read committed :读已提交
③:repeatable read :可重复读 (InnoDB 默认的隔离级别)
④:serializable :串行化
- 隔离级别越高,并发性能越差,但是越安全
在各隔离级别中存在的三个并发读问题:
①:脏读 :dirty read
②:不可重复读:non-repeatable read
③:幻读: phantom read
分别在各隔离级别中分析:
- read uncommitted 读未提交
别的事务还没提交,你就能读到它的修改,可能你拿到的数据还会修改或者rollback,那你拿到的数据就可能是错误的,也就是脏读。
这种隔离级别生产中几乎不会使用。
- read committed 读已提交(Oracle 默认)
只能读到别人已经提交的数据,不会脏读。
但是同一个事务内,进行了两次查询,中途别的事务对该数据进行了修改并且提交,这样的话对这一行数据的两次查询结果就会不一样,出现不可重复读。
- repeatable read 可重复读(MySQL InnoDB 默认)
使用该隔离级别,在同一个事务,多次读取同一行时,读到的永远是事务启动那一刻的数据,不会出现不可重复读。
但如果此时我们进行了前后两次范围查询,得到了两次的结果集,但是在两次范围查询中间,别的事务插入了满足条件的新数据,导致两次查询结果行数不一样,这就是幻读(可以和不可重复读区分,不可重复读是同一行数据查询结果不同,幻读是两次的查询结果集的数据条数不一样)
不过MySQL 的 InnoDB 在 repeatable read (它的默认隔离级别)中,依靠 mvcc(多版本) + 间隙锁(gap lock)来尝试解决幻读,但是它并没有从所有场景完全杜绝幻读,属于部分解决了幻读问题
- serializable 串行化
事务只能排队串行执行,完全没有并发问题,但是性能最差,也是几乎不用。
几点注意总结:
-
不要以为 repeatable read 所有数据库都能解决幻读,只有 InnoDB 依靠间隙锁做到,别的数据库 repeatable read 仍然存在幻读。
-
隔离级别只是定义标准,不同存储引擎对隔离级别的底层实现不一样,也就可能有不同的效果
-
隔离级别越高,锁越严格,并发越差
-
脏读一定是读到了未提交的数据。
查看和设置隔离级别
事务的隔离级别分为全局作用域和会话作用域,查看不同作用域事务的隔离级别,可以使用以下的方式:
sql
-- 查看当前会话隔离级别
select @@session.transaction_isolation;
-- 查看全局隔离级别
select @@global.transaction_isolation;
设置隔离级别:
①只修改当前会话:
sql
set session transaction isolation level 隔离级别;
②修改全局(新连接才生效):
sql
set global transaction isolation level 隔离级别;
- 如果上面的session和global都不写的话,隔离级别的设置就只对下一个事务生效
还有其他写法:

图片中写法能看得懂就行,还是推荐最上面的写法
-
图片中写法是直接给系统变量赋值的写法,隔离级别用字符串形式传入参数,而字符串里面不能写空格,所以用短横线代替
-
这几种写法是等价的,都只是修改了内存参数