MySQL lock confusion - mysql

Mysql 5.6 Innodb repeatable-read isolation level.
T1 T2
select ... where id = 1 for update
select ... where id = 1 for update
T1 run first, then execute T2, they are in separate transactions.
The result is select for update of T2 is blocked.
According to https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html,
select for update will set IX lock on table, and IX is compatible with IX base on the lock compatibility matrix in this document.
Then why 2nd select for update is blocked by 1st?
I searched some posts about this question, now I also have following questions related with it:
select for update will set IX on table first, then set X on match index/row, right?
X and S lock can be table-level or row-level, right?
In lock type compatibility matrix of https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html, the X and S means table-level lock, not row-level, right?
2nd select for update blocked, because 1st select for update have set IX on table and X on matched index/row, so when 2nd select for update set IX on table, it is ok. But when it set X latter, it is blocked because it has been set X by 1st select for update, right?

"Then why 2nd select for update is blocked by 1st?" - because that is the whole point of having an exclusive(X) lock.
Yes.
Yes.
Yep.
Right.

Related

MySQL SET user variable locks rows and doesn't obey REPEATABLE READ

I've encountered an undocumented behavior of "SET #my_var = (SELECT ..)" inside a transaction:
The first one is that it locks rows ( depends whether it is a unique index or not ).
Example -
START TRANSACTION;
SET #my_var = (SELECT id from table_name where id = 1);
select trx_rows_locked from information_schema.innodb_trx;
ROLLBACKL;
The output is 1 row locked, which is strange, it shouldn't gain a reading lock.
Also, the equivalent statement SELECT id INTO #my_var won't produce a lock.
It can lead to a deadlock in case of an UPDATED after the SET statement ( for 2 concurrent requests )
In REPEATABLE READ -
The SELECT inside the SET statement gets a new snapshot of the data, instead of using the original SNAPSHOT.
SESSION 1:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START transaction;
SELECT data FROM my_table where id = 2; # Output : 2
SESSION 2:
UPDATE my_table set data = 3 where id = 2 ;
SESSION 1:
SET #data = (SELECT data FROM my_table where id = 2);
SELECT #data; # Output : 3, instead of 2
ROLLBACK;
However, I would expect that #data will contain the original value from the first snapshot ( 2 ).
If I use SELECT data into #data from my_table where id = 2 then I will get the expected value - 2;
Do you have an idea what is the source of the different behavior of SET = (SELECT ..) compared to SELECT data INTO #var FROM .. ?
Thanks.
Correct — when you SELECT in a context where you're copying the results into a variable or a table, it implicitly works as if you had used a locking read SELECT ... FOR SHARE.
This means it places a shared lock on the rows examined, and it also means that the statement reads only the most recently committed version of rows, as if your transaction were in READ-COMMITTED isolation level.
I'm not sure why SELECT ... INTO #var does not do the same kind of implicit locking in MySQL 8.0. My memory is that in older versions of MySQL it did do locking in that query form. I've searched the manual for an explanation but I can't find one yet.
Other cases that implicitly lock the rows examined by SELECT, and therefore reads data as if you transaction is READ-COMMITTED:
INSERT INTO <table> SELECT ...
UPDATE or DELETE multi-table, even if you don't update or delete a given table, the rows joined become locked.
SELECT inside a trigger

Why INSERT creates X lock

We have simple table:
CREATE TABLE t1 (
c1 INT UNSIGNED NOT NULL AUTO_INCREMENT,
c2 INT,
PRIMARY KEY (c1)
)
ENGINE=INNODB
1st transaction:
set transaction isolation level read uncommitted;
start transaction;
insert into t1 set c2 = 1;
For some reason INSERT creates IX lock on t1 table. I expected no locks. Especially i did not expect only IX lock, without X lock on some row.
2nd transaction:
set transaction isolation level read uncommitted;
start transaction;
update t1 set c2 = 2 where c1 = 1;
Here UPDATE statement creates IX lock on table t1 and create X lock on updated row. As i expected in read uncommited isolation level. But this X lock is waiting for X lock that belongs to 1st transaction. Yes, somehow it appears right now.
Why INSERT statement creates X lock on inserted row? What purpose? Where can i get information about such behaviour?
Why it happened in such weird way? Why INSERT created only IX lock and X lock appeared later?
It is normal behaviour for any database to lock in exclusive mode (X) any row or page it modifies. Also, READ UNCOMMITTED referrers to SELECT, nothing to do with INSERT.
To release the lock either COMMIT or ROLLBACK the first transaction.

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;

Row Level Locking in 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;

How locks (S,X,IS,IX) work in Mysql with queries like FOR UPDATE/LOCK IN SHARE MODE?

1:
I was trying this and it was working fine:
start transaction;
select * from orders where id = 21548 LOCK IN SHARE MODE;
update orders set amount = 1500 where id = 21548;
commit;
According to the definition of LOCK IN SHARE MODE , it locks the table with IS lock and lock the selected rows with S lock.
When a row is locked with S lock.How can it be modified without releasing lock?
It needs X lock to modify it.Right? Or is it valid only for different connection transaction?
2:
//session1
start transaction;
select * from orders where id = 21548 FOR UPDATE;
Keep this session1 same and try this in the different session:
//session2
select * from orders where id = 21548; //working
update orders set amount = 2000 where id = 21548; //waiting
FOR UPDATE locks the entire table into IX mode and selected row into X mode.
As X mode is incompatible with S mode then how come select query in second session is getting executed?
One answer might be that select query is not asking for S lock that's why it's running successfully.But update query in the second session is also not asking for X lock , but as you execute it , it starts waiting for the lock held by session1.
I have read a lot of stuff regarding this but not able to clear my doubts.Please help.
Bill Karwin answered this question through email.He said:
The same transaction that holds an S lock can promote the lock to an X lock. This is not a conflict.
The SELECT in session 1 with FOR UPDATE acquires an X lock. A simple SELECT query with no locking clause specified does not need to acquire an S lock.
Any UPDATE or DELETE needs to acquire an X lock. That's implicit. Those statements don't have any special locking clause for that.
For more details on IS/IX locks and FOR UPDATE/LOCK IN SHARE MODE please visit
shared-and-exclusive-locks .