Mysql 如何在存储过程中使用事务?

Mysql 如何在存储过程中使用事务?,mysql,sql,stored-procedures,Mysql,Sql,Stored Procedures,我有一个需要转换为参数化存储过程的SQL脚本。我过去只编写过简单的函数,从未编写过带参数的复杂事务查询 非常感谢您的帮助-下面是简化的查询。这个脚本实际上可以是任何包含事务和一些用户输入的东西 -- transaction ensures i can clean up a mess, if one happens begin; -- parameters for the script; currently set manually before execution set @parent_id

我有一个需要转换为参数化存储过程的SQL脚本。我过去只编写过简单的函数,从未编写过带参数的复杂事务查询

非常感谢您的帮助-下面是简化的查询。这个脚本实际上可以是任何包含事务和一些用户输入的东西

-- transaction ensures i can clean up a mess, if one happens
begin;

-- parameters for the script; currently set manually before execution
set @parent_id := 123;
set @identifier := 'someid';

-- insert some row with user-specified values
insert into users (field1, field2) values (@parent_id, @identifier);

-- get the new id
set @user_id := last_insert_id();

-- do another insert
insert into usersmeta (user_id, field1, field2) values (@user_id, 1, 2);

-- if no errors happened yet, commit transaction
commit;

-- "return value"; some select query (could be 1 or many rows)
select users.id userid, usersmeta metaid
from users
join usersmeta on usersmeta.user_id = users.id;
我一开始是这样,但后来我几乎被卡住了。我特别关心的是确保错误在发生时以某种方式对调用代码可见

delimiter ;; 
CREATE PROCEDURE mytask(IN parent_id INT(11), IN identifier VARCHAR(200)) 
BEGIN 
        SET @query = ??? 
        PREPARE q FROM @query; 
        EXECUTE q; 
        DEALLOCATE PREPARE q; 
END;; 
delimiter ; 

这需要大量的研究、尝试和错误,但我认为我找到了一个相当好的解决方案

DELIMITER //

CREATE PROCEDURE my_procedure (IN parent_id int, IN identifier varchar(255), OUT out_user_id int)
BEGIN

  -- local variable, set later after user is created
  DECLARE user_id int;

  -- rollback transaction and bubble up errors if something bad happens
  DECLARE exit handler FOR SQLEXCEPTION, SQLWARNING
  BEGIN
    ROLLBACK;
    RESIGNAL;
  END;

  START TRANSACTION;

  -- insert some row with user-specified values
  INSERT INTO users (field1, field2) values (parent_id, identifier);

  -- get the new id
  SET user_id = last_insert_id();

  -- do another insert
  INSERT INTO usersmeta (user_id, field1, field2) values (user_id, 1, 2);

  -- if no errors happened yet, commit transaction
  COMMIT;

  -- return
  SELECT user_id INTO out_user_id;

END //

DELIMITER ;
我可以这样使用它

-- run the procedure
CALL my_procedure(123, 'some_id', @user_id);

-- get the "return" value
SELECT @user_id as user_id;

这绝对是我写过的最复杂的存储过程。如果有人看到需要改进的地方,我很乐意学习如何改进。

经过大量的研究、尝试和错误,但我认为我找到了一个非常好的解决方案

DELIMITER //

CREATE PROCEDURE my_procedure (IN parent_id int, IN identifier varchar(255), OUT out_user_id int)
BEGIN

  -- local variable, set later after user is created
  DECLARE user_id int;

  -- rollback transaction and bubble up errors if something bad happens
  DECLARE exit handler FOR SQLEXCEPTION, SQLWARNING
  BEGIN
    ROLLBACK;
    RESIGNAL;
  END;

  START TRANSACTION;

  -- insert some row with user-specified values
  INSERT INTO users (field1, field2) values (parent_id, identifier);

  -- get the new id
  SET user_id = last_insert_id();

  -- do another insert
  INSERT INTO usersmeta (user_id, field1, field2) values (user_id, 1, 2);

  -- if no errors happened yet, commit transaction
  COMMIT;

  -- return
  SELECT user_id INTO out_user_id;

END //

DELIMITER ;
我可以这样使用它

-- run the procedure
CALL my_procedure(123, 'some_id', @user_id);

-- get the "return" value
SELECT @user_id as user_id;

这绝对是我写过的最复杂的存储过程。如果有人看到需要改进的地方,我很乐意学习如何改进。

如果出现错误,将抛出异常。您可以在过程中捕获它并设置输出参数,重新调用它和/或让调用方捕获异常。如果希望在过程中使用事务,则应捕获错误以便能够回滚。但要小心交易,因为它们不会嵌套;例如,如果调用方也使用了事务,则该外部事务也将提交/回滚。参见eg@Solarflare,谢谢您的评论。我能够使用您链接的资源来获得下面发布的我的解决方案。我真的很感激。如果发生错误,就会抛出异常。您可以在过程中捕获它并设置输出参数,重新调用它和/或让调用方捕获异常。如果希望在过程中使用事务,则应捕获错误以便能够回滚。但要小心交易,因为它们不会嵌套;例如,如果调用方也使用了事务,则该外部事务也将提交/回滚。参见eg@Solarflare,谢谢您的评论。我能够使用您链接的资源来获得下面发布的我的解决方案。我真的很感激。这里是龙:插入到usersmeta user_id,field1,field2值user_id,1,2;值中的用户id不明确,因为您有一个列名和一个同名的程序变量。不要这样做。解析器可以假设您指的是一个,而不是另一个,或者您可以循环使用列的未初始化默认值。始终使程序变量与列名不同。我的约定是变量的前导下划线,例如_user_id。有关问题行为,请参见此示例:@NathanDrake做得很好,但有一点警告没有双关语的意思:如果您退出警告,它将保持不变,即警告。警告通常不会停止调用方中的执行,除非您也在那里捕获它并在函数回滚时处理它。如果你不这样做,你也可以发一个错误信号,所以通过信号把警告变成错误。。。不要在警告时回滚,也不要添加额外的标记,您可以检查该过程是否失败,例如,如果不可能,请将输出参数设置为null。这里有龙:插入usersmeta user_id,field1,field2值user_id,1,2;值中的用户id不明确,因为您有一个列名和一个同名的程序变量。不要这样做。解析器可以假设您指的是一个,而不是另一个,或者您可以循环使用列的未初始化默认值。始终使程序变量与列名不同。我的约定是变量的前导下划线,例如_user_id。有关问题行为,请参见此示例:@NathanDrake做得很好,但有一点警告没有双关语的意思:如果您退出警告,它将保持不变,即警告。警告通常不会停止调用方中的执行,除非您也在那里捕获它并在函数回滚时处理它。如果你不这样做,你也可以发一个错误信号,所以通过信号把警告变成错误。。。不要在出现警告时回滚,也不要添加额外的标记来检查过程是否失败,例如,如果不可能,则将输出参数设置为null,而不是重新签名。