I did it like below
-- connection 1
START TRANSACTION;
LOCK TABLES [table_name] WRITE;
-- connection 2
START TRANSACTION;
SELECT * FROM [table_name]; -- waiting for table metadata lock
I don't understand above situation.
I thought WRITE LOCK doesn't prevent SELECT query.
When I did like below,
-- connection 1
START TRANSACTION;
UPDATE [table_name] SET [column = value] WHERE id = 1;
-- connection 2
START TRANSACTION;
SELECT * FROM [table_name] WHERE id = 1; -- doesn't wait
SELECT doesn't wait as you saw.
And I thought WRITE LOCK also work like UPDATE clause.
But it seems like not..
Now I know something wrong?
Write lock does not work like row-level locking.
https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html says:
WRITE lock:
Only the session that holds the lock can access the table. No other session can access it until the lock is released.
"Access" in this context means read or write.
Related
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;
CREATE TABLE 'test'.'t1' (
'id' INT NULL);
CREATE TABLE 'test'.'t2' (
'id' INT NULL);
INSERT INTO test.t1 VALUES(1);
INSERT INTO test.t2 VALUES(1);
example1:
sqlConnection1:
SET autocommit = 0;
START TRANSACTION;
UPDATE test.t1 set id = 1 WHERE id = 2;
sqlConnection2:
SET autocommit = 0;
START TRANSACTION;
LOCK TABLES test.t2 WRITE,test.t1 WRITE;
COMMIT;
UNLOCK TABLES;
sqlConnection1:
UPDATE test.t2 set id = 1 where id = 2;
COMMIT;
sqlConnection2:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restart transaction
example2:
sqlConnection1:
SET autocommit = 0;
START TRANSACTION;
UPDATE test.t1 set id = 1 WHERE id = 1;
sqlConnection2:
SET autocommit = 0;
START TRANSACTION;
LOCK TABLES test.t2 WRITE,test.t1 WRITE;
COMMIT;
UNLOCK TABLES;
sqlConnection1:
UPDATE test.t2 set id = 1 where id = 1;
COMMIT;
sqlConnection1:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restart transaction
example3:
deadlock not found after mysql5.6
question:
What causes the results to differ in three examples
There are several points in this problem.
When InnoDB tries to update a record it will put a RECORD LOCK or a GAP LOCK onto the range it is going to update. For records that are existent, InnoDB will put a record lock, otherwise a gap lock. For example, in example 1, InnoDB will add a gap lock, and in this case, it adds a [2,∞) gap lock onto the table.
MySQL's LOCK TABLES command actually implements in MySQL itself but not in InnoDB. Although MySQL's LOCK TABLE has a "deadlock-free design" in other table-lock-only storage engines like MyISAM, it cannot avoid the deadlocks in InnoDB since InnoDB has row-level locks. Fortunately, the InnoDB has a deadlock detection mechanism that can detect when an InnoDB row lock and a MySQL table lock occur together on the same table and do what you see -- rollback a "lighter" transaction to avoid deadlock, which is so-called "victim".
The InnoDB engine will generally decide the transaction with fewer rows affected to be the victim. In example 1, connection 1 puts two GAP LOCKS on rows [2,∞) for two tables, while connection 2 only acquires two WRITE LOCKS and does not get anything done. Thus, connection 2 is "lighter" and being selected as the "victim". In example 2, connection 1 only has two RECORD LOCKS while connection 2 still has two WRITE LOCKS, and connection 1 basically do not do anything. Thus InnoDB decides that connection 1 uses fewer resources (i.e. locks in this case), and chooses connection 1 as the victim.
Since MySQL 5.6, a new lock wait mechanism has been introduced to MySQL. When MySQL cannot acquire a table lock, it will wait as long as the interval lock-wait-timeout specified. quote from the MySQL document:
This variable[lock-wait-timeout] specifies the timeout in seconds for attempts to acquire metadata locks. The permissible values range from 1 to 31536000 (1 year). The default is 31536000.
This timeout applies to all statements that use metadata locks. These include DML and DDL operations on tables, views, stored procedures, and stored functions, as well as LOCK TABLES, FLUSH TABLES WITH READ LOCK, and HANDLER statements.
Thus, MySQL will not acquire a table lock when it sees InnoDB already has a lock on the table it is going to manipulate and wait until InnoDB gets its work done. For further information, you can refer to MySQL document(The following documents are for 8.0 version, but the basic principles are the same):
https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html
https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlock-detection.html
I want to create a Lost Update with MySQL Workbench. Therefore, I have 2 connections to my database and 2 transactions. I also changed the transaction isolation level to read uncommitted but transaction A uses the current data when the update statement starts. It never uses the data from the first select statement and with select ... for update the transaction b is blocked.
Transaction A (starts first):
Start transaction;
SELECT * FROM table;
Select sleep(10); -- <- Transaction B executes in this 10 seconds
UPDATE table SET Number = Number + 10 WHERE FirstName = "Name1";
COMMIT;
Transaction B:
Start transaction;
UPDATE table SET Number = Number - 5 WHERE FirstName = "Name1";
COMMIT;
Is it possible to create this failure with MySQL Workbench. What´s wrong with my code?
Thanks for your help
The update in A work with data after the sleep is executed. Select before does nothing in the transaction.
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.
I am performing SELECT ... FOR UPDATE or row level locking with InnoDB tables.
My intention is to only one request can read the same row. So if two users make request for the same data as the same time. Only one of them get data, who fires the query first.
But How can i test that locking is placed or not. as I am testing it by retrieving the same data at same time and both users getting the data.
Note: My tables are InnoDB, My query executes in transaction, my query as below:
SELECT * FROM table_name WHERE cond FOR UPDATE;
Any other thing I have to check for this to make work?
open 2 mysql client session.
on session 1:
mysql> start transaction;
mysql> SELECT * FROM table_name WHERE cond FOR UPDATE;
... (result here) ...
1 row in set (0.00 sec)
on session 2:
mysql> start transaction;
mysql> SELECT * FROM table_name WHERE cond FOR UPDATE;
... (no result yet, will wait for the lock to be released) ...
back to session 1, to update selected record (and release the lock):
mysql> UPDATE table_name SET something WHERE cond;
mysql> commit;
back to session 2:
1) either showing lock timeout error
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
2) or showing result
... (result here) ...
1 row in set (0.00 sec)
3) or showing no result (because corresponding record has been modified, so specified condition was not met)
Empty set (0.00 sec)
You can use own lock mechanizm with lock_by column.
UPDATE table_name SET locked_by=#{proccess_id} WHERE cond and locked_by IS NULL
Now in your program you will get count of affected rows:
if(affected_rows==0)
return 'rows locked'
else
//do your staff with locked_by=#{process_id} rows
With this mechanism you can control locked rows and locking processes. You can also add in UPDATE statement locked_at=NOW() to get more info about locked row.
Don't forget to add some index on locked_by column.
Here is MySQL docs about working with locks.
Before update you can put lock, releasing it after. In another transaction you can check lock using it unique name. Strategy for naming you can choose yourself.