Как получить deadlock определённого типа?

178
31 марта 2017, 23:20

Мне нужно получить (сымитировать) deadlock определённого типа:

  1. У транзакции №1 изначально есть одна блокировка типа X (exclusive).
  2. Транзакция №2 пытается получить блокировку типа S (shared) и начинает ждать, когда Транзакция №1 освободит свою блокировку;
  3. Транзакция №1 пытается получить блокировку типа X (на другой набор записей) и начинает ждать, когда Транзакция №2 получит S-блокировку и освободит её.

У меня получается получить dealock, когда одновременно в двух потоках выполняются следующие транзакции:

-- Транзакция 1
BEGIN;
SELECT * FROM `testlock` WHERE id=1 LOCK IN SHARE MODE; /* GET S LOCK */
SELECT SLEEP(5);
SELECT * FROM `testlock` WHERE id=1 FOR UPDATE; /* TRY TO GET X LOCK */
COMMIT;
-- Транзакция 2
BEGIN;
SELECT * FROM `testlock` WHERE id=1 FOR UPDATE; /* TRY TO GET X LOCK - DEADLOCK AND ROLLBACK HERE */
COMMIT;

Но тогда получается, что это дэдлок другого типа - изначально у первой транзакции блокировка S и т.д.

Я пыталась изменить транзакции, например, переписать их следующим образом:

-- Транзакция 1
BEGIN;
SELECT * FROM `testlock` WHERE id=1 FOR UPDATE; /* GET X LOCK */
SELECT SLEEP(5);
SELECT * FROM `testlock` WHERE id=3 FOR UPDATE; /* TRY TO GET X LOCK */
COMMIT;
-- Транзакция 2
BEGIN;
SELECT * FROM `testlock` WHERE id > 2 LOCK IN SHARE MODE; /* TRY TO GET S LOCK*/
COMMIT;

Также я меняла поле id на поле без индекса, и во всех запросах изменяла условие на WHERE id = 1, но всё равно, в этих случаях я deadlock'а не получаю, обе транзакции выполняются.

Каким образом можно получить именно описанный выше deadlock?

Answer 1

LOCK IN SHARE MODE не запрещает читать. Потому второй SELECT * FROM testlock WHERE id=3 FOR UPDATE; выполняется нормально.

Проделайте следующее - откройте три консоли, чтобы видеть их одновременно. Выполните:

подготовка

create table testlock (id int, val int);
insert into testlock (id,val)
select 1,1 union all
select 2,2 union all
select 3,3 union all
select 4,4 ;

консоль 1

start transaction;
SELECT * FROM `testlock` WHERE id=1 FOR UPDATE;

консоль 2

start transaction;
SELECT * FROM `testlock` WHERE id > 2 LOCK IN SHARE MODE;

консоль 1

SELECT * FROM `testlock` WHERE id=3 FOR UPDATE;

консоль 3

update testlock set val=val+1 where id=3;

консоль 1

commit;

консоль 2

commit;
Answer 2

У меня получилось повторить deadlock именно такого же типа.

Для этого создаётся таблица:

CREATE TABLE ad_data(
   DAY DATE NOT NULL,
   ad_id INT NOT NULL,
   CLIENT INT NOT NULL,
   clicks INT NOT NULL,
   cost INT NOT NULL,
   PRIMARY KEY(DAY, ad_id)
) ENGINE=INNODB;
INSERT INTO ad_data(DAY, ad_id, CLIENT, clicks, cost)
   VALUES
   ('2006-08-01', 1, 1, 10, 100),
   ('2006-08-01', 2, 1, 20, 200),
   ('2006-08-01', 3, 1, 30, 300),
   ('2006-08-01', 4, 1, 40, 400),
   ('2006-08-01', 6, 1, 60, 600);

И в 1-ом соединении выполняются запросы:

START TRANSACTION;
INSERT INTO ad_data(DAY, ad_id, CLIENT, clicks, cost)
   VALUES
   ('2006-08-01', 7, 1, 70, 700);

Далее во 2-ом соединении выполняются запросы:

START TRANSACTION;
DROP TABLE IF EXISTS cost;
create temporary table cost as
select * from ad_data
where day = '2006-08-01';

После этого 2-ое соединении блокируется (в ожидании своей S-блокировки). И в завершении в 1-ом соединении выполняем запрос:

INSERT INTO ad_data(DAY, ad_id, CLIENT, clicks, cost)
   VALUES
   ('2006-08-01', 5, 1, 50, 500);

Получаем дэдлок:

Error Code: 1213

Deadlock found when trying to get lock; try restarting transaction

После вызова команды SHOW ENGINE INNODB STATUS; можно увидеть, что тип deadlock'a именно тот, который был нужен:

------------------------
LATEST DETECTED DEADLOCK
------------------------
170329 18:05:28
*** (1) TRANSACTION:
TRANSACTION 0 187654012, ACTIVE 2 sec, OS thread id 2304 fetching ROWS
mysql TABLES IN USE 2, locked 2
LOCK WAIT 4 LOCK struct(s), HEAP size 320, 7 ROW LOCK(s), UNDO LOG entries 5
MySQL thread id 1180, QUERY id 1984999 127.0.0.1 USER Sending DATA
CREATE TEMPORARY TABLE cost AS
SELECT * FROM ad_data
WHERE DAY = '2006-08-01'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 0 page NO 196086 n bits 80 INDEX `PRIMARY` of TABLE `db`.`ad_data` 
trx id 0 187654012 LOCK MODE S waiting
Record LOCK, HEAP NO 7 PHYSICAL RECORD: n_fields 7; ...
*** (2) TRANSACTION:
TRANSACTION 0 187654007, ACTIVE 6 sec, OS thread id 488 inserting, thread declared inside INNODB 500
mysql TABLES IN USE 1, locked 1
4 LOCK struct(s), HEAP size 320, 4 ROW LOCK(s), UNDO LOG entries 1
MySQL thread id 1179, QUERY id 1985000 ip USER UPDATE
INSERT INTO ad_data(DAY, ad_id, CLIENT, clicks, cost)
   VALUES
   ('2006-08-01', 5, 1, 50, 500)
*** (2) HOLDS THE LOCK(S): 
RECORD LOCKS SPACE id 0 page NO 196086 n bits 80 INDEX `PRIMARY` of TABLE `db`.`ad_data` 
trx id 0 187654007 lock_mode X LOCKS rec but NOT gap
Record LOCK, HEAP NO 7 PHYSICAL RECORD: n_fields 7; ...
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 0 page NO 196086 n bits 80 INDEX `PRIMARY` of TABLE `db`.`ad_data` 
trx id 0 187654007 lock_mode X LOCKS rec but NOT gap waiting
Record LOCK, HEAP NO 8 PHYSICAL RECORD: n_fields 7; ...
*** WE ROLL BACK TRANSACTION (2)
------------

То есть:

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
... LOCK MODE S waiting
*** (2) HOLDS THE LOCK(S): 
... lock_mode X LOCKS rec but NOT gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
...lock_mode X LOCKS rec but NOT gap waiting
READ ALSO
Что именно пытается сделать данный код?

Что именно пытается сделать данный код?

В логах ошибок появилось очень много строк с sql запросом - просто не сомневаюсь что пытаются навредить

267
Помогите составить SQL запрос

Помогите составить SQL запрос

Есть таблица с полями int А и int BМне нужно определить что в таблице есть записи где в одной записи A>0 и B<9

218
PropertyNotFoundException в чем может быть причина?

PropertyNotFoundException в чем может быть причина?

С помощью ManagedBean произвожу CRUD операции, при этом JSF кидает такую ошибку

270