一、事务是什么?🤔
数据库事务概述
事务是数据库的重要特性之一,确保数据库的一致性并支持恢复到某个时间点,防止因系统崩溃而丢失已提交的修改。
存储引擎支持情况
使用 SHOW ENGINES 命令查看当前 MySQL 支持的存储引擎及其是否支持事务。只有 InnoDB 引擎支持事务。
基本概念
- 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
- 事务处理的原则:确保所有操作作为一个工作单元执行。若发生故障,事务要么全部提交(commit),要么回滚(rollback)到初始状态。
案例: AA给BB转账100元
update account set money = money - 100 where name ='AA';
#服务器宕机
update account set monky = money + 100 where name ='BB' ;
在这个案例中,如果事务未能完成,所有操作将被回滚,确保数据一致性。
二、事务的四大特性:ACID 🛡️
- 原子性(atomicity) :事务是一个不可分割的单位,要么全部提交,要么全部回滚。例如,转账要么成功,要么失败,不能出现中间状态。如果原子性无法保证,可能导致数据不一致,如 A 账户减去 100 元而 B 账户未增加,造成 100 元的损失。。
- 一致性(consistency) :事务执行前后,数据状态应从一个合法状态变换到另一个合法状态。这种合法状态由预定约束定义;不满足约束则数据不一致。如果操作失败,系统会自动撤销事务,恢复到之前的状态。
- 隔离型(isolation) :一个事务的执行不应被其他事务干扰。每个事务内部的操作和数据应与并发执行的其他事务隔离,确保互不干扰。
- 持久性(durability) :一旦事务提交,对数据的修改是永久性的,后续操作和数据库故障不应影响其结果。持久性通过事务日志实现,日志记录修改信息以便在系统崩溃后恢复。
总结
ACID 是事务的四大特性:原子性是基础,隔离性是手段,一致性是约束条件,持久性是目标。数据库事务将需要保证这四个特性的操作集合称为一个事务。
三、事务的状态流转 🔄
事务的状态
事务是一个抽象概念,MySQL 根据执行阶段将其划分为以下几种状态:
- 活动 (Active):事务对应的数据库操作正在执行。
- 部分提交 (Partially Committed):最后一个操作完成,但影响尚未刷新到磁盘。
- 失败 (Failed):事务在活动或部分提交状态下遇到错误或被人为停止。
- 中止 (Aborted):事务失败后,需要将已修改的数据恢复到执行前的状态,称为回滚。回滚完成后,事务进入中止状态。
- 提交 (Committed):当部分提交状态的事务将修改同步到磁盘后,事务进入提交状态。
生命周期总结
只有当事务处于提交或中止状态时,它的生命周期才结束。提交的事务对数据库的修改永久生效,而中止状态的事务所做的修改将被回滚。
四、如何使用事务? 🛠️
使用事务有显式事务和隐式事务两种方式。
1. 显式事务
步骤1:开启事务。
mysql> BEGIN;
#或者
mysql> START TRANSACTION;
START TRANSACTION 可以后接修饰符:
- READ ONLY︰只读事务,不能修改数据。
补充:只读事务中只是不允许修改那些其他事务也能访问到的表中的数据,对于临时表来说〔我们使用CREATETMEPORARY TABLE创建的表),由于它们只能在当前会话中可见,所以只读事务其实也是可以对临时表进行增、删、改操作的。 - READ WRITE(默认):读写事务,可以读取和修改数据。
- WITH CONSISTENT SNAPSHOT:启动一致性读。比如:
START TRANSACTION READ ONLY;#开启一个只读事务
START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT;#开启只读事务和一致性读
START TRANSACTION READ WRITE, WITH CONSISTENT SNAPSHOT; #开启读写事务和一致性读
注意:
- READ ONLY和READ WRITE是用来设置所谓的事务访问模式的,就是以只读还是读写的方式来访问数据库中的数据,一个事务的访问模式不能同时既设置为只读的也设置为读写的,所以不能同时把READ ONLY和READ WRITE放到START TRANSACTION语句后边。
- 如果我们不显式指定事务的访问模式,那么该事务的访问模式就是读写模式。
步骤2:执行操作(主要是 DML,不含 DDL)。
步骤3:提交或回滚事务
#提交事务。当提交事务后,对数据库的修改是永久性的。
mysql> COMMIT;
#回滚事务。即撤销正在进行的所有没有提交的修改
mysql> ROLLBACK;
#将事务回滚到某个保存点。
mysql> ROLLBACK TO [SAVEPOINT ]
保存点操作
其中关于SAVEPOINT相关操作有:
#在事务中创建保存点,方便后续针对保存点进行回滚。一个事务中可以存在多个保存点。
SAVEPOINT 保存点名称;
#删除某个保存点。
RELEASE SAVEPOINT 保存点名称;
2. 隐式事务
# MysQL中有一个系统变量autocommit:
SHOW VARIABLES LIKE 'autocommit'; ==>默认是ON
MySQL 默认启用自动提交(autocommit),可通过以下方式关闭:
- 在autocommit为true的情况下,显式的的使用START TRANSACTION或者BEGIN语句开启一个事务。这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能。
- 把系统变量autocommit的值设置为OFF,就像这样:
SET autocommit = OFF;
#或
SET autocommit = 0 ;
在这种情况下,直到显式使用 COMMIT 或 ROLLBACK 之前,所有操作都视为同一事务。
补充:Oracle默认不自动提交,需要手写COMMIT命令,而MySQL默认自动提交。
隐式提交情况
- DDL 语句(如 CREATE、ALTER、DROP)会隐式提交前一个事务。
BEGIN;
SELECT ...#事务中的一条语句
UPDATE ...#事务中的一条语句
... #事务中的其它语句
CREATE TABLE ... #此语句会隐式的提交前边语句所属于的事务
- 用户管理语句(如 CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASSWORD等·)也会隐式提交。
- 事务控制语句
(1)(如再次使用 BEGIN或START TRANSACTION)会隐式提交前一个事务。
BEGIN;
SELECT ...# 事务中的一条语句
UPDATE ...#事务中的一条语句
...#事务中的其它语句
BEGIN;#此语句会隐式的提交前边语句所属于的事务
(2)当前的autocommit系统变量的值为OFF,我们手动把它调为ON时,也会隐式提交前边语句所属的事务。
- 锁定语句(如 LOCK TABLES、UNLOCK TABLES等)也会隐式提。
- 数据加载(如 LOAD DATA)时会隐式提交。
- 复制相关语句(如 START SLAVE、STOP SLAVE、RESET SLAVE、CHANGE MASTER TO等)会隐式提交。
- 其他语句(如 ANALYZE TABLE、 CACHE INDEX、CHECK TABLE、FLUSH、LOAD INDEX INTO CACHE、OPTIMIZE TABLE、REPAIR TABLE、RESET等)同样会隐式提交。
2.3 使用示例 1:提交与回滚
MySQL 的 completion_type 参数有三种设置:
- completion=0:默认,需手动开启新事务。当我们执行COMMIT的时候会提交事务,在执行下一个事务时, 还需要使用START TRANSACTION或者BEGIN来开启。
- completion=1:提交后自动开启链式事务(COMMIT AND CHAIN),即当我们提交事务之后会开启一个相同隔离级别的事务。
- completion=2:提交后自动与服务器断开连接(COMMIT=COMMIT AND RELEAIBE)。
- 当autocommit=0时,不论是否采用START TRANSACTION或者BEGIN的方式来开启事务,都需要用COMMIT进行提交,让事务生效,使用ROLLBACK对事务进行回滚。
- 当autocommit=1时,每条SQL语句都会自动进行提交。不过这时,如果你采用START TRANSACTION或者BEGIN的方式来显式地开启事务,那么这个事务只有在COMMIT时才会生效,在ROLLBACK时才会回滚。
2.4 使用示例 2:不支持事务的引擎
MYISAM 引擎不支持事务,使用 ROLLBACK 无效。
2.5 使用示例 3:SAVEPOINT
BEGIN;
SAVEPOINT s1; -- 创建保存点
ROLLBACK TO s1; -- 回滚到保存点
ROLLBACK; -- 回滚到开始状态
五、事务的隔离级别 🚦
MySQL 是客户端/服务器架构,多个客户端可以同时连接同一服务器。每个客户端与服务器连接上之后,就可以称为一个会话( Session )。每个客户端都可以在自己的会话中向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就是对于服务器来说可能同时处理多个事务。但事务的隔离性要求在某个事务访问数据时,其他事务需排队访问,当该事务提交之后,其他事务才可以继续访问这个数据。
但是这样对性能影响太大,我们既想保持事务的隔离性,又想让服务器在处理访问同一数据的多个事务时性能尽量高些,那就看二者如何权衡取舍了。
1. 数据并发问题
当多个事务并发访问相同数据时,可能出现以下问题:
- 脏写(Dirty Write)
一个事务修改了另一个未提交事务的数据,导致数据不一致。
Session A和Session B各开启了一个事务,Session B中的事务先将studentno列为1的记录的name列更新为’李四’,然后Session A中的事务接着又把这条studentno列为1的记录的name列更新为’张三’。如果之后Session B中的事务进行了回滚,那么Session A中的更新也将不复存在,这种现象就称之为脏写。这时Session A中的事务就没有效果了,明明把数据更新了,最后也提交事务了,最后看到的数据什么变化也没有。这里大家对事务的隔离级比较了解的话,会发现默认隔离级别下,上面SessionA中的更新语句会处于等待状态,这里只是跟大家说明一下会出现这样现象。
2. 脏读(Dirty Read)
一个事务读取了另一个未提交事务的数据,若后者回滚,则前者读取的数据无效。
Session A和Session B各开启了一个事务,Session B中的事务先将studentno列为1的记录的name列更新为张三’,然后Session A中的事务再去查询这条studentno为1的记录,如果读到列name的值为’张三’,而Session B中的事务稍后进行了回滚,那么Session A中的事务相当于读到了一个不存在的数据,这种现象就称之为脏读
3. 不可重复读(Non-Repeatable Read)
一个事务在两次读取同一数据时,数据发生变化。
我们在Session B中提交了几个隐式事务(注意是隐式事务,意味着语句结束事务就提交了),这些事务都修改了studentno列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看到最新的值,这种现象也被称之为不可重复读。
4.幻读(Phantom)
一个事务读取数据后,另一个事务插入新记录,导致后续读取时多出记录。
Session A中的事务先根据条件studentno >0这个条件查询表student,得到了name列值为’张三’的记录;之后Session B中提交了一个隐式事务,该事务向表student中插入了一条新记录;之后Session A中的事务再根据相同的条件studentno >o查询表student,得到的结果集中包含Session B中的事务新插入的那条记录,这种现象也被称之为幻读。我们把新插入的那些记录称之为幻影记录。
注意1:
有的同学会有疑问,那如果Session B中删除了一些符合studentno > 0的记录而不是插入新记录,那Session A之后再根据studentno > 0的条件读取的记录变少了,这种现象算不算幻读呢?这种现象不属于幻读,幻读强调的是一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录。
注意2:
那对于先前已经读到的记录,之后又读取不到这种情况,算啥呢?这相当于对每一条记录都发生了不可重复读的现象。幻读只是重点强调了读取到了之前读取没有获取到的记录。
2. SQL中的四种隔离级别
不同的隔离级别影响并发问题的发生,按照严重性排序为:脏写 > 脏读 > 不可重复读 > 幻读。
设立一些隔离级别,隔离级别越低,并发问题发生的就越多。SQL标准中设立了4个隔离级别:
- READ UNCOMMITTED:读未提交,允许所有事务读取未提交的数据,无法避免所有问题。
- READ COMMITTED:读已提交,事务只能读取已提交的数据,避免脏读,但不可重复读和幻读仍然存在。
- REPEATABLE READ:可重复读,事务多次读取同一数据时返回一致值,避免脏读和不可重复读,但幻读仍然可能。这是 MySQL 的默认隔离级别。
- SERIALIZABLE:可串行化,完全避免并发问题,但性能较差,确保所有事务顺序执行。
SQL标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:
注:oracle只支持读已提交和可串行化两种隔离界别。
脏写怎么没涉及到?
因为脏写这个问题太严重了,不论是哪种隔离级别,都不允许脏写的情况发生.
不同的隔离级别有不同的现象,并有不同的锁和并发机制,隔离级别越高,数据库的并发性能就越差,4种事务隔离级别与并发性能的关系如下:
3. 设置事务的隔离级别
#查询当前会话的隔离级别
mysql> SHOW VARIABLES LIKE 'transaction_isolation'; ===> REPEATABLE-READ
mysql> select @@transaction_isolation;
通过以下语句修改事务的隔离级别:
# 方式一:设置隔离级别
SET [GLOBAL |SESSION ] TRANSACTION_ISOLATION LEVEL 隔离级别;
其中,隔离级别格式:
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
# 方式二:或者(推荐使用下面的):
SET [GLOBAL | SESSION] TRANSACTION_ISOLATION ='隔离级别’
#其中,隔离级别格式:
READ-UNCOMMITTED
READ-COMMITTED
REPEATABLE-READ
SERIALIZABLE
注:使用 GLOBAL 影响所有后续会话(当前已经存在的会话无效),使用 SESSION 仅影响当前会话。
如果在服务器启动时想改变事务的默认隔离级别?
可以修改启动参数transaction_isolation值。比如,在启动服务器时指定了transaction_isolation=SERIALIZABLE,那么事务的默认隔离级别就从原来的REPEATABLE-READ变成了SERIALIZABLE。
小结:
数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
4. 不同隔离级别举例
幻读并不表现为两次读取结果集不同,而是指某次 SELECT 操作得到的数据状态无法支持后续业务操作。例如,一个事务检查某记录是否存在,若不存在则准备插入,但在执行插入时发现该记录已存在,这就是幻读。
在可重复读 (RR) 隔离级别下,假设事务 A 先执行 SELECT,然后事务 B 插入相同数据。事务 A 再次读取时,发现与第一次相同,但实际无法插入,因为该数据已被事务 B 插入。此时,事务 A 发生了幻读。
为了避免幻读,事务 A 可以在 SELECT 时手动加行锁(使用 SELECT … FOR UPDATE),这会在 RR 隔离级别下隐式执行。即使记录不存在,当前事务也会获得锁,阻止其他事务插入,从而避免幻读。
在序列化 (SERIALIZABLE) 隔离级别下,事务 A 执行时会隐式添加行锁,阻塞后续事务,确保数据一致性。幻读的根本问题在于,事务在插入前未能正确识别已存在的数据。
六、事务的常见分类 📂
从理论角度看,事务可以分为以下几种类型:
- 扁平事务 (Flat Transactions):最简单且使用最频繁的事务类型。所有操作在同一层次,由 BEGIN WORK 开始,COMMIT WORK 或 ROLLBACK WORK 结束,确保操作原子性。主要结果有:成功完成(约 占96%)、应用程序要求停止【如程序在捕获到异常时会回滚事务】(约 占3%)、外界因素强制终止【如连接超时或连接断开】(约 占1%)。
- 带有保存点的扁平事务 (Flat Transactions with Savepoints):允许在事务执行过程中回滚到早期状态,以处理局部错误,避免放弃整个事务。
- 链事务 (Chained Transactions):事务串联执行,前一个事务的结果可作为下一个事务的输入,形成依赖关系。
- 嵌套事务 (Nested Transactions):由顶层事务控制的层次结构,包含多个子事务。每个子事务可以独立执行,并可能包含其嵌套事务。
- 分布式事务 (Distributed Transactions):在分布式环境中运行的扁平事务,涉及多个节点的数据库资源。例如,银行间转账需调用不同银行的数据库,构成分布式事务。
七、事务实战小贴士 💡
- 尽量使用显式事务:避免隐式提交导致意外结果。
- 合理设置隔离级别:根据业务需求选择,避免过度隔离影响性能。
- 避免长事务:长时间未提交的事务会占用资源,影响并发性能。
- 善用保存点:复杂事务中,保存点可以帮助部分回滚,减少全量回滚的开销。
参考:尚硅谷视频学习笔记