MySQL存储过程与函数事务特性介绍

总结摘要
MySQL Transaction For Stored Procedure And Stored Function

结论

存储过程(Stored Procedure)与存储函数(Stored Function)在事务控制方面有较大差异。

对于存储过程,如果自动提交是开启的,并且你没有在存储过程中显式地控制事务,那么每条独立的 SQL 语句执行完毕后都会立即提交。

存储过程允许显式的事务操作,包括显式地开始、提交或回滚事务。

存储函数内的 SQL 操作被视为原子的,这意味着存储函数内的所有 SQL 语句要么全部成功执行,要么在发生错误时全部回滚。

存储函数会被视为一个单一的执行单元,即使自动提交是开启的,函数内部的所有操作也会被视为一个事务。

不能在存储函数内部使用显式的 COMMIT 或 ROLLBACK 语句,因为这些操作会破坏函数的原子性。

[HY000][1422] Explicit or implicit commit is not allowed in stored function or trigger.

背景知识

特性存储函数存储过程
返回值必须返回单一值,且类型明确。无返回值,但可通过 <font style="color:rgb(64, 64, 64);">OUT</font>参数返回多个值,或通过结果集返回数据。
事务控制不能使用显式事务语句,依赖 <font style="color:rgb(64, 64, 64);">autocommit</font> 或外层事务。支持显式事务控制,可自主管理提交或回滚。
调用方式可在 SQL 语句中直接调用(如 <font style="color:rgb(64, 64, 64);">SELECT func()</font>)。必须通过 <font style="color:rgb(64, 64, 64);">CALL</font>语句

函数中不允许使用 COMMIT、ROLLBACK 或 START TRANSACTION 等显式事务控制语句。如果尝试使用,会触发语法错误。

存储过程支持显式事务控制,具有更灵活的事务管理能力。

If a SELECT statement within a transaction calls a stored function, and a statement within the stored function fails, that statement rolls back. If ROLLBACK is executed for the transaction subsequently, the entire transaction rolls back.

如果事务中的 SELECT 语句调用了一个存储函数,并且存储函数中的语句失败,则该语句会回滚。如果随后对事务执行 ROLLBACK ,则整个事务会回滚。

https://dev.mysql.com/doc/refman/8.0/en/commit.html

验证

数据库状态

1
2
3
4
5
6
7
8
-- 8.0.41
SELECT version();

-- 1,自动提交模式
SELECT @@autocommit;

-- 查询当前数据库连接的事务 ID
SELECT trx_id FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = CONNECTION_ID();

初始化数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
DROP TABLE IF EXISTS `test_code`;

-- 建表并添加主键约束(确保唯一性冲突可触发错误)
CREATE TABLE `test_code` (
  `id` int PRIMARY KEY,  -- 添加主键
  `code` int DEFAULT NULL,
  `note` varchar(100) DEFAULT NULL
);

TRUNCATE TABLE `test_code`;

隐式提交

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-- SET GLOBAL log_bin_trust_function_creators = 1;

DROP PROCEDURE IF EXISTS sp_autocommit;
DELIMITER ;;
CREATE PROCEDURE sp_autocommit()
BEGIN
  INSERT INTO test_code(id, code, note) VALUES (1, 100, '步骤1');
  INSERT INTO test_code(id, code, note) VALUES (1, 100, '重复步骤1'); -- 主键冲突
END ;;
DELIMITER ;

TRUNCATE TABLE `test_code`;

-- [1062] [23000]: Duplicate entry '1' for key 'test_code.PRIMARY'
-- 事务自动回滚。
-- 第一条插入已提交,第二条失败。
call sp_autocommit();

-- 1	100	步骤1
SELECT * FROM test_code;


DROP FUNCTION IF EXISTS func_autocommit;
DELIMITER ;;
CREATE FUNCTION func_autocommit() RETURNS INT
BEGIN
  INSERT INTO test_code(id, code, note) VALUES (2, 200, '步骤2'); 
  INSERT INTO test_code(id, code, note) VALUES (2, 200, '重复步骤2'); -- 主键冲突
  RETURN 1;
END ;;
DELIMITER ;

TRUNCATE TABLE `test_code`;

-- 执行函数
-- [1062] [23000]: Duplicate entry '2' for key 'test_code.PRIMARY'
-- 事务自动回滚。
-- 无任何数据写入成功。
SELECT func_autocommit(); 

-- 无数据
SELECT * FROM test_code;

主动事务——存过

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
TRUNCATE TABLE `test_code`;

BEGIN;

-- 初始化 1 条数据
INSERT INTO test_code(id, code, note) VALUES (0, 0, '开启事务');

-- 存在 1 条初始化数据
SELECT * FROM test_code;

-- 存在事务 3511
SELECT trx_id FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = CONNECTION_ID();

-- [1062] [23000]: Duplicate entry '1' for key 'test_code.PRIMARY'
call sp_autocommit();

-- 存过中的第一条插入已提交,第二条失败。
-- 仅 2 条数据,初始化数据与存过中的第一条数据。
SELECT * FROM test_code;

-- 存在事务 3511,事务 id 保存不变。
SELECT trx_id FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = CONNECTION_ID();

-- 补充,已验证此处同样支持 COMMIT 命令。提交事务将持久化当前的 2 条数据。
ROLLBACK;

-- 事务已回滚
SELECT trx_id FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = CONNECTION_ID();

-- 无任何数据,成功回滚。
SELECT * FROM test_code;

主动事务——函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
TRUNCATE TABLE `test_code`;

BEGIN;

-- 初始化 1 条数据
INSERT INTO test_code(id, code, note) VALUES (0, 0, '开启事务');

-- 存在 1 条初始化数据
SELECT * FROM test_code;

-- 存在事务 3554
SELECT trx_id FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = CONNECTION_ID();

-- [1062] [23000]: Duplicate entry '2' for key 'test_code.PRIMARY'
SELECT func_autocommit(); 

-- 函数中的两条数据全部插入失败。
-- 仅存在 1 条初始化数据。
SELECT * FROM test_code;

-- 存在事务 3554,事务 id 保存不变。
SELECT trx_id FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = CONNECTION_ID();

-- 补充,已验证此处同样支持 ROLLBACK 命令。回滚当前的 1 条数据。
COMMIT;

-- 事务已提交
SELECT trx_id FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = CONNECTION_ID();

-- 仅存在 1 条初始化数据。
SELECT * FROM test_code;

END