Are MySql stored procedures atomic? - mysql

As the title says, are stored procedures in MySQL atomic? i.e. would something like
for (..)
<check_if_row_has_flag>
for (..)
<update_row>
work atomically?
Interestingly, I couldn't find much about this on Google except one forum thread from 2009.

No, stored procedures are not atomic.
The pseudocode you show above has a race condition. The first loop, checking if a row has a flag, would return an answer, but unless you do a locking read, another concurrent session could change the flag immediately after your procedure reads the row.
This is the effect of optimistic locking. Rows are not locked until you issue a statement to lock them. So even within a transaction, you don't have atomic locking.
The atomicity that MySQL supports is for transaction commit. Transactions are atomic in that all changes made during the transaction succeed, or else all are rolled back. Other sessions cannot see your transaction in a partially-complete state.
Re the comments below:
You can call a procedure within a transaction from your app:
START TRANSACTION;
CALL MyProcedure();
COMMIT;
You can even start and commit a transaction (or multiple transactions serially), explicitly in the body of a procedure:
CREATE PROCEDURE MyProcedure()
BEGIN
START TRANSACTION;
...UPDATE, INSERT, DELETE, blah blah...
COMMIT;
END
But the procedure itself does not implicitly start or commit a transaction.

Related

How to block / delay execution of a mySQL query until the previous one has been completed in a stored procedure

I am trying to write a mySQL stored procedure that does the following:
1. insert a new row into table A;
2. read the new row from table A;
3. using information gleaned from step 2, insert a new row into table B
I need step 2 to wait until step 1 is finished, as table B uses one of the columns - to be more specific the autoincrement index - of table A as a foreign key. I cannot know what that value is until the new row has been inserted.
Usually, step 2 does take place after step 1 is finished, but I don't want any hiccups caused by a slow insertion or any other unforeseen factors.
On second thought, I probably also need mySQL to block any new insertions into table A until I have finished reading from it.
Any ideas? Thank you very much!
You can use the GET_LOCK() and RELEASE_LOCK() functions in MySQL to block or delay the execution of a query until a previous one has been completed within a stored procedure.
The GET_LOCK() function can be used to acquire a named lock, which prevents other sessions from acquiring the same lock until the lock is released. If the lock is already held by another session, the GET_LOCK() function will block until the lock is released or a timeout is reached.
The RELEASE_LOCK() function can be used to release a previously acquired named lock.
Here is an example of how to use GET_LOCK() and RELEASE_LOCK() in a stored procedure:
DELIMITER $$
CREATE PROCEDURE my_procedure()
BEGIN
DECLARE lock_name VARCHAR(255) DEFAULT 'my_lock';
-- Acquire the lock
SELECT GET_LOCK(lock_name, 60); -- Wait up to 60 seconds for the lock to be released
-- Execute your query here
-- Release the lock
SELECT RELEASE_LOCK(lock_name);
END$$
DELIMITER ;
In this example, the GET_LOCK() function is called with a lock name and a timeout value of 60 seconds. If the lock is already held by another session, the query will block until the lock is released or the timeout is reached.
After the lock is acquired, you can execute your query. Once the query is complete, you can release the lock by calling the RELEASE_LOCK() function with the same lock name.
It's important to note that you should use unique lock names to avoid conflicts with other sessions. Also, be careful not to hold the lock for too long, as this can lead to blocking and contention issues in your database.
You can use a transaction. This is exactly what they are for: avoiding inconsistent database states.
Also, for your step 2, you can use the LAST_INSERT_ID function. Even if you do not use a transaction, this function will return the last ID inserted by the current connection; if other connections insert a row, it won't affect the result of LAST_INSERT_ID() in your connection.
So:
INSERT INTO people (name) VALUES ('John');
INSERT INTO addresses (person_id, address) VALUES (LAST_INSERT_ID(), 'There');
is likely sufficient for what you need; if you have more complex logic, transactions will help:
BEGIN TRANSACTION;
-- do anything
COMMIT;
A transaction is treated as a logical unit; other modifications to the database will either happen before or after the transaction, not during one.

How do I handle errors so a locked table will unlock in a procedure?

I want to have a stored procedure that does the following:
Locks a table
Checks for a value in it
Updates same table based on that value
Unlocks the table
If an error occurs between 1 and 4, will the table be unlocked? Or do I need to capture the error somehow? (how?)
Is there a better way to do this?
You can't lock a table within a stored procedure in MySQL.
SQL Statements Not Permitted in Stored Routines
Stored routines cannot contain arbitrary SQL statements. The following statements are not permitted:
The locking statements LOCK TABLES and UNLOCK TABLES.
— http://dev.mysql.com/doc/refman/5.6/en/stored-program-restrictions.html
If you are using InnoDB, then you can accomplish your purpose by locking the rows of interest using locking reads with SELECT ... FOR UPDATE. When you hit an error and roll back the transaction, the rows are unlocked automatically.
I wrote about this in detail in this recent answer where the question involved avoiding conflicting inserts but the underlying concept is the same whether you know the row you want already exists, or whether it might or might not exist.
Have you considered using transactions with a try-catch block? See this:
BEGIN TRAN
SAVE TRAN S1 -- Savepoint so any rollbacks will only affect this transaction
BEGIN TRY
/* Do your work in here */
END TRY
BEGIN CATCH
ROLLBACK TRAN S1 -- rollback just this transaction
SET #ErrorMessage = ERROR_MESSAGE()
SET #Severity = ERROR_SEVERITY()
SET #State = ERROR_STATE()
RAISERROR(#ErrorMessage, #Severity, #State) -- re-throw error if needed
END CATCH

InnoDb transactions with create statements

Are sql statements such as CREATE TABLE tbl_name ..... allowed in transactions.
For example:
begin;
CREATE TABLE .......;
sdfghjk;
rollback;
The table is still created despite a statement in the transaction failing, and a rollback at the end. Is there a way to prevent the table from being created if a statement in the transaction fails?
DDL statements are allowed within transactions, but are not generally impacted by the transactions. From the MySQL Documentation on what can and cannot be rolled back:
Some statements cannot be rolled back. In general, these include data
definition language (DDL) statements, such as those that create or
drop databases, those that create, drop, or alter tables or stored
routines.
You should design your transactions not to include such statements. If
you issue a statement early in a transaction that cannot be rolled
back, and then another statement later fails, the full effect of the
transaction cannot be rolled back in such cases by issuing a ROLLBACK
statement.
Source
If you still need to use table you can do create temporary table..... It doesn't commit transaction but will be deleted when connection will be closed.

mysql transaction in stored procedure (locking/rollback)

does a transaction in a stored procedure do any locking to prevent others from updating tables?
also do i need to explicitly put in rollback logic or will the transaction automatically roll back if an error occurs because it never reaches the commit command.
Does a transaction in a stored procedure do any locking to prevent others from updating tables?
When you perform some DML on an InnoDB table, the rows affected get locked until the end of transaction (doesn't matter is it inside a stored procedure or not).
You can modify the locked rows inside the same transaction that locked it.
To explicitly lock some rows, issue:
SELECT *
FROM table
WHERE condition
FOR UPDATE
Except for commiting or rolling back the transaction, there is no other way to unlock the rows
Do i need to explicitly put in rollback logic or will the transaction automatically roll back if an error occurs because it never reaches the commit command.
You'll need to perform a rollback explicitly.

Transactional updates that are applied to DB regardless of no commit or rollback

I was under the impression that all updates to a SQL server database are first added the T-Log before being applied to the underlying database. In the event of the server crashing, the restore process would rollback any uncommitted transactions. This I also assumed works with transactions, if a commit or rollback is not called the changes will not be made.
So I wanted to see the reaction of SQL server to transactions being cut short. i.e. transactional updates without a commit or rollback. What I found I don’t quite understand. Especially, how SQL server can allow this to happen.
I used the script below to insert rows into a table with a delay to give me enough time to stop the transaction before it reaches the commit or rollback. This I guess would simulate the client application timing out before the transaction completed.
Create Table MyTest (Comment varchar(20))
Go
Create Procedure MyProc
as
Begin Try
Begin Transaction
Insert Into MyTest Select 'My First Entry'
WaitFor Delay '00:00:02'
Insert Into MyTest Select 'My Second Entry'
WaitFor Delay '00:00:02'
Insert Into MyTest Select 'My Third Entry'
Commit Transaction
Return 0 -- success
End Try
Begin Catch
If (##trancount<>0) Rollback Transaction
Declare #err int, #err_msg varchar(max)
Select #err = error_number(), #err_msg = error_message()
Raiserror(#err_msg, 16,1)
Return #err
End Catch
If you run the script, depending on how quickly you stop the procedure, you will see that the first one or two inserts will remain in the table. Could someone explain why this would happen?
Select * From MyTest
I tested this on SQL 2008.
Correct, TXNs are written using "Write Ahead Logging". There are MSDB articles about it and how this interacts with commit/rollback/checkpoints etc
However, a command timout (or what you are doing simply stops code executing) and the TXN is never rolled back and locks released until the connection is closed (or done later separately). This is what SET XACT_ABORT is for
If you begin a transaction and do not commit it or roll it back, you will simply get a hanging transaction that is likely to block other users until something is done with the current transaction. SQL Server will not automatically commit or rollback a transaction on its own, simply because your code didn't do so. The transaction will stay in place and block other users until it's committed or rolled back.
Now, I can quite easily begin a transaction in my T-SQL code, not commit or roll it back, and do a Select statement and see that data that I just inserted or updated as long as the Select statement is using the same connection as my transaction. If I attempt to do a Select using a different transaction, I won't see the inserted or updated data. In fact, the Select statement might not finish at all until the transaction on the other connection is completed.