Do DMLs in a stored procedure run in a single transaction? - mysql

My MySQL 5.5 server has set autocommit=1.
My stored procedure has several DMLs but without explicit transaction management.
When I issue call the_procedure() from MySQL CLI (autocommit is still 1), do all the procedure's DMLs run in one transaction?
Or do they run in separate transactions, and causing implicit transaction commit after every DML (due to autocommit)?

This is surprising to me but:
Although MySQL will automatically initiate a transaction on your
behalf when you issue DML statements, you should issue an explicit
START TRANSACTION statement in your program to mark the beginning of
your transaction.
It's possible that your stored program might be run within a server in
which autocommit is set to TRUE, and by issuing an explicit START
TRANSACTION statement you ensure that autocommit does not remain
enabled during your transaction. START TRANSACTION also aids
readability by clearly delineating the scope of your transactional
code.

They run in separate transactions if autocommit=1. Suppose you define
CREATE TABLE test ( id int PRIMARY KEY )//
CREATE PROCEDURE sp_test_trans()
BEGIN
INSERT INTO test (id) VALUES (1);
INSERT INTO test (id) VALUES (2);
ROLLBACK;
END//
If you run this procedure with autocommit=0, the ROLLBACK will undo the insertions. If you run it with autocommit=1, the ROLLBACK will do nothing. Fiddle here.
Another example:
CREATE PROCEDURE sp_test_trans_2()
BEGIN
INSERT INTO test (id) VALUES (1);
INSERT INTO test (id) VALUES (1);
END//
If you run this procedure with autocommit=0, failure of the second insert will cause a ROLLBACK undoing the first insertion. If you run it with autocommit=1, the second insert will fail but the effects of the first insert will not be undone.

Tests done in the following SQL Fiddle, shows that by not explicitly handle transactions are handled separately when the variable autocommit is 1 (TRUE).

Related

What is the perfect strategy to lock tables in MySql?

I have wrote this procedure which creates new A/c voucher. It runs on MySql 5.0. Now, it's time to implement this in our production. But I am not sure that it meets the requirement. Is this Lock Strategy perfect ? Please help.
It calls this way :
CALL SpAcVoucherCreate(1,'2022/03/31','2831',5000,'A001');
My procedure is as follows :
USE `FinanceDB`;
DROP PROCEDURE IF EXISTS `SpAcVoucherCreate`;
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `SpAcVoucherCreate`(
V_VOCHNO INT,
V_VOCHDT CHAR(10),
V_ACCODE CHAR(4),
V_AMOUNT DECIMAL(12,2),
V_USER_ID CHAR(5)
)
BEGIN
DECLARE V_ERR_OCCURED BOOLEAN;
SET V_ERR_OCCURED=FALSE;
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
SET V_ERR_OCCURED=TRUE;
SET AUTOCOMMIT=0;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
LOCK TABLES `ACTRANS` WRITE;
INSERT INTO ACTRANS
(VOCHNO,VOCHDT,ACCODE,AMOUNT,USER_ID)
VALUES
(V_VOCHNO,V_VOCHDT,V_ACCODE,V_AMOUNT,V_USER_ID);
IF V_ERR_OCCURED=TRUE THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
UNLOCK TABLES;
SET AUTOCOMMIT=1;
END;
END $$
DELIMITER ;
I find the procedure to be doing several things that are unnecessary.
SERIALIZABLE acts the same as REPEATABLE READ for what you're doing. The only effect of SERIALIZABLE in MySQL is that it makes non-locking SELECT statements into locking SELECT statements as if you had used SELECT...LOCK IN SHARE MODE. Since you only do an INSERT, this difference has no effect.
There's no need to disable autocommit and then do a transaction start and commit/rollback for a single statement. If you had left autocommit enabled, then a successful INSERT would commit, and an unsuccessful INSERT would not commit. It would have the same result as your code.
There would be no need for an SQLEXCEPTION handler to rollback the transaction, because you have only a single statement in the transaction. It either succeeds or it does not succeed.
Regardless of any of the above, you have shown no reason to use LOCK TABLES. It will only block INSERT/UPDATE/DELETE unnecessarily, if multiple clients try to add rows concurrently. If there is a special reason you need to do this, you have not described it.
Your procedure body has an unnecessary BEGIN/END block. It doesn't do anything. Also I believe that DECLARE is only allowed following the first BEGIN, not within other blocks in the body.
There is probably no reason to use a stored procedure at all, since it only accomplishes a single INSERT operation. Why not just do the INSERT directly in the client? I suppose if you want to restrict the privilege to do inserts to the procedure, if the user doesn't have that privilege, that would be a reason.
I also agree with the comment above that you are using a version of MySQL that is suspiciously out of date. MySQL 5.0 passed its end of life date in 2012, according to https://endoflife.software/applications/databases/mysql. You are missing many bug fixes, security patches, and of course modern features.
Update:
The comment from SolarFlare is correct, one is not allowed to use LOCK or UNLOCK statements in stored procedures in MySQL:
mysql> create procedure p()
-> begin
-> lock tables mytable write;
-> insert into mytable () values ();
-> unlock tables;
-> end//
ERROR 1314 (0A000): LOCK is not allowed in stored procedures

How to execute N inserts in mysql and in a "all or nothing " mode

I need to make N inserts into a bunch of tables but if any of them fails I want all of the inserts to be rolled.
I am using MySql 8.0 and InnoDB, I have spent a lot of time trying to make it work out and I have read that I should be using transactions to achieve that but it isn't working how I would like and I don't know how to fix it.
This is the definition of the table:
CREATE TABLE IF NOT EXISTS `Example` (
`A` VARCHAR(30) NOT NULL,
`ID` int NOT NULL,
PRIMARY KEY (`ID`)
);
the table only contains the record ("abc", 1)
So I execute
START TRANSACTION;
INSERT INTO `Example` (`A`, `ID`) VALUES ("abd", 2);
INSERT INTO `Example` (`A`, `ID`) VALUES ("abc", 1);
COMMIT;
after the transaction ("abd", 2) is added to the table.
I would expect to not have any change on the table since the second insert that is inside the transaction fails
Your transaction demarcation layer is wrong. You are executing COMMIT regardless of the 3rd statement suceeding or failing (it fails).
This is probably due to the statements being executed in the command line, one after another, without considering error conditions.
Transaction APIs such as Spring, JTA, etc. would "doom" the transaction when the 3rd statement fails, and would never honor a commit after that. They would translate the commit to a rollback instead.
For example in Spring you demarcate transactions by marking a method with the #Transactional annotation. You never execute a commit or a rollback programatically; Spring does it for you when the method finishes. In this case any RuntimeException thrown by the method will doom the transaction and Spring will rollback when the method finishes its execution. Otherwise, it will commit the transaction.

MySQL Script with rollback on error

I am trying to create a transaction in MySql which will roll back when an exception occurs during the transaction. Similar using the following in a stored procedure.
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
Can this be done without writing a stored procedure? For example the following snippet should roll back the first insert because the second insert would fail.
START TRANSACTION;
INSERT INTO mytable VALUE 'foo1',2,'foo3','foo4'
INSERT INTO mytable VALUE 'foo1','foo2','foo3','foo4'
COMMIT
If I understand correctly you want to run a SQL script containing, for example inserts, in a single transaction that will rollback if any of the inserts fail. Is right?
I'm not 100% on this, but I'm pretty sure you'd need to do this as a stored procedure or programatically in, say, Java.

run trigger without writing to binary log

I'm trying to insert rows into a table via a trigger or stored procedure without writing any data to the binary log. Is this possible? I know that for normal connections you can set SQL_LOG_BIN=0 to disable binary logging for the connection, but I haven't been able to get that to work for triggers. If there's a way to do this with a federated engine table, that would also be OK.
edit:
I can almost accomplish this via a stored procedure:
CREATE PROCEDURE nolog_insert(`id` INT, `data` blob)
BEGIN
SET SESSION SQL_LOG_BIN = 0;
INSERT INTO `table` (`id`, `data`) VALUES (id, data);
SET SESSION SQL_LOG_BIN = 1;
END
I insert a record by calling the procedure from the mysql prompt:
call nolog_insert(50, 'notlogged');
As expected, the record (50, 'notlogged') is inserted into the table, but is not written to the binary log.
However, I want to run this procedure from a trigger. When using a trigger as follows:
create trigger my_trigger before insert on blackhole_table for each row call nolog_insert(new.id, new.data);
The data is both inserted to the table and written to the binary log.
If you run statement based replication triggers are executed on both the master and the slave but not replicated. Perhaps that could solve your problem.
Other than that, it's not allowed to change sql_log_bin inside a transaction so I would say that there is no good way to have the effects of a trigger not replicated when using row based replication.

Is there an equivalent for SQL Server's ##error in MySQL

I want to run an update query against a production database and as good little developer I am trying to make it as safe as possible. I am looking to do the following
BEGIN TRANSACTION
UPDATE table_x SET col_y = 'some_value'
.
.
.
IF (##error <> 0)
BEGIN
ROLLBACK
END
ELSE
BEGIN
COMMIT
END
The above should work in SQL Server but I need this to work against a MySQL database.
EDIT:
Sorry, there is more than 1 statement to execute. Yes I am aware of not needing to wrap a single query in a transaction.
BEGIN;
UPDATE foo SET bar = 3;
UPDATE bar SET thing = 5;
COMMIT;
If an error occurs, the entire transaction will be rolled back automatically. You really only need to execute ROLLBACK if something in your application indicates the need to rollback.
It is possible to handle errors explicitly within procedures or compound statements in MySQL, but I wouldn't recommend going down this route. See this how-to article and the docs for DECLARE HANDLER. You'll also have to find the specific error code you want to handle, or you can use the general SQLEXCEPTION condition. You'll also want to review compound statements and defining stored programs.
Anyway, based on the docs, you could do something like the query below, but it honestly wouldn't do anything differently than my previous answer above. It would also get you very strange looks from anyone who uses MySQL (including myself).
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
UPDATE foo SET bar = 3;
UPDATE bar SET thing = 5;
COMMIT;
END;
Old answer:
If you're executing a single query, this doesn't make sense. Simply execute the query; if an error occurs, nothing will happen - your transaction will automatically be rolled back.
The reason is that, by default, all single queries are wrapped in a "hidden" transaction, called "autocommit" mode in MySQL. The typical alternative is to explicitly use transactions - once you execute "BEGIN" you have started a transaction. Once you either COMMIT or ROLLBACK you'll be back in autocommit mode.
So, the only reason to use a transaction in MySQL is if you want to rollback to a particular state when an error (or some other external event) occurs. In MySQL, a transaction is always aborted if an error occurs.
Lastly, it is possible to turn this behavior off entirely, and then you must use transactions explicitly at all times. I believe "BEGIN" is implied from when you last committed or rolled back, but you must either COMMIT or ROLLBACK any queries you run.
See The InnoDB Transaction Model in the MySQL manual for more info.
CREATE PROCEDURE prc_test()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
START TRANSACTION;
INSERT
INTO t_test VALUES ('test', 'test');
INSERT
INTO no_such_table
VALUES ('no');
COMMIT;
END;
CALL prc_test();
SELECT *
FROM t_test;
0 rows fetched.
I don't think this is necessary as there is the concept of implicit commit/rollback.
From MySQL docs:
By default, MySQL starts the session
for each new connection with
autocommit mode enabled, so MySQL does
a commit after each SQL statement if
that statement did not return an
error. If a statement returns an
error, the commit or rollback behavior
depends on the error. See Section
13.6.13, “InnoDB Error Handling”.