12、事务
一、概念
- 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。事务用于保证数据的一致性。
- 事务处理的原则:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。
- 当在一个事务中执行多个操作时,要么所有的事务都被提交( commit )(全部成功),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚( rollback )(全部失败)到最初状态。
二、事务的ACID特性
-
原子性(atomicity)
- 原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚。
-
例如:A帐户向 B 帐户划账 1000,则先将A 减少 1000,再将 B 增加 1000,这两个动作要么都提交,要么都回退,不可能发生一个有效、一个无效的情况。
-
一致性(consistency)
- 一致性是指事务执行前后,数据从一个合法性状态变换到另外一个合法性状态。这种状态是语义上的而不是语法上的,跟具体的业务有关。
-
隔离型(isolation)
- 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
-
持久性(durability)
- 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
- 持久性是通过事务日志来保证的。日志包括了重做日志和回滚日志。当我们通过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改。这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重做日志,重新执行,从而使事务具有持久性。
ACID 是事务的四大特性,在这四个特性中,原子性是基础,隔离性是手段,一致性是约束条件,而持久性是我们的目的。
数据库事务,其实就是数据库设计者为了方便起见,把需要保证原子性、隔离性、一致性和持久性的一个或多个数据库操作称为一个事务。
三、事务的状态
- 活动的(active)
- 事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。
- 部分提交的(partially committed)
- 当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
- 失败的(failed)
- 当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
- 中止的(aborted)
- 如果事务执行了一部分而变为失败的状态,那么就需要把已经修改的事务中的操作还原到事务执行前的状态。换句话说,就是要撤销失败事务对当前数据库造成的影响。我们把这个撤销的过程称之为回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的状态。
- 提交的(committed)
- 当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。
由此可见,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算结束。对于已经提交的事务来说,该事务对数据库所做的修改将永久生效,对于处于中止状态的事务,该事务对数据库所做的修改都会被回滚到没执行该事务之前的状态。
四、如何使用事务
使用事务有两种方式,分别为显示事务和隐式事务。
1、显示事务
START TRANSACTION
或者BEGIN
,作用是显示开启一个事务一系列事务中的操作(主要是DML,不含DDL)- START TRANSACTION语句相较于BEGIN特别之处在于,后边能跟随几个修饰符 :
- READ ONLY:标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。只读事务只是不允许修改那些其它事务也能访问到的表中的数据,对于临时表来说(使用CREATE TEMPORARY TABLE创建的表),由于它们只能在当前会话中可见,所以只读事务其实也是可以对临时表进行增,删、改等操作。
- READ WRITE(默认):标识当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据。
- WITH CONSISTENT SNAPSHOT:启动一致性读。
- START TRANSACTION语句相较于BEGIN特别之处在于,后边能跟随几个修饰符 :
- 一系列事务中的操作(主要是DML,不含DDL)
COMMIT
(提交事务)或ROLLBACK
(中止事务,即回滚事务)- 将事务回滚到某个保存点:ROLLBACK TO 保存点名称;
- 在事务中创建保存点:SAVE SAVEPOINT 保存点名称;
- 删除某个保存点:REPLACE 保存点名称;
2、隐式事务
默认情况下,如果我们不显示的使用START TRANSACTION或者BEGIN语句开启一个事务,那么每一条语句都算是一个独立的事务,这种特性称之为事务的自动提交。如果我们想要关闭这种自动提交的功能,可以显示的使用START TRANSCATION或者BEGIN语句开启一个事务。这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能。或者把系统变量autocommit的值设置为OFF。
3、隐式提交数据的情况
- 数据定义语言(Data definition language,缩写为:DDL)
- 数据库对象,指的就是数据库、表、视图、存储过程等结构。当我们使用CREATE、ALTER、DROP等语句取修改数据库对象是,就会隐式的提交前面语句所属的事务。
- 隐式使用或修改mysql数据库中的表
- 当我们使用ALTER USER、CREATE USER、DROP USER、GRANT USER、RENAME USER、REVOKE、SET PASSWORD等语句也会隐式的提交前面语句所属于的事务。
- 事务控制或关于锁定的语句
- 当我们在一个事务还没提交或者回滚时就又使用START TRANSACTION或者BEGIN语句开启了另一个事务时,会隐式的提交上一个事务。
- 当前的autocommit系统变量的值为OFF,我们手动把它调为ON时,也会隐式的提交前边语句所属的事务。
- 使用LOCK TABLES、UNLOCK TABLES等关于锁定的语句也会隐式的提交前边语句所属的事务。
- 加载数据的语句
- 使用LOAD DATA语句来批量往数据库中导入数据时,也会隐式的提交前面语句所属的事务。
- 关于MySQL复制的一些语句
- 使用START SLAVE、CREATE SLAVE、RESET SLAVE、CHANGE MASTER TO等语句时会隐式的提交前面语句所属的事务。
- 其它的一些语句
- 使用ANALYZE TABLE、CACHE INDEX、CHECK TABLE、FLUSH、LOAD INDEX INTO CACHE、OPTIMIZE TABLE、RAPAIR TABLE、RESET等语句也会隐式的提交前面语句所属的事务。
五、MySQL事务控制
- 一般来说,mysql默认开启了事务自动提交功能,每条sql执行都会提交事务。可以使用如下语句关闭事务自动提交功能。
- 在mysql中,事务只针对innodb存储引擎,而MYISAM是非事务的存储引擎,不支持事务
- 事务开始 begin或start transaction;
- 事务提交 commit或commit work;
- 回滚 rollback或rollback work;
- 保存点设置 savepoint 标识;
- 回滚到保存点 rollback to 标识;
- 删除保存点 release savepoint 标识。
六、实例演示
- 下面通过两个例子来演示一下 MySQL 事务的具体用法。
-- 创建账号表 create table account( account_id varchar(36) PRIMARY KEY, -- 使用UUID生成唯一值,UUID32+4 account_name varchar(15) NOT NULL, balance decimal(10,2) NOT NULL, -- 十进制 reg_date date NOT NULL ); -- 插入数据到账号表 INSERT INTO account(account_id,account_name,balance,reg_date) VALUES(UUID(),'路飞',1000,CURDATE()); INSERT INTO account(account_id,account_name,balance,reg_date) VALUES(UUID(),'索隆',2000,CURDATE());
示例 1
下面模拟在路飞的账户减去 500 元后,账号编号索隆的账户还未增加 500 时,有其他会话访问数据表的场景。
由于代码需要在两个窗口中执行,为了方便阅读,这里我们称为 A 窗口和 B 窗口。
- 在 A 窗口中开启一个事务,更新 account 表“路飞”的余额数据,SQL 语句和运行结果如下:
-- 开启事务 start transaction; -- 进行更新数据 update account set balance = balance - 500 where account_id = '65a476b9-abea-11ec-b6ed-0a0027000018'; -- 单旺 select * from account;
- 在 B 窗口中查询 account 数据表中的数据,运行结果如下:
从结果可以看出,虽然 A 窗口中的事务已经更改了 account表中的数据,但没有立即更新数据(没有提交事务),这时其他会话读取到的仍然是更新前的数据。
- 在 A 窗口中继续执行事务,更新“索隆”数据,并提交事务,SQL 语句和运行结果如下:
-- 继续更新 update account set balance = balance + 500 where account_id = '65a629b2-abea-11ec-b6ed-0a0027000018'; -- 伟文 -- 提交事务 COMMIT; select * from account;
- 在 B 窗口中再次查询 bank 数据表的数据,SQL 语句和运行结果如下:
在 A 窗口中执行 COMMIT 提交事务后,对数据所做的更新将一起提交,其他会话读取到的是更新后的数据。
从结果可以看出路飞和索隆的总账户余额和转账前保持一致,这样数据从一个一致性状态更新到另一个一致性状态。1000+2000=500+2500。
示例 2
前面提到,当事务在执行中出现问题,也就是不能按正常的流程执行一个完整的事务时,可以使用 ROLLBACK 语句进行回滚,使用数据恢复到初始状态。
在例1中,路飞的账户余额已经减少到 500 元,如果再转出 1000 元,将会出现余额为负数,因此需要回滚到原始状态。如例 2 所示。
- 将路飞的账户余额减少 1000 元,并让事务回滚
- 进行事务的回滚事务,然后再一次查询
-- 开启事务 start transaction; -- 进行更新数据 update account set balance = balance - 1000 where account_id = '65a476b9-abea-11ec-b6ed-0a0027000018'; -- 单旺 -- 本窗口查询 select * from account; -- 回滚事务 ROLLBACK; select * from account;
从结果可以看出,执行事务回滚后,账户数据恢复到初始状态,即该事务执行之前的状态。
拓展
在数据库操作中,为了有效保证并发读取数据的正确性,提出了事务的隔离级别。在例 1 和例 2 的演示中,事务的隔离级别为默认隔离级别。
在 MySQL 中,事务的默认隔离级别是 REPEATABLE-READ (可重读)隔离级别,即事务未结束时(未执行 COMMIT 或 ROLLBACK),其它会话只能读取到未提交数据。
七、事务的隔离级别
MySQL是一个客户端/服务器架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每个客户端与服务器连接上之后,就可以称为一个会话( Session )。每个客户端都可以在自己的会话中向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就是对于服务器来说可能同时处理多个事务。事务有隔离性的特性,理论上在某个事务对某个数据进行访问时,其他事务应该进行排队,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样对性能影响太大,我们既想保持事务的隔离性,又想让服务器在处理访问同一数据的多个事务时 性能尽量高些 ,那就看二者如何权衡取舍了。
1、数据并发问题
-
脏写( Dirty Write )
对于两个事务 Session A、Session B,如果事务Session A修改了另一个未提交事务Session B修改过的数据。
-
脏读( Dirty Read )
对于两个事务 Session A、Session B,Session A读取了已经被 Session B更新但还没有被提交的字段。之后若 Session B回滚,Session A 读取 的内容就是临时且无效的。
-
不可重复读( Non-Repeatable Read )
对于两个事务Session A、Session B,Session A读取了一个字段,然后 Session B更新了该字段。 之后Session A再次读取同一个字段,值就不同了。那就意味着发生了不可重复读。
-
幻读( Phantom )
对于两个事务Session A、Session B, Session A 从一个表中读取了一个字段, 然后 Session B 在该表中插入了一些新的行。之后, 如果 Session A再次读取同一个表,就会多出几行。那就意味着发生了幻读。
如果Session B中删除了一些符合条件的记录而不是插入新的记录,那Session A再根据此条件读取的记录变少了,这种现象不属于幻读。幻读强调的是一个事务按照某个相同条件多次读取记录时,后读到时读到了之前没有读到的数据。
2、SQL中的四种隔离级别
- READ UNCOMMITTED:读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。
- READ COMMITTED:读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。
- REPEATABLE READ:可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍然存在。这是MySQL的默认隔离级别。
- SERIALIZABLE:可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。
SQL标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
READ UNCONMITED | YES | YES | YES | NO |
READ COMMITED | NO | YES | YES | NO |
REPEATABLE READ | MO | NO | YES | NO |
SERIALIZABLE | NO | NO | NO | YES |
3、MySQL支持的四种隔离级别
(1)查看隔离级别
SELECT @@transaction_isolation;
SHOW VARIABLES LIKE 'transaction_isolation';
2、设置隔离级别
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 的值。
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本