MySQL(8.0.24 Community Server-GPL)不会回滚存储过程中的事务
我使用的是MySQL(8.0.24社区服务器-GPL)。当存储过程中出现异常时,MySQL不会回滚。下面是一个演示脚本: 首先运行以下SQL:MySQL(8.0.24 Community Server-GPL)不会回滚存储过程中的事务,mysql,Mysql,我使用的是MySQL(8.0.24社区服务器-GPL)。当存储过程中出现异常时,MySQL不会回滚。下面是一个演示脚本: 首先运行以下SQL: CREATE TABLE already_exists ( ROW_ID INT UNSIGNED AUTO_INCREMENT, PRIMARY KEY (row_id)) ENGINE=INNODB CHARACTER SET utf8mb4; Query OK, 0 r
CREATE TABLE already_exists (
ROW_ID INT UNSIGNED AUTO_INCREMENT,
PRIMARY KEY (row_id))
ENGINE=INNODB CHARACTER SET utf8mb4;
Query OK, 0 rows affected (0.01 sec)
现在创建此存储过程:
delimiter $$
create procedure test_proc()
begin
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
resignal;
END;
start transaction;
set @sql = concat('create table tbl1 (
ROW_ID INT UNSIGNED AUTO_INCREMENT,
PRIMARY KEY (row_id))
ENGINE=INNODB CHARACTER SET utf8mb4;'
);
prepare stmt from @sql;
execute stmt;
set @sql = concat('CREATE TABLE already_exists (
ROW_ID INT UNSIGNED AUTO_INCREMENT,
PRIMARY KEY (row_id))
ENGINE=INNODB CHARACTER SET utf8mb4;
');
prepare stmt from @sql;
execute stmt;
commit;
end $$
delimiter ;
call test_proc();
现在调用存储过程:
delimiter $$
create procedure test_proc()
begin
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
resignal;
END;
start transaction;
set @sql = concat('create table tbl1 (
ROW_ID INT UNSIGNED AUTO_INCREMENT,
PRIMARY KEY (row_id))
ENGINE=INNODB CHARACTER SET utf8mb4;'
);
prepare stmt from @sql;
execute stmt;
set @sql = concat('CREATE TABLE already_exists (
ROW_ID INT UNSIGNED AUTO_INCREMENT,
PRIMARY KEY (row_id))
ENGINE=INNODB CHARACTER SET utf8mb4;
');
prepare stmt from @sql;
execute stmt;
commit;
end $$
delimiter ;
call test_proc();
期望值:
由于表已经存在,因此不应创建表tbl1
观察到:
我得到这个错误:
mysql> call test_proc();
ERROR 1050 (42S01): Table 'already_exists' already exists
但是MySQL也在系统中创建tbl1
。这是一个bug还是有人知道我该怎么做才能解决这个问题?DDL语句会导致错误。过程中的第一条CREATETABLE语句提交事务。您不能在MySQL中回滚DDL语句
您可以使用CREATETABLE IF NOT EXISTS语法,这样,如果其中一个表已经存在,就不会导致错误
或者如果要跳过创建两个表(如果其中一个已经存在),则应首先检查它们是否存在:
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME IN ('tbl1', 'already_exists');
我会这样写:
DELIMITER !!
CREATE PROCEDURE test_proc()
BEGIN
DECLARE tbl1 VARCHAR(64);
DECLARE already_exists VARCHAR(64);
SELECT TABLE_NAME INTO tbl1 FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='tbl1';
IF tbl1 IS NULL THEN
CREATE TABLE tbl1 (
ROW_ID INT UNSIGNED AUTO_INCREMENT,
PRIMARY KEY (row_id)
) ENGINE=INNODB CHARACTER SET utf8mb4;
END IF;
SELECT TABLE_NAME INTO already_exists FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='already_exists';
IF already_exists IS NULL THEN
CREATE TABLE already_exists (
ROW_ID INT UNSIGNED AUTO_INCREMENT,
PRIMARY KEY (row_id)
) ENGINE=INNODB CHARACTER SET utf8mb4;
END IF;
END !!
DELIMITER ;
您不需要使用PREPARE
/EXECUTE
来运行创建表,除非您需要使某些列或其他语法成为动态的。但对于您的示例,它可以直接执行,如上图所示。请在Windows10本地计算机上使用8.0.15版本,因为这是Microsoft在Azure服务上为提供MySQL数据库服务的公司使用的最新稳定版本,就我个人而言,在2021年,我受到了这个bug的影响,已经花了3天时间…3天!!!!解决这个问题。
祝你好运
[查看实际的MySQL错误][1]
[1]: https://bugs.mysql.com/bug.php?id=99940你读过吗?事务通过执行DDL语句隐式提交。谢谢。我现在看到了这一点:InnoDB中的CREATETABLE语句作为单个事务处理。这意味着用户的回滚不会撤消用户在该事务期间所做的CREATE TABLE语句。也许你想把它作为一个答案,我会接受的。在回答中还要提到我应该做些什么来获得预期的行为。你能扩展你的回答并包括需要编写哪些代码来中止(如果任何表已经存在)吗?我已经编写了一个示例过程。如果你想要不同的逻辑,我会让你来实现。谢谢。顺便说一句,我有你的书!顺便说一句,我们不能在不存在的情况下创建表,而不是在答案中进行所有检查吗?这是我上面的第一个建议。