powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / MySQL [игнор отключен] [закрыт для гостей] / Deadlock при добавлении строки
42 сообщений из 42, показаны все 2 страниц
Deadlock при добавлении строки
    #38511967
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Здравствуйте.

Есть такая задача: пополнение баланса пользователя.

По запросу на пополнение баланса выполняется простой алгоритм: Ищем в таблице балансов запись, которая принадлежит пользователю. Если у пользователя есть баланс, то такая запись присутствует, если баланса нет - записи тоже нет.
Если приходит одновременно несколько запросов на пополнение баланса одному юзеру, у которого нет записи (нулевой баланс), то случается deadlock.

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
START TRANSACTION;
SELECT * FROM `balances` WHERE id=1 FOR UPDATE;
...
// если существует запись, делается UPDATE - с этим проблем нет, т.к. существующая запись блокируется SELECT-ом в начале транзакции
UPDATE `balances` SET `balance` = `balance`+10 WHERE `id` = 1;

// если записи нет,  делается INSERT - вот тут появляется deadlock, если до этого момента началась вторая транзакция, т.к. SELECT в начале транзакции ничего не заблокировал
INSERT INTO `balances` (`id`, `balance`) VALUES (1, 10.00);

COMMMIT;



id - не является автоинкрементом, на самом деле это USER_ID

Пробовал INSERT ... ON ON DUPLICATE KEY UPDATE - также не решает проблемы.

Как решить проблему?
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512036
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000,

Дай определение таблицы balances.
В частности, интересно какие там индексы и какой движок используется. (storage engine).
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512039
tanglir
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000
Код: sql
1.
если существует запись, делается UPDATE - с этим проблем нет, т.к. существующая запись блокируется SELECT-ом в начале транзакции

Решение напрашивается само собой - сразу создавать для нового пользователя запись с нулевым балансом.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512077
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
MasterZiv,
Код: sql
1.
2.
3.
4.
5.
CREATE TABLE IF NOT EXISTS `balances` (
  `id` int(1) NOT NULL,
  `balance` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512080
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
tanglirРешение напрашивается само собой - сразу создавать для нового пользователя запись с нулевым балансом.
Не хотелось бы... юзеров будет очень много, поэтому решили вынести баланс в отдельную таблицу, чтобы работа с балансом была максимально быстрой
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512084
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000MasterZiv,
Код: sql
1.
2.
3.
4.
5.
CREATE TABLE IF NOT EXISTS `balances` (
  `id` int(1) NOT NULL,
  `balance` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;




Так просто ? Два поля ? Не верю...
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512085
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000tanglirРешение напрашивается само собой - сразу создавать для нового пользователя запись с нулевым балансом.
Не хотелось бы... юзеров будет очень много, поэтому решили вынести баланс в отдельную таблицу, чтобы работа с балансом была максимально быстрой

Это дурость.

Не в смысле вынесения, а в смысле надежды, что станет быстрее.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512091
tanglir
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
MasterZivТак просто ? Два поля ? Не верю...Вытащили 2 поля из основной таблицы юзеров в отдельную. Видимо, во многих полях в исходной таблице был нулл/0, и надеялись, что за счёт отсутствия их в новой таблице работа ускорится. Но
MasterZivЭто дурость.

Не в смысле вынесения, а в смысле надежды, что станет быстрее.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512100
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
тут еще интереснее стало...
даже если приходят запрос на пополнение баланса от разных юзеров. все равно вываливается deadlock

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
transaction 1> start transaction;
Query OK, 0 rows affected (0.00 sec)

transaction 1> select * from balances where id=5 for update;
Empty set (0.00 sec)


transaction 2> start transaction;
Query OK, 0 rows affected (0.00 sec)

transaction 2> select * from balances where id=10 for update;
Empty set (0.01 sec)

transaction 2> insert into balances (id, balance) values (10,10);
// висит ...

transaction 1> insert into balances (id, balance) values (5,5);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

// вторая транзакция после делока первой, выполнилась
Query OK, 1 row affected (9.14 sec)



Мне кажется, это связано с блокировкой следующего ключа, но пока я плаваю в этой теме... В эту сторону копать?
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512104
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
MasterZiv,
ну это просто для того чтоб легче примеры читались.
ситуация воспроизводится при такой структуре.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512106
miksoft
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
tanglirсразу создавать для нового пользователя запись с нулевым балансом.+1
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512115
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Давайте рассмотрим эту проблему не с точки зрения архитектуры, а с точки зрения решения самой проблемы.
Структура таблицы есть. Воспроизвести проблему на локале - дело 1 минуты, достаточно открыть 2 терминала.
Deadlock есть. Как от него избавится?
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512122
Фотография Akina
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ну или ужесточите уровень изоляции.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512135
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Akina,
вот кстати копаю в этом направлении ))
только не ужесточить, а наоборот понизить помогло.

Код: sql
1.
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;



тогда такой запрос работает как надо.

Код: sql
1.
insert into balances (id, balance) values (1,10) ON DUPLICATE KEY UPDATE balance = balance+10;



но это еще только первые вести с полей... возможно будут побочные эффекты
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512188
Фотография Akina
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А был какой?
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512206
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000тут еще интереснее стало...
даже если приходят запрос на пополнение баланса от разных юзеров. все равно вываливается deadlock

Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
transaction 1> start transaction;
Query OK, 0 rows affected (0.00 sec)

transaction 1> select * from balances where id=5 for update;
Empty set (0.00 sec)


transaction 2> start transaction;
Query OK, 0 rows affected (0.00 sec)

transaction 2> select * from balances where id=10 for update;
Empty set (0.01 sec)

transaction 2> insert into balances (id, balance) values (10,10);
// висит ...

transaction 1> insert into balances (id, balance) values (5,5);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

// вторая транзакция после делока первой, выполнилась
Query OK, 1 row affected (9.14 sec)



Мне кажется, это связано с блокировкой следующего ключа, но пока я плаваю в этой теме... В эту сторону копать?


Странно, такого вроде бы быть не должно...
А точно между ними дедлок ?
попробуй только одну сессию пустить ...
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512207
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
AkinaНу или ужесточите уровень изоляции.

Куда уж жёсче ...
Некуда ужесточать...
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512213
Фотография MasterZiv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Можно попробовать не делать SELECT... FOR UPDATE вообще,
делать UPDATE, что-то про UPDATE-илось -- ок, хорошо, нет -- вставлять.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512215
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
AkinaА был какой?
был по дефолту REPEATABLE-READ
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512218
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
MasterZivпопробуй только одну сессию пустить ...

по отдельности отрабатывают нормально
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512229
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
MasterZivМожно попробовать не делать SELECT... FOR UPDATE вообще,
делать UPDATE, что-то про UPDATE-илось -- ок, хорошо, нет -- вставлять.

Без FOR UPDATE нельзя.
Это редкий случай - когда пришел запрос, а баланса у юзера нету.
Чаще происходит ситуации, когда у юзера есть баланс.

Если в начале транзакции будет просто SELECT ... WHERE id=1, а в конце UPDATE ... SET balance = balance+10 WHERE id=1
То при паралельных запросах один из них потеряется
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512231
Фотография ScareCrow
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
insert on duplicate key update
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512234
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
ScareCrowinsert on duplicate key update
а еще можно прочесть все сообщения в теме.
уже обсуждалось....
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512240
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
ScareCrowinsert on duplicate key update
На самом деле это пока наилучший вариант, но требуется понизить уровень изоляции.
Это пока смущает...
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38512249
tanglir
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
MasterZivСтранно, такого вроде бы быть не должно...
А точно между ними дедлок ?я ради интереса на пустой таблице (создал по ddl ТСа) попробовал - воспроизводится
на пустой! таблице
что оно там ухитряется залочить?..
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513081
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Вобщем сделал понижение уровня изоляции транзакции до READ COMMITTED с использованием INSERT ON DUPLICATE KEY UPDATE.
И вот как чувствовал, что что-то не то...

На локале все потестил, все нормально, со всех ракурсов.
На бой вынес и повалились ошибки

Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engi ne limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED.
Так что проблема все еще актуальна...
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513364
Arhat109
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000,

как-то странно у вас. Огласите полный набор действий от начала и до конца транзакции, которые приводят к блокировкам в случае insert ... on duplicate...
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513403
tanglir
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Arhat109Огласите полный набор действий от начала и до конца транзакции, которые приводят к блокировкам в случае insert ... on duplicate...
Я так проверял
Oleg8000
Код: sql
1.
2.
3.
4.
5.
CREATE TABLE IF NOT EXISTS `balances` (
  `id` int(1) NOT NULL,
  `balance` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Потом на пустой таблице
Oleg8000
Код: sql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
transaction 1> start transaction;
Query OK, 0 rows affected (0.00 sec)

transaction 1> select * from balances where id=5 for update;
Empty set (0.00 sec)


transaction 2> start transaction;
Query OK, 0 rows affected (0.00 sec)

transaction 2> select * from balances where id=10 for update;
Empty set (0.01 sec)

transaction 2> insert into balances (id, balance) values (10,10);
// висит ...

transaction 1> insert into balances (id, balance) values (5,5);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

// вторая транзакция после делока первой, выполнилась
Query OK, 1 row affected (9.14 sec)

результат аналогичен - дедлок.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513430
Фотография ScareCrow
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513435
Фотография ScareCrow
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
просто отсортируй инсерты по ID
сначала 5 потом 10
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513438
Фотография ScareCrow
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513561
Arhat109
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
tanglir,

А теперь попробуйте мне внятно объяснить зачем ПЕРЕД insert делаете блокировку через select for update?

Ссылки, привели верные. Ежели табличка пуста, то первая сессия своим селектом сразу же захватывает gap-ом всю табличку, и следующая сессия своим селектом делает тоже самое, что и приводит к нормальному дедлоку.

Я понимаю, когда делают для последующего update - лочить конкретный набор строк, дабы по ним не пролетел ещё кто-то. А вот для инсерта, да ещё и с дубликатным апдейтом - нифига не понял. Потому что:

1. Если инсерт втыкает новую запись - он это сделает.
2. Если НЕ втыкает, то пройдет апдейт ровно одной записи.

Насколько понимаю, втыкаем по одной записи... (впрочем он тут и для апдейта нафиг не нужен).
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513571
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Arhat109,

select for update нужен для того чтобы заблочить запись, если таковая имеется, что чаще всего и случается.
а вот если ее нет, тогда нужно добавить новую строку
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513572
Arhat109
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000,

(*facepalm*)... это понятно. А сможете внятно объяснить неоходимость именно предварительного лока в вашем конкретном случае? :)
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513579
Arhat109
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Arhat109,

там интересен совсем другой вопрос: каким способом второй селект для апдейта ваще проходит?
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513601
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Arhat109,

Селектом в начале транзакции, мы ищем баланс юзера. Если баланс юзера есть, значит в таблице есть запись. Мы ее блочим, для того, чтобы проделав некоторые операции с балансом (например, сохранить значение баланса до и после операции. т.е. обычный лог), в конце транзакции увеличить этот самый баланс.

Ежели у юзера нет баланса, значит нет записи. Тогда добавляем запись, опять же логгируя значения баланса до и после.

Пока вижу 2 пути:
1) после первого select for update, если записи нет, то заканчивать транзакцию, сбрасывая тем самым gap-ы. А дальше извращаться с insert on duplicate key update (пока вижу проблему с логгированием)

2) для юзера сразу заводить запись с нулевым балансом. Это, кажется более правильный путь, но смущают размеры таблицы. Юзеров реально до хрена, политика партии допускает наличие у пользователя неограниченного кол-ва аккаунтов. Из этой таблицы делаются ежеминутные выборки с целью списания абонентской платы. Сейчас объем таблицы под 10 млн. записей + ежесуточный прирост в 50-100 тыс. Поэтому и было решено вести балансы только активных юзеров.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513603
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Arhat109Arhat109,

там интересен совсем другой вопрос: каким способом второй селект для апдейта ваще проходит?
Вы можете попробовать самостоятельно. tanglir чуть выше описал полную последовательность операций, которая приводит к дедлоку.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513604
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
ScareCrowпросто отсортируй инсерты по ID
сначала 5 потом 10
К сожалению, это невозможно, т.к. это фактически идентификаторы юзеров.
Да и если честно, я не представляю, как это можно сделать в разных потоках.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513606
Arhat109
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000,

Дык и попробовал. Тоже лочится. Вот и не понимаю "почто так". Пока объяснение тока одно: гапы - разные. Тока не понимаю "в чём"? Таблица-то пустая!
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513611
Arhat109
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000,

ну, во-первых: "операции с балансом". Если это "лог", то выборка значения "до" - осмыслена, а вот смысла в логировании "между" чтением и сохранением - собственно для чего нужна блокировка - уже нет.

Какая нафиг разница, когда физически запишется лог? У вас есть исходное значение на момент начала транзакции, есть конечное... оттого, что вы лог чирканете "задним числом" (после изменения значения) - плохо? А если "заранее"?

А если отдельно вставлять в лог предварительно пустую запись "заранее", а потом одним, многотабличным апдейтом одновременно менять баланс и запись лога? Никак?

Я к тому, что если в процессе транзакции нет логики, изменяющей поведение транзакции, то она скорее всего избыточна и вполне достаточно "встроенных" блокировок на каждый запрос. Как-то так.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513632
Oleg8000
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Arhat109ну, во-первых: "операции с балансом". Если это "лог", то выборка значения "до" - осмыслена, а вот смысла в логировании "между" чтением и сохранением - собственно для чего нужна блокировка - уже нет.

Какая нафиг разница, когда физически запишется лог? У вас есть исходное значение на момент начала транзакции, есть конечное... оттого, что вы лог чирканете "задним числом" (после изменения значения) - плохо? А если "заранее"?


Если мы читаем значение баланса в начале транзакции, и не будем блокировать запись, то к моменту записи в лог это значение может быть уже неактуально, т.к. другая транзакция также в этот же момент времени прочитала значение баланса "до" и изменила баланс. Поэтому, без блокировки записи баланса, в логе запросто могут быть такие записи, при 2-х одновременных запросах

user_id баланс до баланс после1 0 101 0 10

А в таблице баланса будет верное значение 20

Arhat109А если отдельно вставлять в лог предварительно пустую запись "заранее", а потом одним, многотабличным апдейтом одновременно менять баланс и запись лога? Никак?

Надо подумать, пока плохо понял смысл... голова уже плохо соображает )

Arhat109Я к тому, что если в процессе транзакции нет логики, изменяющей поведение транзакции, то она скорее всего избыточна и вполне достаточно "встроенных" блокировок на каждый запрос. Как-то так.
Логгирование - не единственная операция в транзакции. Есть и другие, которые как раз меняют логику, в зависимости от вида совершенного платежа.
...
Рейтинг: 0 / 0
Deadlock при добавлении строки
    #38513960
Arhat109
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Oleg8000,

В таком случае, надо смотреть почему первый select for update не блокирует запись для второго. Скорее всего это связано с разными gap, при отсутствии нужной записи. Тогда ваша мысля про "сначала проверять и если отсутствует - разблокировать", скорее всего верна.

Но, в целом, явно наблюдается "косяк", имхо. Если табличка пуста, то gap с 0 по 5 должен блокировать gap с 0 по 10 - всяко. Впрочем и наоборот тоже. То есть данная последовательность (у меня воспроизводится тоже, MySQL 5.1.47) - симптом неверного перехлеста gap при оценке построчной блокировки. Надо заглянуть в код, давно не лазил.
...
Рейтинг: 0 / 0
42 сообщений из 42, показаны все 2 страниц
Форумы / MySQL [игнор отключен] [закрыт для гостей] / Deadlock при добавлении строки
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]