Row Level Locking in Mysql - mysql

I have 5 rows in a table (1 to 5). I want row 2 lock for some update and in the meanwhile if someone tries to update row 4, then he should able to update.
I am trying this with code below, but I feel its placing lock on table level rather than row level.
------ session 1
START TRANSACTION;
SELECT * FROM test WHERE t=1 FOR UPDATE;
UPDATE test SET NAME='irfandd' WHERE t=2;
COMMIT;
----- session 2 (which is being blocked)
START TRANSACTION;
UPDATE test SET NAME='irfandd' WHERE t=4;
COMMIT;

Instead of FOR UPDATE use LOCK IN SHARE MODE. FOR UPDATE prevents other transactions to read the row as well. LOCK IN SHARE MODE allows read, but prevents updating.
Reference: MySQL Manual
------ session 1
START TRANSACTION;
SELECT * FROM test WHERE t=1 LOCK IN SHARE MODE;
UPDATE test SET NAME='irfandd' WHERE t=2;
COMMIT;
----- session 2 (which is not being blocked anymore :) )
START TRANSACTION;
UPDATE test SET NAME='irfandd' WHERE t=4;
COMMIT;
Update:
Realizing that the table has no index on t, I have the following explanation:
First, transaction T1 locks the row 1 in SELECT * FROM test WHERE t=1 FOR UPDATE
Next, transaction T2 tries to execute UPDATE test SET NAME='irfandd' WHERE t=4. To find out which row(s) are affected, it needs to scan all rows, including row 1. But that is locked, so T2 must wait until T1 finishes.
If there is any kind of index, the WHERE t=4 can use the index to decide if row 1 contains t=4 or not, so no need to wait.
Option 1: add an index on test.t so your update can use it.
Option 2: use LOCK IN SHARE MODE, which is intended for putting a read lock only.
Unfortunately this option creates a deadlock. Interestingly, T2 transaction executes (updating row 4), and T1 fails (updating row 2). It seems that T1 read-locks row 4 also, and since T2 modifies it, T1 fails because of the transaction isolation level (REPEATABLE READ by default). The final solution would be playing with Transaction Isolation Levels, using READ UNCOMMITTED or READ COMMITTED transaction levels.
The simplest is Option 1, IMHO, but it's up to your possibilities.

I found below option is more appropriate i generate 40000 numbers from concurrent session on the same time. I didnt found any duplicate number. Without below command i generate 10000 numbers and found 5 duplicate numbers.
START TRANSACTION
SELECT * FROM test WHERE t=1 FOR UPDATE;
UPDATE test SET NAME='irfandd' WHERE t=2;
COMMIT;

Related

Transactions - Locks on INSERT INTO...SELECT/UPATE...SELECT type queries

What does the bold text refer to? The "SELECT part acts like READ COMMITTED" part I already understand with this sql
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION; -- snapshot 1 for this transaction is created
SELECT * FROM t1; -- result is 1 row, snapshot 1 is used
-- another transaction (different session) inserts and commits new row into t1 table
SELECT * FROM t1; -- result is still 1 row, because its REPEATABLE READ, still using snapshot 1
INSERT INTO t2 SELECT * FROM t1; -- this SELECT creates new snapshot 2
SELECT * FROM t2; -- result are 2 rows
SELECT * FROM t1; -- result is still 1 row, using snapshot 1
Here: https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html
The type of read varies for selects in clauses like INSERT INTO ...
SELECT, UPDATE ... (SELECT), and CREATE TABLE ... SELECT that do not
specify FOR UPDATE or FOR SHARE:
By default, InnoDB uses stronger locks for those statements and the
SELECT part acts like READ COMMITTED, where each consistent read, even
within the same transaction, sets and reads its own fresh snapshot.
I do not understand THIS, what does a stronger block mean?
InnoDB uses stronger locks for those statements
This question helped me, but I still don't understand that part of the sentence.
Prevent INSERT INTO ... SELECT statement from creating its own fresh snapshot

How to simulate a deadlock on a row in mysql?

To simulate a lock in mysql I can grab the row with the following:
BEGIN;
SELECT * FROM table WHERE id=1 FOR UPDATE;
Now, if I try and update that row (from another connection) it will raise the following error after innodb_lock_wait_timeout seconds (default: 50):
(1205, 'Lock wait timeout exceeded; try restarting transaction')
How would I simulate a deadlock then, so I get an error that looks like:
Deadlock found when trying to get lock; try restarting transaction”
When I try and query or update the row?
Update: even when trying to simulate the mysql deadlock example, I get Lock wait timeout exceeded; try restarting transaction rather than a deadlock message.
First of all, refering to your last edit, the example in the manual should work. If it doesn't, there is either a fundamental problem, or you are missing some detail, so I would start there and make sure that you get it working.
The deadlock example has 3 steps, and I suspect you may have missed the last one:
T1: select
T2: delete. T2 has to wait for T1 now. Waiting means, that MySQL currently still sees a possible way that both T1 and T2 can finish successfully! For example, T1 can just commit now. Noone knows, so T2 waits for what happens. If you wait too long in this step, you will get a timeout (which is what I suspect happened).
T1: delete. This will result in a deadlock in T2. You need this last step to create a non-resolvable conflict.
You should try that example first, and carefully, as the devil is in the details. Leading to a detail in your own example:
You are using SELECT ... FOR UPDATE. FOR UPDATE is actually a way to reduce the number of deadlocks (which is the opposite of what you want), at the price of locking more restrictively. E.g. you have more situation where MySQL waits just to be safe, instead of going on and hoping it will work out eventually (or not, hence deadlock). Note that the example in the manual uses LOCK IN SHARE MODE for that reason.
So to modify and expand your own example to get a deadlock, you can do
T1: START TRANSACTION;
SELECT * FROM table WHERE id=1 LOCK IN SHARE MODE;
T2: START TRANSACTION;
UPDATE table SET id=2 WHERE id=1
-- wait
T1: UPDATE table SET id=2 WHERE id=1
-- deadlock in T2
For completeness (and to exclude a potential misunderstanding): the row has to exists, if your table is e.g. empty, you won't get a deadlock.
If you use FOR UPDATE instead, you don't get a deadlock, but T2 keeps waiting until you commit/rollback T1. It has to do with the way locking works, but you can maybe get an idea of that if you add a select to T2:
T1: START TRANSACTION;
SELECT * FROM table WHERE id=1 LOCK IN SHARE MODE;
T2: START TRANSACTION;
SELECT * FROM table WHERE id=1 LOCK IN SHARE MODE;
-- fine in shared mode. Waits here if you use `for update`!
T1: UPDATE table SET id=2 WHERE id=1
-- wait
T2: UPDATE table SET id=2 WHERE id=1
-- deadlock
If you replace both LOCK IN SHARE MODE with FOR UPDATE, T2 will wait at/before the select, until T1 commits, without a deadlock.
Is Deadlock Detection enabled?
You can read more here: https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlock-detection.html
A mechanism that automatically detects when a deadlock occurs, and automatically rolls back one of the transactions involved (the victim). Deadlock detection can be disabled using the innodb_deadlock_detect configuration option.
Lock another table in another transaction and then try to access other transactions table.
For example:
In transaction A lock table 1
In transaction B lock table 2
In transaction A to update table 2
In transaction B to update table 1.
Also, you can increase your timeout to 5 minutes so that while you are creating your deadlock it doesn't timeout.
UPDATE:
An example
In session A:
START TRANSACTION;
UPDATE tbl1 SET b=1 WHERE id=1;
in session B:
START TRANSACTION;
UPDATE tbl2 SET b=1 WHERE id=1;
Then
In session A:
UPDATE tbl2 SET b=1 WHERE id=1;
in session B:
UPDATE tbl1 SET b=1 WHERE id=1;

How do I unlock a tuple after updating in MySQL/Innodb?

I am trying to model a transaction database for my databases course. I can't find how to unlock a tuple after using it for update.
I have used commits and assumed that this would release the exclusive lock but it doesn't.
START TRANSACTION;
BEGIN;
SELECT * FROM account WHERE account_num = 3 FOR UPDATE;
UPDATE account SET balance= balance + 100 WHERE account_num = 3;
COMMIT;
What am I supposed to do to make sure this exclusive lock is let go?
START TRANSACTION and BEGIN are synonyms; don't use both.
Unless you are doing something else in the transaction, there is no need for the SELECT, nor for the transaction. UPDATE will lock the row for the duration of the UPDATE.

How can I rollback a transaction on error in MySQL?

update my_table set limit_id = 2 where id='176846';
start transaction;
update my_table set limit_id = 1 where id='176846';
update my_table set limit_id = 4 where id='176846'; -- <- this one fails
commit;
select limit_id from my_table where id='176846';
I would like to roll this back automatically - I want the script to output 2, not 1. I have no access to the connection policy in use.
reading here:
http://dev.mysql.com/doc/refman/5.5/en/commit.html
By default, MySQL runs with autocommit mode enabled. This means that
as soon as you execute a statement that updates (modifies) a table,
MySQL stores the update on disk to make it permanent. The change
cannot be rolled back.
try something like
SET autocommit = 0;
start transaction;
(...)
commit;
It depends on why a limit_id value of 4 causes an error, but MySql does not always roll back the entire transaction. See: http://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html for more information, but in several cases, MySql will only implicitly rollback the last statement, then continue with the transaction.

MySql InnoDB increment and return a field in a transaction

In my application I want to take a value from an InnoDB table, and then increment and return it within a single transaction. I want also lock the row that i am going to update in order to prevent another session from changing the value during the transaction. I wrote this query;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION;
SELECT #no:=`value` FROM `counter` where name='booking' FOR UPDATE;
UPDATE `counter` SET `value` = `value` + 1 where `name`='booking';
SELECT #no;
COMMIT;
I want to know if the isolation level is right and is there any need for 'FOR UPDATE' statement. Am i doing it right?
Yes whatever you are doing perfectly fine.
Below lines I am directly quoting from MySQL documentation.
"If you query data and then insert or update related data within the same transaction, the regular SELECT statement does not give enough protection.
..
To implement reading and incrementing the counter, first perform a locking read of the counter using FOR UPDATE, and then increment the counter. For example:
SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;
A SELECT ... FOR UPDATE reads the latest available data, setting exclusive locks on each row it reads. Thus, it sets the same locks a searched SQL UPDATE would set on the rows.
Reference:
https://dev.mysql.com/doc/refman/5.6/en/innodb-locking-reads.html