I'm trying to modify my MySQL stored procedure and make it transactional. The existing stored procedure works fine with no problem but as soon as I make it transactional it does not even allow me to save my changes. I checked MySQL documentation and searched online but I cannot find any problem with my code. It seems to be pretty straight forward but can't figure it out.
BEGIN
DECLARE poid INT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION SQLWARNING
BEGIN
ROLLBACK;
END
START TRANSACTION;
-- ADD option 5
INSERT INTO product_option(product_id,option_id,required) VALUES(insertedProductID,5,0);
SET poid = (SELECT LAST_INSERT_ID());
INSERT INTO product_option_value(product_option_id,product_id,option_id,option_value_id,quantity,subtract,price,price_prefix,points,points_prefix,weight,weight_prefix) VALUES(poid,insertedProductID,5,50,0,0,4.99,'+',0,'+',0,'+');
-- ADD option 12
INSERT INTO product_option(product_id,option_id,required) VALUES(insertedProductID,12,1);
-- ADD option 13
INSERT INTO product_option(product_id,option_id,required) VALUES(insertedProductID,13,0);
COMMIT;
END
any idea ?
Two syntax errors:
You need commas in between the conditions for your exit handler. Notice the syntax documentation shows commas.
You need to terminate the END of the exit handler with a semicolon. The DECLARE statement itself (including its BEGIN...END block) is a statement like any other, and need to have a terminator.
So you need this:
DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING
BEGIN
ROLLBACK;
END;
Try like this ie, include your Declare statement inside the START TRANSACTION;. Earlier your ROLLBACK was not a part of TRANSACTION as you wrote it above the START TRANSACTION:-
BEGIN
DECLARE poid INT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING
START TRANSACTION;
BEGIN
ROLLBACK;
END
-- ADD option 5
INSERT INTO product_option(product_id,option_id,required) VALUES(insertedProductID,5,0);
SET poid = (SELECT LAST_INSERT_ID());
INSERT INTO product_option_value(product_option_id,product_id,option_id,option_value_id,quantity,subtract,price,price_prefix,points,points_prefix,weight,weight_prefix) VALUES(poid,insertedProductID,5,50,0,0,4.99,'+',0,'+',0,'+');
-- ADD option 12
INSERT INTO product_option(product_id,option_id,required) VALUES(insertedProductID,12,1);
-- ADD option 13
INSERT INTO product_option(product_id,option_id,required) VALUES(insertedProductID,13,0);
COMMIT;
END
Put your DECLAREs after the first BEGIN and it should work.
If you use BEGIN and END to group multiple statements, you normally also need to declare an alternate DELIMITER at the top and replace the ; after the last END with it.
Related
I am trying to implement a ROLLBACK functionality on my stored procedure that executes 2 UPDATE queries. I want the second update to only run if the first update was successful. Also if the second update fails, there should be a rollback of the first update statement. I did the below but I need to confirm from you guys if what I wrote is okay. Here is my stored procedure
BEGIN
declare exit handler for sqlexception
BEGIN
rollback;
END;
declare exit handler for sqlwarning
BEGIN
rollback;
END;
START transaction;
FIRST UPDATE
SECOND UPDATE
COMMIT;
SELECT FOUND_ROWS() INTO res;
END
I'm sort of new to SQL and stored procedures. I'm trying to set some variables to use later on in a transaction while also setting a rollback variable on an exception, but I'm not able to do this.
I don't know where the error is because when I switch the how_many section after the _rollback, The error changes.
What's wrong with the way I'm declaring variables here?
DELIMITER $$
DROP PROCEDURE IF EXISTS `do_thing` $$
CREATE PROCEDURE `do_thing`()
BEGIN
DECLARE how_many INT;
SELECT COUNT(*) FROM things INTO how_many;
-- Prepare roleback.
DECLARE `_rollback` BOOL DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET `_rollback` = 1;
-- Start transaction.
START TRANSACTION;
-- Do all the things.
IF `_rollback` THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END $$
DELIMITER ;
EDIT:
when I declare variables like this:
DECLARE how_many INT;
-- Prepare roleback.
DECLARE `_rollback` BOOL DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET `_rollback` = 1;
SELECT COUNT(*) FROM tiers INTO how_many;
It seems to be fine. Am I not allowed to declare more variables after certain variables in a procedure are set?
From the Manual Page DECLARE Syntax:
DECLARE is permitted only inside a BEGIN ... END compound statement
and must be at its start, before any other statements.
Declarations must follow a certain order. Cursor declarations must appear before handler declarations. Variable and condition
declarations must appear before cursor or handler declarations.
Note that these are called Local Variables in mysql. These differ from User Variables (prefixed with the # symbol), which can be sprinkled about with your statements and do not require a DECLARE.
The DECLARE statements are only permitted after BEGIN, if you try to add another DECLARE after that it shows error.
Move the line SELECT COUNT(*) FROM things INTO how_many; after all DECLARE lines.
DELIMITER $$
DROP PROCEDURE IF EXISTS `do_thing` $$
CREATE PROCEDURE `do_thing`()
BEGIN
DECLARE how_many INT;
-- Prepare roleback.
DECLARE `_rollback` BOOL DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET `_rollback` = 1;
-- Start transaction.
START TRANSACTION;
SELECT COUNT(*) FROM things INTO how_many;
-- Do all the things.
IF `_rollback` THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END $$
DELIMITER ;
Can someone tell me if it is possible to call another procedure from within a procedure and if any part of either procedure fails, roll everything back?
If this is possible, can someone please show me a tiny example of how this would be implemented?
EDIT: Procedure "b" fails but procedure "a" still inserts a row into table "a". It's my understanding that if any part of the insert fails that everything (both inserts) is rolled back which is not happening here. The questions is why not?
Procedure "a"
BEGIN
DECLARE b INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLWARNING ROLLBACK;
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
INSERT INTO a(a)
VALUES(iA);
CALL b(iB,LAST_INSERT_ID(),#b);
SELECT #b INTO b;
IF b !=1 THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END
Procedure "b"
BEGIN
DECLARE b INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLWARNING ROLLBACK;
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
INSERT INTO b VALUES(iB,id);
SET b=1;
COMMIT;
END;
You will need to handle transactions in both procedures, but the proc that is calling the other, should check for the return value and rollback it's transactions based on that. Here is an example of the inner proc:
How to detect a rollback in MySQL stored procedure?
you would then check for p_return_code and do a rollback of the parent transaction.
EDIT:
What I think is happening is that inner SP COMMIT or ROLLBACK affect outer SP TRANSACTION. This code works for me, if inner SP fail it rolls back both insert statements. First call to ab() works, new user record gets inserted and new game record gets inserted, if we remove record from the games table and run ab() again, because user id already exists it rolls back games table insert:
create procedure ab()
BEGIN
START TRANSACTION;
INSERT INTO games (title) VALUES ('bad game');
CALL ba(#ret);
IF #ret!=0 THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END;
create procedure ba(OUT return_value tinyint unsigned)
BEGIN
DECLARE exit handler for sqlexception
BEGIN
set return_value = 1;
END;
INSERT INTO users (id) VALUES(1);
set return_value = 0;
END;
To test use call ab();
The basic structure of my stored procedure is,
BEGIN
.. Declare statements ..
START TRANSACTION;
.. Query 1 ..
.. Query 2 ..
.. Query 3 ..
COMMIT;
END
MySQL version: 5.1.61-0ubuntu0.11.10.1-log
Currently, if 'query 2' fails, result of 'query 1' is committed.
How can I rollback the transaction if any of the query fails?
Take a look at http://dev.mysql.com/doc/refman/5.0/en/declare-handler.html
Basically you declare error handler which will call rollback
START TRANSACTION;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
EXIT PROCEDURE;
END;
COMMIT;
Just an alternative to the code by rkosegi,
BEGIN
.. Declare statements ..
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
.. set any flags etc eg. SET #flag = 0; ..
ROLLBACK;
END;
START TRANSACTION;
.. Query 1 ..
.. Query 2 ..
.. Query 3 ..
COMMIT;
.. eg. SET #flag = 1; ..
END
[This is just an explanation not addressed in other answers]
At least in recent versions of MySQL, your first query is not committed.
If you query it under the same session you will see the changes, but if you query it from a different session, the changes are not there, they are not committed.
What's going on?
When you open a transaction, and a query inside it fails, the transaction keeps open, it does not commit nor rollback the changes.
So BE CAREFUL, any table/row that was locked with a previous query like SELECT ... FOR SHARE/UPDATE, UPDATE, INSERT or any other locking-query, keeps locked until that session is killed (and executes a rollback), or until a following query commits it explicitly (COMMIT) or implicitly, thus making the partial changes permanent (which might happen hours later, while the transaction was in a waiting state).
That's why the solution involves declaring handlers to immediately ROLLBACK when an error happens.
Extra
Inside the handler you can also re-raise the error using RESIGNAL, otherwise the stored procedure executes "Successfully":
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
-- .. Query 1 ..
-- .. Query 2 ..
-- .. Query 3 ..
COMMIT;
END;
Here's an example of a transaction that will rollback on error and return the error code.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `SP_CREATE_SERVER_USER`(
IN P_server_id VARCHAR(100),
IN P_db_user_pw_creds VARCHAR(32),
IN p_premium_status_name VARCHAR(100),
IN P_premium_status_limit INT,
IN P_user_tag VARCHAR(255),
IN P_first_name VARCHAR(50),
IN P_last_name VARCHAR(50)
)
BEGIN
DECLARE errno INT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET CURRENT DIAGNOSTICS CONDITION 1 errno = MYSQL_ERRNO;
SELECT errno AS MYSQL_ERROR;
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO server_users(server_id, db_user_pw_creds, premium_status_name, premium_status_limit)
VALUES(P_server_id, P_db_user_pw_creds, P_premium_status_name, P_premium_status_limit);
INSERT INTO client_users(user_id, server_id, user_tag, first_name, last_name, lat, lng)
VALUES(P_server_id, P_server_id, P_user_tag, P_first_name, P_last_name, 0, 0);
COMMIT WORK;
END$$
DELIMITER ;
This is assuming that autocommit is set to 0.
Hope this helps.
DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING, NOT FOUND
BEGIN
ROLLBACK;
END;
START TRANSACTION;
UPDATE tbl_order SET TransactionID="abc" WHERE OrderID=1;
UPDATE tbl_order SET TransactionID="xyz" WHERE OrderID=;
UPDATE tbl_order SET TransactionID="zzz" WHERE OrderID=13;
COMMIT;
for some reason order 1 and 13 are filled without rollback and i get syntax error for the exit hadler.
Query: DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING, NOT FOUND BEGIN ROLLBACK
Error Code: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING, NOT FOUND
BEGIN
ROLLBACK' at line 1
can someone help me figure out what i'm doing wrong?
Thanks in advance
EDIT
UPDATE tbl_order SET TransactionID="xyz" WHERE OrderID=;
is intentional
I believe exit handlers can only be used in stored procedures. The documentation doesn't explicitly state this, but alludes to
Conditions may arise during stored program execution that require special handling
http://dev.mysql.com/doc/refman/5.1/en/condition-handling.html
I know this is quite outdated topic, but I encountered the same error when declaring my own procedure with transaction and error handling. The above procedure looks well, something OP didn't copy as it seems. The order is important and it should look like this:
Procedure declaration
Declaration of variables
ERROR HANDLING
Start transaction
Statements
Commit
End
In my case, it looks like this:
DELIMITER //
CREATE PROCEDURE pAddUser(IN _nick VARCHAR(30))
BEGIN
DECLARE doesUserExist BOOLEAN;
DECLARE accountCreationDate DATE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
START TRANSACTION;
SET doesUserExist = (SELECT EXISTS(SELECT * FROM tblUser WHERE nick=_nick));
IF NOT doesUserExist THEN
SET accountCreationDate = CURDATE();
INSERT INTO tblUser (nick, accountCreation) VALUES (_nick, CURDATE());
ELSE
SELECT CONCAT("Nick '", _nick, "' is in use!") AS 'Console output';
END IF;
COMMIT;
END;
//
DELIMITER ;