正如标题所说,MySQL 中的过程和函数是原子的吗?即想要类似的东西
for (..)
<check_if_row_has_flag>
for (..)
<update_row>
原子工作?
有趣的是,除了 2009 年的一个论坛帖子之外,我在 Google 上找不到太多相关内容。
不,存储过程不是原子的。
上面显示的伪代码存在竞争条件。第一个循环检查一行是否有标志,将返回答案,但除非您执行“锁定读取”,否则另一个并发会话可能会在您的过程读取该行后立即更改标志。 这就是
乐观锁的效果。在您发出锁定行的语句之前,行不会被锁定。因此,即使在事务中,您也没有原子锁定。 MySQL支持的原子性是针对事务提交的。事务是原子的,因为事务期间所做的所有更改都会成功,否则所有更改都会回滚。其他会话无法看到您处于部分完成状态的交易。
回复以下评论:
您可以从应用程序调用事务中的过程:
START TRANSACTION;
CALL MyProcedure();
COMMIT;
您甚至可以在过程正文中显式启动并提交一个事务(或连续的多个事务):
CREATE PROCEDURE MyProcedure()
BEGIN
START TRANSACTION;
...UPDATE, INSERT, DELETE, blah blah...
COMMIT;
END
但是过程本身不会隐式启动或提交事务。
例如,您创建test
CREATE TABLE test (
num int
);
然后,插入num为2
的行,如下所示:
INSERT INTO test (num) VALUES (2);
然后,您创建my_proc()
过程,将num
更新为
5
,然后通过SIGNAL语句
导致错误,如下所示:
DELIMITER $$
CREATE PROCEDURE my_proc()
BEGIN
UPDATE test SET num = 5;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'An error occurred';
END$$
DELIMITER ;
或者,您创建 my_proc()
过程,将 num
更新为
5
,然后导致事务中出现错误(START TRANSACTION 和 COMMIT 语句
),如下所示:
DELIMITER $$
CREATE PROCEDURE my_proc()
BEGIN
START TRANSACTION;
UPDATE test SET num = 5;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'An error occurred';
COMMIT;
END$$
DELIMITER ;
然后,调用my_proc()
会出现错误,但num
不会回滚到
2
,如下所示:
mysql> CALL my_proc();
ERROR 1644 (45000): An error occurred
...
mysql> SELECT num FROM test;
+------+
| num |
+------+
| 5 |
+------+
现在,您可以使用 DECLARE ... HANDLER 语句和事务(
my_proc()
和
START TRANSACTION
语句)来使 COMMIT
过程原子化,如下所示:
DELIMITER $$
CREATE PROCEDURE my_proc()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
START TRANSACTION;
UPDATE test SET num = 5;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'An error occurred';
COMMIT;
END$$
DELIMITER ;
然后,调用my_proc()
不会出现错误,因为错误是由DECLARE ... HANDLER
语句处理的,然后
num
会回滚到
2
,如下所示:
mysql> CALL my_proc();
...
mysql> SELECT num FROM test;
+------+
| num |
+------+
| 2 |
+------+