We got a puzzled deadlock on MySQL 5.7 (Engine: InnoDB, Isolation Level: RR). The report result of show engine innodb status as below shown
*** (1) TRANSACTION:
TRANSACTION 1739954050, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 4 row lock(s)
MySQL thread id 4253877, OS thread handle 47904135608064, query id 4259685238 jacky Searching rows for update
UPDATE fruit_setting set
value = CASE
WHEN eid = 'L1XSHY' and `key` = 'priority' THEN '14343'
WHEN eid = 'Rtb95t' and `key` = 'priority' THEN '14344'
WHEN eid = 'wdsNwr' and `key` = 'priority' THEN '14345'
WHEN eid = 'K1Ikqy' and `key` = 'priority' THEN '14345'
WHEN eid =
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 533 page no 65378 n bits 0 index PRIMARY of table `jacky`.`fruit_setting` trx id 1739954050 lock_mode X locks rec but not gap waiting
...
*** (2) TRANSACTION:
TRANSACTION 1739954049, ACTIVE 0 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 94 lock struct(s), heap size 1136, 184 row lock(s)
MySQL thread id 4257460, OS thread handle 47904340621056, query id 4259685231 jacky Searching rows for update
UPDATE fruit_setting set value = CASE
WHEN eid = 'M5ecaA' and `key` = 'priority' THEN '16171'
WHEN eid = '1o0bdT' and `key` = 'priority' THEN '16172'
WHEN eid = 'XNxx5S' and `key` = 'priority' THEN '16173'
WHEN eid = '1o0bdT' and `key` = 'priority' THEN '16174'
WHEN eid =
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 533 page no 65378 n bits 0 index PRIMARY of table `jacky`.`fruit_setting` trx id 1739954049 lock_mode X locks rec but not gap
Record lock, heap no 105 PHYSICAL RECORD: n_fields 10; compact format; info bits 0
...
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 533 page no 46944 n bits 0 index PRIMARY of table `jacky`.`fruit_setting` trx id 1739954049 lock_mode X locks rec but not gap waiting
Record lock, heap no 58 PHYSICAL RECORD: n_fields 10; compact format; info bits 0
...
The sqls in above report has been truncated (might due to size limit of MySQL), one of the whole sql looks like (we only record the prepared statement)
UPDATE fruit_setting set value = CASE
WHEN eid = ? and `key` = ? THEN ?
WHEN eid = ? and `key` = ? THEN ?
WHEN eid = ? and `key` = ? THEN ?
WHEN eid = ? and `key` = ? THEN ?
WHEN eid = ? and `key` = ? THEN ?
WHEN eid = ? and `key` = ? THEN ?
WHEN eid = ? and `key` = ? THEN ?
WHEN eid = ? and `key` = ? THEN ?
WHEN eid = ? and `key` = ? THEN ?
END
WHERE aid = ? and eid in (?, ?, ?, ?, ?, ?, ?, ?, ?) and `key` = ?
The table DDL
CREATE TABLE `fruit_setting` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`aid` varchar(32) NOT NULL,
`eid` varchar(32) NOT NULL,
`key` varchar(32) NOT NULL,
`value` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `i_eid_key` (`eid`, `key`),
KEY `i_aid_key` (`aid`, `key`),
KEY `i_aid_eid` (`aid`, `eid`)
);
Note that the aid and key in two sqls are the same and the eid in in clause could be overlapped; And thus we guess that the deadlock occurred since the locks are acquired in reverse order on index i_eid_key or i_aid_eid.
Question 1: why the lock is not waiting/hold on secondary index i_eid_key or i_aid_eid, but on primary index? AFAIK, the secondary index would be locked before primary index if the searching for update used the secondary index.
If a secondary index is used in a search and index record locks to be set are exclusive, InnoDB also retrieves the corresponding clustered index records and sets locks on them.
https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html
Question 2: Does MySQL acquire lock on index record one by one based on the order of eid appeared in in clause during execute update ... where eid in (...)?
By the way, the deadlock is hard to reproduce, I have tried to locked on eid in different order, however, the new deadlock report showed that the lock was on the secondary index.
For Question 1:
After some discussion and thought, we think that the deadlock might occurred like this: There are at least two same extension_ids in two sqls.
InnoDB first locks on two different secondary indexes, such as i_eid_key i_aid_key
and then lock the PK record, which is in the reverse order, and finally result in the deadlock.
For Question 2:
The MySQL optimization would sort the (constant) values in in clause
Related
In an attempt to overcome deadlocks on the combat server, I reached a dead end. There are 2 tables:
First:
create table table_1
(
id int auto_increment
primary key,
data1 text null,
data2 text null
);
Second:
create table table_2
(
id int auto_increment
primary key,
t1_id int null,
data1 text null,
data2 text null,
constraint table_2_table_1_id_fk
foreign key (t1_id) references table_1 (id)
on update cascade on delete cascade
);
For tests, there are 30 records in table_1, 60 in table_2 (every 2 records from table_2 refer by key to 1 record in table_1).
Next, a simple php script that updates some records from table_1, by condition from table_2 in an infinite loop:
<?php
$db = new PDO("mysql:host=127.0.0.1;dbname=test_db;", 'debian-sys-maint', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'",
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$random = rand(0, 9);
while (true) {
$db->exec("
UPDATE table_1
INNER JOIN table_2 on table_1.id = table_2.t1_id and table_2.data2 like '%$random%'
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1;");
}
So, I run this PHP script in 150 instances, and a Deadlock error is generated. I tried to fix it by modifying the query as follows:
UPDATE table_1
INNER JOIN (SELECT t1_id FROM table_2 WHERE table_2.data2 like '%$random%' ORDER BY t1_id) table_2 on table_1.id = table_2.t1_id
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1;
WITH tmp (id) AS (SELECT t1_id FROM table_2 WHERE table_2.data2 like '%$random%' ORDER BY t1_id)
UPDATE table_1
INNER JOIN tmp on table_1.id = tmp.id
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1;
UPDATE table_1
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1
WHERE table_1.id in (SELECT t1_id FROM table_2 WHERE table_2.data2 like '%$random%' ORDER BY t1_id);
UPDATE table_1, (SELECT t1_id FROM table_2 WHERE table_2.data2 like '%$random%' ORDER BY t1_id) tmp1
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1
WHERE table_1.id = tmp1.t1_id;
Actually the first question: I don't understand where the deadlock comes from, if the records are always sorted in the same order, the race condition is excluded. This is what the SHOW ENGINE INNODB STATUS; returns when catching a deadlock:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-05-10 01:56:01 140233769219840
*** (1) TRANSACTION:
TRANSACTION 3529589, ACTIVE 1 sec starting index read
mysql tables in use 2, locked 2
LOCK WAIT 7 lock struct(s), heap size 1128, 90 row lock(s)
MySQL thread id 20, OS thread handle 140233708357376, query id 173 localhost 127.0.0.1 debian-sys-maint executing
UPDATE table_1
INNER JOIN table_2 on table_1.id = table_2.t1_id and table_2.data2 like '%9%'
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 2 page no 4 n bits 224 index PRIMARY of table `test_db`.`table_1` trx id 3529589 lock_mode X locks rec but not gap
.....................
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2 page no 4 n bits 224 index PRIMARY of table `test_db`.`table_1` trx id 3529589 lock_mode X locks rec but not gap waiting
.....................
*** (2) TRANSACTION:
TRANSACTION 3529887, ACTIVE 0 sec starting index read
mysql tables in use 2, locked 2
LOCK WAIT 5 lock struct(s), heap size 1128, 52 row lock(s)
MySQL thread id 118, OS thread handle 140229428811520, query id 444 localhost 127.0.0.1 debian-sys-maint executing
UPDATE table_1
INNER JOIN table_2 on table_1.id = table_2.t1_id and table_2.data2 like '%7%'
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 2 page no 4 n bits 224 index PRIMARY of table `test_db`.`table_1` trx id 3529887 lock_mode X locks rec but not gap
.....................
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2 page no 4 n bits 224 index PRIMARY of table `test_db`.`table_1` trx id 3529887 lock_mode X locks rec but not gap waiting
.....................
*** WE ROLL BACK TRANSACTION (2)
The only way I managed to solve the deadlock was to create a temporary table that would contain a ready-made set of keys that would be affected by UPDATE immediately from table_1:
DROP TABLE IF EXISTS tmp1;
CREATE TEMPORARY TABLE tmp1
SELECT table_2.t1_id FROM table_2 WHERE table_2.data2 like '%$random%'
UPDATE table_1
INNER JOIN tmp1 on table_1.id = tmp1.t1_id
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1;
Or with the same success I can create a copy of table_2 in a temporary table, it also does not cause deadlocks:
DROP TABLE IF EXISTS tmp1;
CREATE TEMPORARY TABLE tmp1
SELECT * FROM table_2 WHERE 1;
UPDATE table_1
INNER JOIN tmp1 on table_1.id = tmp1.t1_id and tmp1.data2 like '%$random%'
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1;
The second question is: Why does this option not cause deadlocks? As I understand it, it is the records from table_1 that are blocked, in this case they are blocked in the same way, only the search does not take place according to table_2, but according to the temporary table tmp1.
It would seem that my problem is solved, deadlocks are not called, but I did not like the solution with a temporary table and I continued my tests during which I came across a very strange thing that finally drove me to a dead end. If you start and complete the transaction yourself, then deadlocks do not appear:
BEGIN;
UPDATE table_1
INNER JOIN table_2 on table_1.id = table_2.t1_id and table_2.data2 like '%$random%'
SET table_1.data1 = table_1.data1 + 1,
table_1.data2 = table_1.data2 + 1;
COMMIT;
This is probably the last and most exciting question for me... I have an autocommit enabled, why if I don't explicitly start and complete the transaction, then deadlocks come out? PHP is known to work in one thread, for 1 script there is exactly 1 connection to the database, all 150 scripts work in parallel
I think this is because of the isolation level. The default is REPEATABLE READ and the second clause maybe the problem.
For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it.
For other search conditions, InnoDB locks the index range scanned, using gap locks or next-key locks to block insertions by other sessions into the gaps covered by the range. For information about gap locks and next-key locks, see Section 15.7.1, “InnoDB Locking”.
From dev.mysql
So, try SERIALIZABLE
This level is like REPEATABLE READ, but InnoDB implicitly converts all plain SELECT statements to SELECT ... FOR SHARE if autocommit is disabled. If autocommit is enabled, the SELECT is its own transaction. It therefore is known to be read only and can be serialized if performed as a consistent (nonlocking) read and need not block for other transactions. (To force a plain SELECT to block if other transactions have modified the selected rows, disable autocommit.)
I think it is simply that
table_2.data2 like '%$random%'
has a leading wildcard, thereby necessitating a full table scan. In doing so, it either locks too many rows, or bumps into the other thread.
Note: "90 row lock(s)"
Can the LIKE be improved and provide a suitable INDEX? Perhaps FULLTEXT would be useful here.
I faced multiple locking issues with ROLLBACK TO SAVEPOINT command
and i think there's a next key lock happen here by mistake
mysql version: 8.0.31
my transaction type: REPEATABLE READ
Suppose i have this table with some dats:
CREATE TABLE book (
id int NOT NULL,
name varchar(64) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
INSERT INTO book (id, name) VALUES (700, 'test_book_700');
INSERT INTO book (id, name) VALUES (750, 'test_book_750');
INSERT INTO book (id, name) VALUES (800, 'test_book_800');
and then i have two connections to mysql server:
Connection #1:
START TRANSACTION;
SELECT * FROM book WHERE name = 'test_book_700';
SAVEPOINT sa_savepoint_1;
INSERT INTO book (id, name) VALUES (900, 'test_book_900');
ROLLBACK TO SAVEPOINT sa_savepoint_1;
=> x_lock will happen for the id > 800 (i don't now why )
(still without final commit or rollback to the main transaction)
Connection #2:
START TRANSACTION;
INSERT INTO book (id, name)
VALUES (950, 'test_book_950');
=> will wait to the x lock release or it will return lock wait timeout
if i use RELEASE SAVEPOINT instead ROLLBACK TO SAVEPOINT the x lock will not happen, very weird behavior, no ?
is there a wrong behavior when use ROLLBACK TO SAVEPOINT ?? why the next key lock happen in this case ?
and this the TRANSACTION output from the SHOW ENGINE INNODB STATUS that have relevant to this issue
INSERT INTO book (id, name)
VALUES (950, 'test_book_950')
------- TRX HAS BEEN WAITING 17 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 383 page no 4 n bits 72 index PRIMARY of table `test`.`book` trx id 286322 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;;
I'm working with a legacy table which I cannot change. It looks similar to:
CREATE TABLE foo
(
Id int IDENTITY(1,1) not null,
OwnerId int not null,
OwnerRecordId int not null,
SomeColumn varchra(50) not null,
CONSTRAINT ix_foo_OwnerId (OwnerId)
-- Ideally the following constraint would exist, but it doesn't. It is enforced
-- with code alone. There are currently duplicates, which should not
-- not exist, but they prevent creation of the unique index.
--CONSTRAINT ux_foo UNIQUE (OwnerId, OwnerRecordId)
)
OwnerRecordId is similar to an identity column within each OwnerId:
Id
OwnerId
OwnerRecordId
1
16
1
2
16
2
3
16
3
4
57
1
Now I would like to copy all records from ownerId 16 to ownerId 57. So OwnerId 57 would end up with 3 new records, and their OwnerRecordId would range from 2 - 4.
While this copying is taking places, other processes might be creating new records.
I thought about doing the following, but the sub-query seems slow:
insert into foo (OwnerId, SomeColumn, OwnerRecordId)
select
(57, SomeColumn, (select isnull(max(OwnerRecordId), 0) + 1
from foo where ownerId = 57)
from foo
where OwnerId = 16
Then I thought I could lock the table where OwnerId = 57. If I could do this I could lock those records, get the current maximum, and then use ROW_NUMBER in my select and add that to the MAX value I grabbed once.
Only, I can't seem to prevent other users from selecting from the table short of a table lock. Is there a way to lock records where colun OwnerId = 57? Doing so would prevent others from geting the current max(OwnerRecordId) + 1 value.
Perhas there is a better approach?
Certainly the unique index should be added, I can't do that at this point though.
The following code should hopefully do the correct amount of locking
insert into foo WITH (SERIALIZABLE)
(OwnerId, SomeColumn, OwnerRecordId)
select
57,
SomeColumn,
(select isnull(max(OwnerRecordId), 0) + 1
from foo with (SERIALIZABLE, UPDLOCK)
where ownerId = 57)
from foo
where OwnerId = 16;
SERIALIZABLE (which is a synonym for HOLDLOCK) will cause a range lock over all the rows where ownerId = 57, and UPDLOCK will cause that lock to be held until the end of the transaction.
You need an index on (OwnerId) with OwnerRecordId as another key column or as an INCLUDE, otherwise the whole table will get locked.
Do not fall into the trap of using XLOCK, it doesn't work unless you are actually modifying that table reference.
You say you have many different IDs to copy, in which case it would be more performant to do it in bulk.
Dump the list into a temp table (or a Table Valued Parameter), then do a joined update. Something like this
CREATE TABLE #tmp (SourceId int, TargetId int, primary key (SourceId, TargetId))
-- insert using statements or BULK INSERT or SqlBulkCopy etc
insert into foo WITH (SERIALIZABLE)
(OwnerId, SomeColumn, OwnerRecordId)
select
t.TargetId,
f.SomeColumn,
ISNULL(f2.mx, 0) + ROW_NUMBER() OVER (PARTITION BY f.OwnerId ORDER BY f.OwnerRecordId)
FROM foo f
JOIN #tmp t ON t.SourceId = f.OwnerId
LEFT JOIN (
SELECT f2.OwnerId, mx = MAX(f2.OwnerRecordId)
FROM foo f2 WITH (SERIALIZABLE, UPDLOCK)
GROUP BY f2.OwnerId
) f2 ON f2.OwnerId = t.TargetId;
Is there a way in mysql that I can find the number of rows that get locked when a certain query runs? Eg. for a query, what is the number of rows locked:-
UPDATE xyz SET ARCHIVE = 1 , LAST_MODIFIED = CURRENT_TIMESTAMP WHERE ID = '123' AND ARCHIVE = 0;
Assume in this case, there is a index on ID and Archive is part of primary key.
BEGIN;
# lock
UPDATE xyz SET ARCHIVE = 1 , LAST_MODIFIED = CURRENT_TIMESTAMP WHERE ID = '123' AND ARCHIVE = 0;
# returns locked rows (X)
SELECT trx_rows_locked FROM information_schema.innodb_trx;
# release
COMMIT;
i have table Temp_load with Columns:
key bigint(19) UN PK
plane_key bigint(20) PK
locat_key bigint(20) PK
time_period_key bigint(19) UN PK
business_unit_key bigint(19) UN
curret_allocated tinyint(1)
value float
valid_ind int(11)
last_updated datetime
Above are the column which table Temp_load is containing in it.
i am trying to insert data into this table using below query
INSERT INTO <Schema_name>.`Temp_load`
(key,
plane_key,
locat_key,
time_period_key,
business_unit_key,
curret_allocated,
value)
(SELECT DISTINCT 1,
plane_key,
locat_key,
1,
CASE
WHEN current_area = 'HEALTH' THEN 1
WHEN current_area = 'BEAUTY/PERSONAL' THEN 3
WHEN current_area = 'GM' THEN 2
WHEN current_area = 'CONSUMABLES' THEN 4
end,
current_flag,
opt_metric_1
FROM staging.curves
WHERE opt_metric_1 IS NOT NULL
AND current_area IS NOT NULL);
The Source table is having 29 million records in it. The above insert statment is running for more than 5 hours and still running.
I am doing an insert of 29 Million in one go like this from the same table i need to do 3 times more insert on different column.
When i try to load using LOAD DATA INFILE it is throwing ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
i also tried increasing innodb_lock_wait_timeout to 120 but still we are facing the problem.
Also before loading i ave disabled below flags.
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
Do we have any other optimal solution for this? where the insert can be done in much faster way.
Thanks
The Problem might be that the PRIMARY KEY integrity is checked for each row to insert. You should remove the PK before you insert your data. In MariaDB you can disable some of These checks, but I dont know if it is possible for MySQL, too.
An alternative might be to UNION your Temp_load and staging.curves into another table:
CREATE TABLE myNewTable
SELECT ... FROM Temp_load
UNION
SELECT ... FROM staging.curves