Multiple SELECT ... FOR UPDATE with a delayed INSERT INTO - mysql

I am having an issue with SELECT ... FOR UPDATE and INSERT INTO statements on separate connections deadlocking.
Given an empty table tblFoo with the primary key id, consider the following pseudocode:
function locate(array values) {
BEGIN TRANSACTION;
rows = SELECT * FROM tblFoo WHERE id IN values FOR UPDATE;
if (rows is empty) {
sleep(10); // i.e., do some stuff
rows = INSERT INTO tblFoo (id) VALUES values;
}
COMMIT;
return rows;
}
On process A # t=0: return locate([1,2,3]);
On process B # t=1: return locate([1]);
My expectation is that process 1 would gap lock rows with ids 1, 2, 3 which blocks process B at the SELECT ... FOR UPDATE until process A's transaction is committed. Once committed, process B gets unblocked and returns the row with id 1 which was just inserted by process A.
The observed behavior is that a deadlock is encountered, causing process A to roll back and process B inserts a row with id 1.
Can anyone help me understand why MySQL is behaving this way?
I am using innoDB with MySQL version 5.5.
Edit: The following is the table structure
CREATE TABLE `tblFoo` (
`id` INT(11) NOT NULL,
PRIMARY KEY (`id`)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
;
Edit 2: The following is the innoDB status detailing the deadlock
------------------------
LATEST DETECTED DEADLOCK
------------------------
161205 15:55:50
*** (1) TRANSACTION:
TRANSACTION 32A3E743A, ACTIVE 3 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1248, 2 row lock(s)
MySQL thread id 12243323, OS thread handle 0x7fd7dd47f700, query id 4713227035 localhost root update
INSERT INTO test.tblFoo (id) VALUES (1)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 1556528 n bits 72 index `PRIMARY` of table `test`.`tblFoo` trx id 32A3E743A lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000002; asc ;;
1: len 6; hex 00032a3e5f6b; asc *>_k;;
2: len 7; hex b30017d06b0110; asc k ;;
*** (2) TRANSACTION:
TRANSACTION 32A3E5FD3, ACTIVE 5 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1248, 5 row lock(s)
MySQL thread id 12243319, OS thread handle 0x7fd7f0097700, query id 4713230393 localhost root update
INSERT INTO test.tblFoo (id) VALUES (1),(2),(3)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 1556528 n bits 72 index `PRIMARY` of table `test`.`tblFoo` trx id 32A3E5FD3 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000005; asc ;;
1: len 6; hex 00032a38e424; asc *8 $;;
2: len 7; hex cc001c166a0110; asc j ;;
Record lock, heap no 4 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000002; asc ;;
1: len 6; hex 00032a3e5f6b; asc *>_k;;
2: len 7; hex b30017d06b0110; asc k ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 1556528 n bits 72 index `PRIMARY` of table `test`.`tblFoo` trx id 32A3E5FD3 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000002; asc ;;
1: len 6; hex 00032a3e5f6b; asc *>_k;;
2: len 7; hex b30017d06b0110; asc k ;;
*** WE ROLL BACK TRANSACTION (2)

I think what is happening is, that you start 2 transactions. Both get a "select ... for update" on the same table. You would expect the second transaction to wait before the update. But from the docs https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html it sounds like it will not (and this is the behaviour you see). So both "selects ... for update" block each other, leaving you with a deadlock.

Related

deadlock when selecting max() for update on auto increment primary key

I have a table defined in this way:
CREATE TABLE `measure` (
`measureId` bigint NOT NULL,
`sensorId` int NOT NULL,
`timestamp` bigint NOT NULL,
`data` float NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
ALTER TABLE `measure`
ADD PRIMARY KEY (`measureId`),
ADD KEY `measure_index` (`sensorId`,`timestamp`);
ALTER TABLE `measure`
MODIFY `measureId` bigint NOT NULL AUTO_INCREMENT;
measureId is mostly used as auto increment, but sometimes I need to specify measureId during insert. I use the following transaction for my application:
begin;
select max(measureId) from measure for update;
-- use the max id retrieved to create measurements
insert into measure values (max_id + 1, ...), (max_id + 2, ...), ...;
-- do other stuff
commit;
I use select ... for update to avoid insertions between selecting max(measureId) and and adding the new rows. Without using it, the transaction would fail since the id would be already taken via autoincrement (I use the default isolation REPEATABLE READS).
In a non concurrent environment the transaction succeeds, but I get a deadlock when this happens between two transactions:
T1: select max(measureId) ...;
T2: select max(measureId) ...; -- starts waiting
T1: into measure values (max_id + 1, ...), ...;
-- ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Why should this be a dealock? T1 should already have the lock on the primary key index (link).
How can i fix this?
Edit: Added output of the innodb status monitor, not sure what is happening. I seems to me that transaction 2 (below) is waiting for a lock it already has.
------------------------
LATEST DETECTED DEADLOCK
------------------------
2023-01-23 13:58:10 139850373236480
*** (1) TRANSACTION:
TRANSACTION 41530, ACTIVE 62 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s)
MySQL thread id 1251, OS thread handle 139850310264576, query id 5030779 172.19.0.1 root optimizing
select max(measureId) from measure for update
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 177 page no 22722 n bits 360 index PRIMARY of table `WEATHER_STATION`.`measure` trx id 41530 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 177 page no 22722 n bits 360 index PRIMARY of table `WEATHER_STATION`.`measure` trx id 41530 lock_mode X waiting
Record lock, heap no 290 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 8; hex 80000000004aa795; asc J ;;
1: len 6; hex 00000000a1f8; asc ;;
2: len 7; hex 82000000a913e6; asc ;;
3: len 4; hex 80000192; asc ;;
4: len 8; hex 8000000063cac0ed; asc c ;;
5: len 4; hex 6666ea41; asc ff A;;
*** (2) TRANSACTION:
TRANSACTION 41529, ACTIVE 163 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 3 row lock(s)
MySQL thread id 1250, OS thread handle 139850312378112, query id 5030780 172.19.0.1 root update
insert into measure values (4892566, 1, 2, 3)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 177 page no 22722 n bits 360 index PRIMARY of table `WEATHER_STATION`.`measure` trx id 41529 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 290 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 8; hex 80000000004aa795; asc J ;;
1: len 6; hex 00000000a1f8; asc ;;
2: len 7; hex 82000000a913e6; asc ;;
3: len 4; hex 80000192; asc ;;
4: len 8; hex 8000000063cac0ed; asc c ;;
5: len 4; hex 6666ea41; asc ff A;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 177 page no 22722 n bits 360 index PRIMARY of table `WEATHER_STATION`.`measure` trx id 41529 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
*** WE ROLL BACK TRANSACTION (2)
Instead, get rid of measureid:
CREATE TABLE `measure` (
`sensorId` int NOT NULL,
`timestamp` bigint NOT NULL,
`data` float NOT NULL,
PRIMARY KEY(sensorId, timestamp)
) ENGINE=InnoDB
Consider using the TIMESTAMP(n) datatype, where n is the number of decimal places (fraction of a second). The max is 6 (microseconds). This will give you a variety of date/time functions that will probably be clearer than futzing with a BIGINT.
Suggest shrinking sensorId to a smaller datatype. For example, SMALLINT UNSIGNED would allow 65K sensors in a 2-byte column.
With those changes, the table (data+indexes) will take about half the disk space. This will have a favorable impact on speed.
With that, you won't need the SELECT (another speedup). And you can possibly do nothing more than the multi-row INSERT.

How to explain the reason for this deadlock?

There are tow transaction,transaction 1 holds an S lock on a row,transaction 2 wants to update the row,then transaction 2 waits,then transaction 1 also performs an updates on the row,at this time a deadlock occurs,I think Know what the reason is ? what is the lock situation here?
I did the following test on mysql5.6 version.There is a deadlock.
Table Stracture:
CREATE TABLE `test` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增',
`uni_id` bigint(20) DEFAULT NULL,
`current_status` int(11) DEFAULT '0' ,
`total` int(11) NOT NULL DEFAULT '0' ,
PRIMARY KEY (`id`),
UNIQUE KEY `uni_id_unique` (`uni_id`),
KEY `uni_id_idx` (`uni_id`),
KEY `current_status_idx` (`current_status`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
init data:
INSERT INTO `test`(`id`, `uni_id`, `current_status`, `total`) VALUES (1, 1, 0, 1);
The following operations are performed in order:
1. first step
Transaction 1 :
start transaction;
select * from test where id=1 lock in share mode;
second step
start transaction;
update test set uni_id=1,total=total+1 where uni_id=1;
third step
Transaction 1:
update test set current_status=1 where id=1 and
current_status=0;
then the dealock happened.
first step : transaction 1 holds S lock.
second step: transaction 2 waits, and from the results of the source code debug,the obtained lock failed.
third step: deadlock
the deadlock info :
*** (1) TRANSACTION:
TRANSACTION 4360, ACTIVE 14 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 376, 2 row lock(s)
MySQL thread id 2, OS thread handle 0x70000a7f4000, query id 145 localhost 127.0.0.1 root updating
update test set uni_id=1,total=total+1 where uni_id=1
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4 page no 3 n bits 72 index `PRIMARY` of table `test`.`test` trx id 4360 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 8; hex 8000000000000001; asc ;;
1: len 6; hex 000000001106; asc ;;
2: len 7; hex 83000001360110; asc 6 ;;
3: len 8; hex 8000000000000001; asc ;;
4: len 4; hex 80000000; asc ;;
5: len 4; hex 80000001; asc ;;
*** (2) TRANSACTION:
TRANSACTION 4359, ACTIVE 24 sec starting index read
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1248, 2 row lock(s)
MySQL thread id 1, OS thread handle 0x70000a7b0000, query id 149 localhost 127.0.0.1 root updating
update test set current_status=1 where id=1 and
current_status=0
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 4 page no 3 n bits 72 index `PRIMARY` of table `test`.`test` trx id 4359 lock mode S locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 8; hex 8000000000000001; asc ;;
1: len 6; hex 000000001106; asc ;;
2: len 7; hex 83000001360110; asc 6 ;;
3: len 8; hex 8000000000000001; asc ;;
4: len 4; hex 80000000; asc ;;
5: len 4; hex 80000001; asc ;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4 page no 3 n bits 72 index `PRIMARY` of table `test`.`test` trx id 4359 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
0: len 8; hex 8000000000000001; asc ;;
1: len 6; hex 000000001106; asc ;;
2: len 7; hex 83000001360110; asc 6 ;;
3: len 8; hex 8000000000000001; asc ;;
4: len 4; hex 80000000; asc ;;
5: len 4; hex 80000001; asc ;;
*** WE ROLL BACK TRANSACTION (1)
I don't believe that your analysis of what actually happened is completely correct. This is the likely version of events:
First transaction gets an S lock on the record
Second transaction wants to get an exclusive lock on the same record, but can't, because the first transaction holds the S lock. This transaction therefore waits, trying to obtain the lock.
The third transaction also goes into a wait state on the same record, but now a deadlock happens.
From the MySQL documentation:
Here, FOR SHARE is not a good solution because if two users read the counter at the same time, at least one of them ends up in deadlock when it attempts to update the counter.
As that documentation suggests, a better approach might be to do a SELECT ... FOR UPDATE:
SELECT * FROM test WHERE id = 1 FOR UPDATE;
UPDATE test SET uni_id = 1, total = total+1 WHERE uni_id = 1;
A Friend of mine explained this situation.
From the MYSQL documentation:
Deadlock occurs here because client A needs an X lock to delete the row. However, that lock request cannot be granted because client B already has a request for an X lock and is waiting for client A to release its S lock. Nor can the S lock held by A be upgraded to an X lock because of the prior request by B for an X lock. As a result, InnoDB generates an error for one of the clients and releases its locks

How would this deadlock happens?

Recently I am handing a SQL issue, I have two transaction, the transaction A first got the Next-Key Locks, and the transaction B tried to get the same lock, so it was waiting, then the transaction A tried to get Insert Intention Locks, so the deadlock happened. But I am confuse that why would this happens?
Here is my table structure:
CREATE TABLE `changeset` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT',
`userId` int(10) NOT NULL COMMENT,
`documentId` varchar(20) NOT NULL,
`memberId` bigint(13) NOT NULL,
`createTime` bigint(13) NOT NULL,
`version` bigint(13) NOT NULL COMMENT,
`changesets` mediumtext,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_documentId_version` (`documentId`,`version`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=85771623 DEFAULT CHARSET=utf8
And here is my deadlock log:
(1) TRANSACTION:
TRANSACTION 22640, ACTIVE 66 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 209, OS thread handle 123145559986176, query id 6204
localhost root Sending data
select * from changeset where documentId = '7oO5C_v' and version >=
13 for update
(1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 107 page no 15 n bits 704 index
uniq_documentId_version of table test.changeset trx id 22640
lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format;
info bits 0
0: len 7; hex 3976735431644a; asc 9vsT1dJ;;
1: len 8; hex 8000000000000000; asc ;;
2: len 4; hex 051cbef7; asc ;;
(2) TRANSACTION:
TRANSACTION 22639, ACTIVE 95 sec inserting
mysql tables in use 1, locked 1
6 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1
MySQL thread id 212, OS thread handle 123145561657344, query id 6210
localhost root update insert into changeset values (0, 9, '7oO5C_v',
814, 1, 13, 'x')
(2) HOLDS THE LOCK(S):
RECORD LOCKS space id 107 page no 15 n bits 704 index
uniq_documentId_version of table test.changeset trx id 22639
lock_mode X
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format;
info bits 0
0: len 7; hex 3976735431644a; asc 9vsT1dJ;;
1: len 8; hex 8000000000000000; asc ;;
2: len 4; hex 051cbef7; asc ;;
(2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 107 page no 15 n bits 704 index
uniq_documentId_version of table test.changeset trx id 22639
lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format;
info bits 0
0: len 7; hex 3976735431644a; asc 9vsT1dJ;;
1: len 8; hex 8000000000000000; asc ;;
2: len 4; hex 051cbef7; asc ;;
WE ROLL BACK TRANSACTION (1)
Have you used "(nolock) " in your selects?
Select * From Table with (nolock)

MySQL deadlock with update and delete on the same row

I have a simple table with:
deviceid
pushid
tag
external_id
When sending messages to the users, we sometimes do an update of the pushid based on the deviceid:
update user_notifications set pushid='xyz' where deviceid='abc'
But at the same time we can get a new registration from the user where we reset all his notifications with
delete from user_notifications where pushid='xyz' and external_id is null
This seems to trigger deadlocks regularly. I have added indexes on "deviceid" and "pushid, external_id" but it seems to still trigger a deadlock. The table have no suitable primary key so MySQL have created a GEN_CLUST_INDEX key. Can this be the reason? Should I add an auto-incrementing primary key?
------------------------
LATEST DETECTED DEADLOCK
------------------------
141014 8:13:38
*** (1) TRANSACTION:
TRANSACTION F5ED32, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1248, 2 row lock(s)
MySQL thread id 2422, OS thread handle 0x7f6295cd3700, query id 35096 localhost root Updating
update user_notifications set pushid='APA91bEO5zBhpAqiNlHIlWvvb0U4KH2JWByzh5IzmJFg2GZyXX2s1wJ2pbTHWoTDDao5hoZ10e1bw70Z5nTi4dIEfsTj6q-cS9U0VuqwGkWpW4ofb4XnbjOd39845_jXsPaiFg5EmD0Y9JSd3rP3BY-M8ZQEet1So6SBOgSLdjlV5MtxYyR5kos' where deviceid='64881a83-c43b-4282-b82f-2a136395e3c6'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 143022 n bits 128 index `GEN_CLUST_INDEX` of table `myappdb`.`user_notifications` trx id F5ED32 lock_mode X locks rec but not gap waiting
Record lock, heap no 55 PHYSICAL RECORD: n_fields 7; compact format; info bits 32
0: len 6; hex 0000003315f7; asc 3 ;;
1: len 6; hex 000000f5ed31; asc 1;;
2: len 7; hex 63000c40180110; asc c # ;;
3: len 30; hex 36343838316138332d633433622d343238322d623832662d326131333633; asc 64881a83-c43b-4282-b82f-2a1363; (total 36 bytes);
4: len 30; hex 415041393162454f357a4268704171694e6c48496c577676623055344b48; asc APA91bEO5zBhpAqiNlHIlWvvb0U4KH; (total 183 bytes);
5: len 9; hex 7465616d5f39383233; asc team_9823;;
6: SQL NULL;
*** (2) TRANSACTION:
TRANSACTION F5ED31, ACTIVE 0 sec updating or deleting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 1
MySQL thread id 2423, OS thread handle 0x7f6295c92700, query id 35104 localhost root updating
delete from user_notifications where pushid='APA91bEO5zBhpAqiNlHIlWvvb0U4KH2JWByzh5IzmJFg2GZyXX2s1wJ2pbTHWoTDDao5hoZ10e1bw70Z5nTi4dIEfsTj6q-cS9U0VuqwGkWpW4ofb4XnbjOd39845_jXsPaiFg5EmD0Y9JSd3rP3BY-M8ZQEet1So6SBOgSLdjlV5MtxYyR5kos' and external_id is null
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 143022 n bits 128 index `GEN_CLUST_INDEX` of table `myappdb`.`user_notifications` trx id F5ED31 lock_mode X locks rec but not gap
Record lock, heap no 55 PHYSICAL RECORD: n_fields 7; compact format; info bits 32
0: len 6; hex 0000003315f7; asc 3 ;;
1: len 6; hex 000000f5ed31; asc 1;;
2: len 7; hex 63000c40180110; asc c # ;;
3: len 30; hex 36343838316138332d633433622d343238322d623832662d326131333633; asc 64881a83-c43b-4282-b82f-2a1363; (total 36 bytes);
4: len 30; hex 415041393162454f357a4268704171694e6c48496c577676623055344b48; asc APA91bEO5zBhpAqiNlHIlWvvb0U4KH; (total 183 bytes);
5: len 9; hex 7465616d5f39383233; asc team_9823;;
6: SQL NULL;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 68396 n bits 232 index `index3` of table `myappdb`.`user_notifications` trx id F5ED31 lock_mode X locks rec but not gap waiting
Record lock, heap no 97 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 30; hex 36343838316138332d633433622d343238322d623832662d326131333633; asc 64881a83-c43b-4282-b82f-2a1363; (total 36 bytes);
1: len 6; hex 0000003315f7; asc 3 ;;
*** WE ROLL BACK TRANSACTION (1)
As you said and the log shows, updating the 'pushid' value and deleting the row is happening at same time. This should be easy to avoid. For some official tips, take a look at MySQL 'How to Cope with Deadlocks" document: http://dev.mysql.com/doc/refman/5.5/en/innodb-deadlocks.html

Deadlock using SELECT ... FOR UPDATE in MySQL

Let's say I have the table:
CREATE TABLE t (id INTEGER AUTOINCREMENT NOT NULL, desc TEXT NOT NULL)
I populate the table with 1 element:
INSERT INTO TABLE t VALUES (1, 'Hello')
And I run two transactions in MySQL. In t1 I run:
START TRANSACTION;
SELECT * FROM t WHERE id = 1 FOR UPDATE;
In t2 I run:
START TRANSACTION;
SELECT * FROM t WHERE id = 1 FOR UPDATE;
At this point I expect t1 to hold an e(X)clusive lock on the row, and t2 to wait until it can get an X lock (and t2 gets indeed blocked, so far so good). I then run an update in t1 (without any WHERE clause!):
UPDATE t SET desc = 'Hello from t1';
At this point in t2 I get immediately (no need to COMMIT the transaction) the error:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
Why am I getting this error? I guess there is a lock that t2 is obtaining that the full UPDATE needs to proceed, making a deadlock, but I don't understand how can t2 obtain a lock given that it should be waiting for t1 to finish.
What works and what does not
A way to make both transactions run through without a deadlock is to change the isolation level to READ COMMITED (or READ UNCOMMITED) in both connections:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
(before start transaction).
Likely it would be enough to set it in t2, but just to be sure for the example, set it in both.
Changing the isolation level of transactions does introduce some side-effects, which one should inform about in the manual before changing this in a production environment.
Status information concerning deadlock
------------------------
LATEST DETECTED DEADLOCK
------------------------
140424 8:45:46
*** (1) TRANSACTION:
TRANSACTION B6F18A3, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 13885, OS thread handle 0x7f8b1dbd2700, query id 901012
localhost root statistics
SELECT * FROM t WHERE id = 1 FOR UPDATE
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 22921 n bits 72 index `PRIMARY` of table
`test`.`t` trx id B6F18A3 lock_mode X locks rec but not gap waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 00000b6f1883; asc o ;;
2: len 7; hex 06000059a211ea; asc Y ;;
3: len 5; hex 48656c6c6f; asc Hello;;
*** (2) TRANSACTION:
TRANSACTION B6F18A2, ACTIVE 10 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 376, 2 row lock(s)
MySQL thread id 13888, OS thread handle 0x7f8b1f64d700, query id 901068
localhost root Updating
UPDATE t SET `descc` = 'Hello from t1'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 22921 n bits 72 index `PRIMARY` of table
`test`.`t` trx id B6F18A2 lock_mode X locks rec but not gap
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 00000b6f1883; asc o ;;
2: len 7; hex 06000059a211ea; asc Y ;;
3: len 5; hex 48656c6c6f; asc Hello;;
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 22921 n bits 72 index `PRIMARY` of table
`test`.`t` trx id B6F18A2 lock_mode X waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 00000b6f1883; asc o ;;
2: len 7; hex 06000059a211ea; asc Y ;;
3: len 5; hex 48656c6c6f; asc Hello;;
*** WE ROLL BACK TRANSACTION (1)
Explanation
As a_horse_with_no_name mentioned, this seems like a bug in MySQL. Transaction (2) wants to obtain a gap lock on the same row it already holds an X lock. Transaction (1) waits for a non-gap X lock on this row. It is not clear to me why this requests should conflict. Setting the isolation level to READ COMMITTED disables gap locking. Since the example works then, this is a hint that gap locking is indeed the problem here.