如何通过MySQL事务实现复杂业务逻辑的自动处理?

摘要:2. 如何使用事务 使用事务有两种方式,分别为 显式事务 和 隐式事务 。 2.1 显式事务 步骤1
2. 如何使用事务 使用事务有两种方式,分别为 显式事务 和 隐式事务 。 2.1 显式事务 步骤1: START TRANSACTION或者BEGIN,作用是显式开启一个事务。 mysql> BEGIN; #或者 mysql> START TRANSACTION; START TRANSACTION 语句,相较于 BEGIN 特别之处在于,后边能跟随几个 修饰符 : ① READ ONLY :标识当前事务是一个 只读事务 ,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。 补充:只读事务中只是不允许修改,那些其他事务也能访问到的表中的数据。对于临时表来说(我们使用 CREATE TMEPORARY 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.2 隐式事务 MySQL中有一个系统变量 autocommit : mysql> SHOW VARIABLES LIKE 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.01 sec) 默认情况下,如果我们不显式的使用 START TRANSACTION 或者 BEGIN 语开启一个事务,那么每一条语句都算是一个独立的事务,这种特性称之为事务的 自动提交。 也就是说,不以START TRANSACTION 或者 BEGIN 语句显式的开启一个事务,那么下边这两条语句,就相当于放到两个独立的事务中去执行: UPDATE account SET balance = balance - 10 WHERE id = 1; UPDATE account SET balance = balance + 10 WHERE id = 2; 当然,如果我们想关闭这种 自动提交 的功能,可以使用下边两种方法之一: 显式的的使用 START TRANSACTION 或者 BEGIN 语句开启一个事务。这样在本次事务提交、或者回滚前,会暂时关闭掉自动提交的功能。 把系统变量 autocommit 的值设置为 OFF ,就像这样: SET autocommit = OFF; #针对于DMI操作是有效的,对DDL操作是无效的. #或 SET autocommit = 0; 这样的话,我们写入的多条语句就算是属于同一个事务了,直到我们显式的写出 COMMIT 语句,来把这个事务提交掉,或者显式的写出 ROLLBACK 语句,来把这个事务回滚掉。 2.3 隐式提交数据的情况 1)数据定义语言(Data definition language,缩写为:DDL) 数据库对象,指的就是数据库、表、视图、存储过程等结构。 当我们CREATE、ALTER、DROP等语句去修改数据库对象时,就会隐式的提交前边语句所属于的事务。 即: BEGIN; SELECT ... # 事务中的一条语句 UPDATE ... # 事务中的一条语句 ... # 事务中的其他语句 CREATE TABLE ... # 此语句会隐式的提交前边语句所属于的事务 2)隐式使用或修改mysql数据库中的表 当我们使用ALTER USER、CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASSWORD等语句时,也会隐式的提交前边语句所属于的事务。 3)事务控制或关于锁定的语句 ① 当我们在一个事务还没提交或者回滚时,就又使用START TRANSACTION 或者 BEGIN 语句,开启了另一个事务时,会隐式的提交上一个事务。即: BEGIN; SELECT ... # 事务中的一条语句 UPDATE ... # 事务中的一条语句 ... # 事务中的其他语句 BEGIN; # 此语句会隐式的提交前边语句所属于的事务 ② 当前的 autocommit 系统变量的值为 OFF ,我们手动把它调为 ON 时,也会 隐式的提交前边语句所属的事务。 ③ 使用 LOCK TABLES 、 UNLOCK TABLES 等关于锁定的语句,也会 隐式的提交 前边语句所属的事务。 4)加载数据的语句 使用LOAD DATA语句,来批量往数据库中导入数据时,也会隐式的提交前边语句所属的事务。 5)关于MySQL复制的一些语句 使用START SLAVE、STOP SLAVE、RESET SLAVE、CHANGE MASTER TO等语句,会隐式的提交前边语句所属的事务 6)其他的一些语句 使用ANALYZE TABLE、CACHE INDEX、CAECK TABLE、FLUSH、LOAD INDEX INTO CACHE、OPTIMIZE TABLE、REPAIR TABLE、RESET等语句,也会隐式的提交前边语句所属的事务。 2.4 使用举例1:提交与回滚 我们看下在 MySQL 的默认状态下,下面这个事务最后的处理结果是什么。 情况1: CREATE TABLE user(name varchar(20), PRIMARY KEY (name)) ENGINE=InnoDB; BEGIN; INSERT INTO user SELECT '张三'; COMMIT; BEGIN; INSERT INTO user SELECT '李四'; INSERT INTO user SELECT '李四'; ROLLBACK; SELECT * FROM user; 运行结果(1 行数据): mysql> commit; Query OK, 0 rows affected (0.00 秒) mysql> BEGIN; Query OK, 0 rows affected (0.00 秒) mysql> INSERT INTO user SELECT '李四'; Query OK, 1 rows affected (0.00 秒) mysql> INSERT INTO user SELECT '李四'; Duplicate entry '李四' for key 'user.PRIMARY' mysql> ROLLBACK; Query OK, 0 rows affected (0.01 秒) mysql> select * from user; +--------+ | name | +--------+ | 张三 | +--------+ 1 行于数据集 (0.01 秒) 情况2: CREATE TABLE user (name varchar(20), PRIMARY KEY (name)) ENGINE=InnoDB; BEGIN; INSERT INTO user SELECT '张三'; COMMIT; INSERT INTO user SELECT '李四'; INSERT INTO user SELECT '李四'; ROLLBACK; 运行结果(2 行数据): mysql> SELECT * FROM user; +--------+ | name | +--------+ | 张三 | | 李四 | +--------+ 2 行于数据集 (0.01 秒) 情况3: CREATE TABLE user(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB; SET @@completion_type = 1; BEGIN; INSERT INTO user SELECT '张三'; COMMIT; INSERT INTO user SELECT '李四'; INSERT INTO user SELECT '李四'; ROLLBACK; SELECT * FROM user; 运行结果(1 行数据): mysql> SELECT * FROM user; +--------+ | name | +--------+ | 张三 | +--------+ 1 行于数据集 (0.01 秒) 你能看到相同的SQL代码,只是在事务开始之前设置了SET @@completion_type = 1;,结果就和我们第一次处理的一样,只有一个“张三”。这是为什么呢? 这里我讲解下MySQL中completion_type 参数的作用,实际上这个参数有3种可能 completion=0,这是默认情况。当我们执行 COMMIT 的时候会提交事务,在执行下一个事务时,还需要使用 START TRANSACTION 或者 BEGIN 来开启 completion=1,这种情况下,当我们提交事务后,相当于执行了 COMMIT AND CHAIN,也就是开启一个链式事务,即当我们提交事务之后会开启一个相同隔离级别的事务 completion=2,这种情况下 COMMITECOMMIT AND RELEASE,也就是当我们提交后,会自动与服务器断3开连接。 当我们设置 autocommit=0 时,不论是否采用 START TRANSACTION 或者 BEGIN 的方式来开启事 务,都需要用 COMMIT 进行提交,让事务生效,使用 ROLLBACK 对事务进行回滚。 当我们设置 autocommit=1 时,每条 SQL 语句都会自动进行提交。 不过这时,如果你采用 START TRANSACTION 或者 BEGIN 的方式来显式地开启事务,那么这个事务只有在 COMMIT 时才会生效, 在 ROLLBACK 时才会回滚。 2.5 使用举例2:测试不支持事务的engine CREATE TABLE test1(i INT) ENGINE=InnoDB; CREATE TABLE test2(i INT) ENGINE=MYISAM; 针对于InnoDB表 BEGIN; INSERT INTO test1 VALUES(1); ROLLBACK; SELECT * FROM test1; 结果:没有数据 针对于MYISAM表: BEGIN; INSERT INTO test1 VALUES(1); ROLLBACK; SELECT * FROM test2; 结果:有一条数据 2.6 使用举例3:SAVEPOINT 创建表并添加数据: CREATE TABLE account( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(15), balance DECIMAL(10,2) ); INSERT INTO account(NAME,balance) VALUES ('张三',1000), ('李四',1000); BEGIN; UPDATE account SET balance = balance - 100 WHERE NAME = '张三'; UPDATE account SET balance = balance - 100 WHERE NAME = '张三'; SAVEPOINT s1; # 设置保存点 UPDATE account SET balance = balance + 1 WHERE NAME = '张三'; ROLLBACK TO s1; # 回滚到保存点 结果:张三:800.00 ROLLBACK; 结果:张三:1000.00 2.7 使用举例4 案例:转账 张三丰 1000 郭襄 1000 开启事务的语句; update 表 set 张三丰的余额=500 where name='张三丰' update 表 set 郭襄的余额=1500 where name='郭襄' 结束事务的语句; #1.演示事务的使用步骤 #开启事务 SET autocommit=0; START TRANSACTION; #编写一组事务的语句 UPDATE account SET balance = 1000 WHERE username='张无忌'; UPDATE account SET balance = 1000 WHERE username='赵敏'; #结束事务 ROLLBACK; #commit; SELECT * FROM account; #2.演示事务对于delete和truncate的处理的区别 SET autocommit=0; START TRANSACTION; DELETE FROM account; ROLLBACK; #3.演示savepoint 的使用 SET autocommit=0; START TRANSACTION; DELETE FROM account WHERE id=25; SAVEPOINT a;#设置保存点 DELETE FROM account WHERE id=28; ROLLBACK TO a;#回滚到保存点 SELECT * FROM account; 3. COMMIT 和 ROLLBACK 用法 1)基础概念 事务是数据库中一组不可分割的原子性操作(仅限增、删、改),遵循ACID四大特性(原子性、一致性、隔离性、持久性),是保证数据安全的核心机制。MySQL中只有InnoDB引擎支持事务,MyISAM引擎无事务能力,无法使用COMMIT和ROLLBACK。 COMMIT(提交事务):将当前事务内所有的数据库修改操作,永久同步保存到磁盘,提交完成后事务结束,后续无法撤销本次修改。 ROLLBACK(回滚事务):撤销当前事务内所有未提交的增删改操作,将数据恢复到事务开启前的初始状态,回滚完成后事务结束,数据保持一致。 2)前置使用条件 数据表存储引擎必须指定为InnoDB,建表时需标注ENGINE=InnoDB,否则事务失效。 MySQL默认开启自动提交模式(autocommit=1),每执行一条SQL语句会自动触发COMMIT,无法手动回滚;开启事务后,会临时关闭当前会话的自动提交,需手动控制提交或回滚。 仅针对数据操纵语言(DML:INSERT/UPDATE/DELETE)生效,数据定义语言(DDL:CREATE/ALTER/DROP/TRUNCATE)不支持事务回滚。 3)标准语法与执行流程 事务操作遵循固定流程,必须先开启事务,再执行业务SQL,最后根据执行结果选择提交或回滚,核心语法如下: -- 开启事务(两种写法完全等价,任选其一) BEGIN; -- 或者 START TRANSACTION; -- 执行业务SQL(可单条、多条增删改操作) INSERT INTO 表名(字段1,字段2) VALUES(值1,值2); UPDATE 表名 SET 字段=新值 WHERE 筛选条件; DELETE FROM 表名 WHERE 筛选条件; -- 执行结果判断:二选一执行,不可同时触发 COMMIT; -- 业务无异常,提交事务,修改永久生效 ROLLBACK;-- 业务出现报错/异常,回滚事务,撤销所有修改 4)COMMIT和ROLLBACK能否同时使用? 语法上不能同时执行,实际项目中必须配套配合使用,具体规则如下: 同一事务内,COMMIT和ROLLBACK是互斥操作,不能同时执行两条命令,执行其中一条后事务立即终止,另一条命令完全失效。 先执行COMMIT再执行ROLLBACK:回滚无效,数据已永久保存,无法撤回。 先执行ROLLBACK再执行COMMIT:提交无效,数据已恢复初始状态,无修改可提交。 项目开发中,二者是事务的双出口,必须绑定使用:正常业务流程走COMMIT,捕获到异常、错误、业务校验失败时,立即触发ROLLBACK,缺一不可。 5)核心特性与避坑要点 提交/回滚范围仅限当前事务:仅撤销或保存本次事务开启后的操作,不影响事务外的历史数据。 异常断开自动回滚:事务未手动提交或回滚时,若数据库连接中断、会话关闭,MySQL会自动执行ROLLBACK,避免数据错乱。 DDL语句强制提交:执行CREATE、ALTER、DROP等库表结构操作时,会自动触发COMMIT,即使未手动提交,之前的事务修改也会永久生效,无法回滚。 查询语句不参与事务:SELECT查询不修改数据,不受事务提交、回滚的影响。 事务不可嵌套:MySQL不支持嵌套事务,重复开启事务会自动提交上一个未完成的事务。 6)项目案例(用户转账场景) 转账是事务最经典的应用场景,要求“扣款+加款”两步操作要么全部成功,要么全部失败,杜绝单边账(一方扣款、另一方未到账)。 步骤1:创建InnoDB引擎账户表 CREATE TABLE account( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '账户ID', username VARCHAR(30) NOT NULL COMMENT '账户名', balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '账户余额' ) ENGINE=InnoDB COMMENT '用户账户表' CHARSET=utf8mb4; -- 初始化测试数据 INSERT INTO account(username,balance) VALUES('张三',2000.00),('李四',1500.00); -- 查看初始数据 SELECT * FROM account; 步骤2:事务正常执行(COMMIT提交) 模拟张三给李四转账500元,两步操作均无异常,提交事务完成转账: -- 开启事务 START TRANSACTION; -- 1. 张三账户扣款500元 UPDATE account SET balance = balance - 500 WHERE username='张三'; -- 2. 李四账户加款500元 UPDATE account SET balance = balance + 500 WHERE username='李四'; -- 校验余额(防止扣款后余额为负) SELECT * FROM account WHERE username='张三'; -- 无异常,提交事务,数据永久生效 COMMIT; 步骤3:事务异常执行(ROLLBACK回滚) 模拟转账异常(如余额不足、SQL报错、系统故障),回滚撤销所有操作: -- 开启事务 START TRANSACTION; -- 1. 张三账户扣款10000元(远超余额,模拟异常) UPDATE account SET balance = balance - 10000 WHERE username='张三'; -- 业务校验:扣款后余额为负,触发异常 -- 此时终止后续操作,执行回滚 ROLLBACK; 步骤4:项目代码标准伪代码(通用规范) // 所有编程语言(Java/Python/PHP)事务通用逻辑 try { // 建立数据库连接,开启事务(关闭自动提交) connection.setAutoCommit(false); // 执行第一步:扣款SQL executeUpdate(扣款语句); // 执行第二步:加款SQL executeUpdate(加款语句); // 业务校验通过,提交事务 connection.commit(); } catch (Exception e) { // 捕获异常,立即回滚事务 connection.rollback(); // 打印异常日志,抛出错误提示 log.error("事务执行失败,已回滚", e); } finally { // 关闭数据库连接 closeConnection(connection); } 7)完整实操验证脚本 -- 重置测试数据 TRUNCATE TABLE account; INSERT INTO account(username,balance) VALUES('张三',2000.00),('李四',1500.00); SELECT '初始数据' AS stage,username,balance FROM account; -- 提交测试 START TRANSACTION; UPDATE account SET balance=1500 WHERE username='张三'; UPDATE account SET balance=2000 WHERE username='李四'; COMMIT; SELECT '提交后数据' AS stage,username,balance FROM account; -- 回滚测试 START TRANSACTION; UPDATE account SET balance=0 WHERE username='张三'; ROLLBACK; SELECT '回滚后数据' AS stage,username,balance FROM account; 本文由mdnice多平台发布