ERROR 1305 (42000): SAVEPOINT ... does not exist - mysql

I have this SQL in my MYSQL DB (sproc with empty body so I guess no implicit commits ?).
DROP PROCEDURE IF EXISTS doOrder;
DELIMITER $$
CREATE PROCEDURE doOrder(IN orderUUID VARCHAR(40))
BEGIN
SAVEPOINT sp_doOrder;
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK TO sp_doOrder;
-- doing my updates and selects here...
END;
RELEASE SAVEPOINT sp_doOrder;
END $$
DELIMITER ;
When I
call doOrder('some-unique-id');
I get: ERROR 1305 (42000): SAVEPOINT sp_doOrder does not exist.
I might overlook something... Any idea?

Since this is the top answer on Google when searching for "savepoint does not exist", I'll add my solution here as well.
I had a TRUNCATE statement within the code executed in my transaction, which caused an implicit commit and thus ended the transaction. Creating a savepoint outside of a transaction does not cause an error, it will just not be executed. This means the first time you'll notice something is wrong is when you try to release your savepoint / rollback it back.
This is the full list of statements that cause an implicit commit: https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html

You have to use START TRANSACTION instead of BEGIN to start a transaction in a stored procedure.
Also, you may need to move the SAVEPOINT statement to be after the DECLARE (depending upon where you put the START TRANSACTION)
Note
Within all stored programs (stored procedures and functions, triggers,
and events), the parser treats BEGIN [WORK] as the beginning of a
BEGIN ... END block. Begin a transaction in this context with START
TRANSACTION instead.
Cf: http://dev.mysql.com/doc/refman/5.6/en/commit.html

This worked at the end. Thanks udog.
DROP PROCEDURE IF EXISTS doOrder;
DELIMITER $$
CREATE PROCEDURE doOrder(IN orderUUID VARCHAR(40))
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK TO sp_order;
START TRANSACTION;
SAVEPOINT sp_order;
-- doing my updates and selects here...
COMMIT;
END $$
DELIMITER ;

Related

Why is this query failing on an empty line?

I'm getting the error 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 '' at line 9 but line 9 has nothing on it and it shows that the error is an empty string in the message.
START TRANSACTION;DROP PROCEDURE IF EXISTS `ReporteAcumulado`;
CREATE DEFINER = CURRENT_USER PROCEDURE `ReporteAcumulado`(in Tipo int,in dtInicio date,in dtFin date,in dtmensual date)
SQL SECURITY INVOKER
BEGIN
if Tipo=0 then
END if;
if Tipo=1 then
end if;
END
COMMIT;
The problem is that MySQL is interpreting the semicolons between the BEGIN and END statements as terminating the CREATE PROCEDURE statement, rather than simply being part of the code of the procedure.
You should change the delimiter from ; to something else (// is commonly used when defining MySQL procs). If you don't already have it, add DELIMITER // before the CREATE PROCEDURE statement, and put DELIMITER ; after the END statement for the procedure. I also don't believe that you need to start and commit a transaction when defining a procedure - I've never found it necessary, but perhaps you're doing something different. You also need // after the END. In addition, I've never seen an empty block between IF...THEN...END IF so I'm not sure how that might affect things.
So your code should look something like
DROP PROCEDURE IF EXISTS `ReporteAcumulado`;
DELIMITER //
CREATE DEFINER = CURRENT_USER PROCEDURE `ReporteAcumulado`(in Tipo int,in dtInicio date,in dtFin date,in dtmensual date)
SQL SECURITY INVOKER
BEGIN
if Tipo=0 then
END if;
if Tipo=1 then
end if;
END//
DELIMITER ;
The MySQL documentation for CREATE PROCEDURE explains it this way:
The example uses the MySQL client delimiter command to change the statement delimiter from ; to // while the procedure is being defined. This enables the ; delimiter used in the procedure body to be passed through to the server rather than being interpreted by MySQL itself.

MySQL stored procedure the reason of rollback

MySQL stored procedure will throw the error out if there is no rollback command for SQLEXCEPTION, but it has changed some data before the exception.
I add rollback command for SQL exceptions:
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
But I can not know the reason of the rollback now.
I know we can define every SQL exception and it's handler, but it's too complex and I just want to know why the rollback occurred.
Is there a simple method to get the reason of rollback in MySQL stored procedure?
Thanks #kordirko. I have get one solution with RESIGNAL. But it is only supported until MySQL 5.5.
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET #flag = 1;
IF #flag = 1 THEN RESIGNAL;END IF;
ROLLBACK;
END;

How to rollback and stop execution from an inner stored procedure

Let's say I have two stored procedures, Outer and Inner:
CREATE PROCEDURE dbo.Outer
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRAN
EXEC Inner
-- Perform additional processing (which should not occur if there is
-- a ROLLBACK in Inner)
...
COMMIT
END;
GO
The Outer stored procedure turns on XACT_ABORT and starts an explicit transaction. It then calls the Inner stored procedure inside the transaction.
CREATE PROCEDURE dbo.Inner
AS
BEGIN
DECLARE #Id INT=(SELECT Id FROM SomeTable WHERE ...);
IF (#Id IS NOT NULL)
ROLLBACK;
INSERT INTO SomeTable(...)
VALUES (...);
END;
GO
The Inner stored procedure performs a check to see if something is present and if it is wants to rollback the entire transaction started in the Outer stored procedure and abort all further processing in both Inner and Outer.
The thing that happens instead of what I expect as outlined above, is that I get the error message:
In Inner:
Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. Previous count = 1, current count = 0.
In Outer:
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
Clearly, the ROLLBACK even with XACT_ABORT turned on does not stop the flow of execution. Putting a RETURN statement after the ROLLBACK gets us out of Inner, but execution in Outer continues. What do I need to do in order to cause the ROLLBACK to stop all further processing and effectively cause an exit out of Outer?
Thanks for any help.
Issuing the rollback does not abort the batch (Regardless of the XACT_ABORT setting). The batch will automatically abort in your case if an error is thrown with the high enough severity - either system or custom generated via RAISERROR.
You have to implement Exception Handling
Begin Try
Set NOCOUNT ON
SET XACT_ABORT ON
Begin Tran
--Your Code
Commit Tran
End Try
Begin Catch
Rollback Tran
End Catch

MySQL transaction conundrum

I need to perform several inserts in a single atomic transaction. For example:
start transaction;
insert ...
insert ...
commit;
However when MySQL encounters an error it aborts only the particular statement that caused the error. For example, if there is an error in the second insert statement the commit will still take place and the first insert statement will be recorded. Thus, when errors occur a MySQL transaction is not really a transaction. To overcome this problem I have used an error exit handler where I rollback the transaction. Now the transaction is silently aborted but I don't know what was the problem.
So here is the conundrum for you:
How can I both make MySQL abort a transaction when it encounters an error, and pass the error code on to the caller?
How can I both make MySQL abort a transaction when it encounters an error, and pass the error code on to the caller?
MySQL does pass error code to the caller and based on this error code the caller is free to decide whether it wants to commit work done up to the moment (ignoring the error with this particular INSERT statement) or to rollback the transaction.
This is unlike PostgreSQL which always aborts the transaction on error and this behavior is a source of many problems.
Update:
It's a bad practice to use an unconditional ROLLBACK inside the stored procedures.
Stored procedures are stackable and transactions are not, so a ROLLBACK within a nested stored procedure will roll back to the very beginning of the transaction, not to the state of the stored procedure execution.
If you want to use transactions to restore the database state on errors, use SAVEPOINT constructs and DECLARE HANDLER to rollback to the savepoints:
CREATE PROCEDURE prc_work()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK TO sp_prc_work;
SAVEPOINT sp_prc_work;
INSERT …;
INSERT …;
…
END;
Failure in either insert will roll back all changes made by the procedure and exit it.
Using Mr. Quassnoi's example, here's my best approach to catching specific errors:
The idea is to use a tvariable to catch a simple error message, then you can catch sql states you think may happen to save custom messages to your variable:
DELIMITER $$
DROP PROCEDURE IF EXISTS prc_work $$
CREATE PROCEDURE prc_work ()
BEGIN
DECLARE EXIT HANDLER FOR SQLSTATE '23000'
BEGIN
SET #prc_work_error = 'Repeated key';
ROLLBACK TO sp_prc_work;
END;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET #prc_work_error = 'Unknown error';
ROLLBACK TO sp_prc_work;
END;
START TRANSACTION;
SAVEPOINT sp_prc_work;
INSERT into test (id, name) VALUES (1, 'SomeText');
COMMIT;
END $$
DELIMITER ;
Then you just do your usual call, and do a select statement for the variable like:
call prc_work(); select #prc_work_error;
This will return either NULL if no error, or the message error in case of an error. If you need persistent error message you can optionally create a table to store it.
It's tedious and not very flexible because requires a DECLARE EXIT HANDLER segment for each status code you want to catch, it won't also show detailed error messages but hey, it works.

MySql stored procedures, transactions and rollbacks

I can't find an optimal way to use transactions in a MySql Stored Procedure. I want to ROLLBACK if anything fails:
BEGIN
SET autocommit=0;
START TRANSACTION;
DELETE FROM customers;
INSERT INTO customers VALUES(100);
INSERT INTO customers VALUES('wrong type');
COMMIT;
END
1) Is autocommit=0 required?
2) If the second INSERT breaks (and it does of course) the first INSERT is not rolled back. The procedure simply continues down to the COMMIT. How can I prevent this?
3) I've found I can DECLARE HANDLER, should I use this instruction or is there a simpler way to say that if any command fails, the stored procedure should ROLLBACK and fail too?
DECLARE HANDLER works fine, but since I have MySql version 5.1 I can't use RESIGNAL. So if an error occurs, the caller won't be notified:
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
-- RESIGNAL; not in my version :(
END;
START TRANSACTION;
Answer to 1: You don't need to set autocommit=0
With START TRANSACTION, autocommit remains disabled until you end the
transaction with COMMIT or ROLLBACK. The autocommit mode then reverts
to its previous state.
http://dev.mysql.com/doc/refman/5.6/en/commit.html
Different Approach to Answer 2: You could use a boolean variable to know if you should COMMIT or ROLLBACK. For example:
BEGIN
DECLARE `should_rollback` BOOL DEFAULT FALSE;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET `should_rollback` = TRUE;
START TRANSACTION;
DELETE FROM customers;
INSERT INTO customers VALUES(100);
INSERT INTO customers VALUES('wrong type');
IF `should_rollback` THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
END
Or, you could use your very usefull 3)