在mysql的实际开发中,当我们需要执行多步关联的数据库操作时,比如转账场景需要先扣减转出方余额,再增加转入方余额,这两步操作必须同时成功或同时失败,否则会出现数据不一致的问题。此时可以在存储过程中结合事务来实现这类需求,保证操作的原子性。

mysql存储过程中事务相关的基础语句
在存储过程中控制事务,首先需要了解mysql中事务操作的核心语句,这些语句可以直接在存储过程的逻辑中调用:
- START TRANSACTION 或者 BEGIN:开启一个新的事务,后续的所有数据操作都会处于这个事务的管控范围内。
- COMMIT:提交事务,将事务内所有的操作永久保存到数据库中,一旦提交就无法回滚。
- ROLLBACK:回滚事务,撤销事务内所有的未提交操作,数据库会恢复到事务开启前的状态。
- SAVEPOINT 保存点名称:在事务中设置一个保存点,后续可以回滚到这个保存点,而不是回滚整个事务。
- ROLLBACK TO 保存点名称:回滚到指定的保存点,保存点之后的操作会被撤销。
存储过程中结合异常处理控制事务
实际开发中,事务内的操作很可能出现错误,比如更新时字段值不符合约束、SQL语句语法错误等,此时需要捕获异常并自动回滚事务,避免事务长时间占用资源。mysql存储过程支持通过DECLARE ... HANDLER语句定义异常处理器,配合事务使用。
异常处理器的基本语法
定义异常处理器的语法如下:
-- 声明异常处理器,当SQL执行出现错误时触发
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
-- 异常触发时执行的逻辑,比如回滚事务
ROLLBACK;
-- 可以设置返回参数标识操作失败
SET @result = 0;
END;
这里的CONTINUE HANDLER表示触发异常后执行处理器内的逻辑,然后继续执行后续代码;如果使用EXIT HANDLER,则执行完处理器逻辑后直接退出存储过程。
完整示例:转账场景的存储过程事务实现
下面以转账场景为例,实现一个完整的存储过程,包含事务开启、操作执行、异常捕获和事务提交/回滚逻辑。假设我们有用户余额表user_balance,表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| user_id | INT | 用户ID,主键 |
| balance | DECIMAL(10,2) | 用户余额 |
存储过程需要实现从转出用户扣减指定金额,转入用户增加指定金额,两步都成功则提交事务,任意一步失败则回滚事务,同时通过输出参数返回操作结果。
DELIMITER //
CREATE PROCEDURE transfer_money(
IN from_user INT, -- 转出用户ID
IN to_user INT, -- 转入用户ID
IN amount DECIMAL(10,2), -- 转账金额
OUT result INT -- 操作结果:1成功,0失败
)
BEGIN
-- 声明异常处理器,出现SQL异常时回滚事务并设置结果为失败
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SET result = 0;
END;
-- 初始化结果为失败
SET result = 0;
-- 开启事务
START TRANSACTION;
-- 第一步:扣减转出用户余额,校验余额是否充足
UPDATE user_balance
SET balance = balance - amount
WHERE user_id = from_user AND balance >= amount;
-- 如果扣减影响的行数为0,说明转出用户不存在或者余额不足,主动回滚
IF ROW_COUNT() = 0 THEN
ROLLBACK;
SET result = 0;
ELSE
-- 第二步:增加转入用户余额
UPDATE user_balance
SET balance = balance + amount
WHERE user_id = to_user;
-- 如果转入用户不存在,影响的行数为0,回滚事务
IF ROW_COUNT() = 0 THEN
ROLLBACK;
SET result = 0;
ELSE
-- 两步都成功,提交事务
COMMIT;
SET result = 1;
END IF;
END IF;
END //
DELIMITER ;
调用这个存储过程的示例如下:
-- 调用存储过程,从用户1转100元到用户2,result接收返回结果 CALL transfer_money(1, 2, 100.00, @result); -- 查看操作结果 SELECT @result;
存储过程事务使用的注意事项
- 存储过程中如果使用了
START TRANSACTION开启事务,一定要确保有对应的COMMIT或者ROLLBACK,避免事务长时间未结束导致锁表、资源占用问题。 - mysql的存储过程中,事务是嵌套的话,内层事务的提交不会生效,只有最外层的事务提交才会真正提交所有操作,回滚则会回滚所有未提交的内层事务。
- 如果存储过程中包含DDL语句(比如
CREATE TABLE、ALTER TABLE),这些语句会隐式提交当前事务,因此如果需要事务管控DDL操作,需要特别注意DDL的位置,避免提前提交事务。 - 异常处理器的定义需要放在变量声明之后,游标声明之前,否则会报语法错误。
- 可以通过
ROW_COUNT()函数获取上一条UPDATE、INSERT、DELETE语句影响的行数,用来判断操作是否生效,辅助事务的提交或回滚判断。
需要注意的是,mysql的事务隔离级别会影响事务内数据的可见性,存储过程中的事务同样遵循当前会话设置的事务隔离级别,开发时需要根据业务需求选择合适的隔离级别。