2013-12-23 2 views
1

я отслеживать пользователей кредитов в таблице creditlog, которая выглядит следующим образом:Mysql одновременно SELECT/INSERT

user quantity action balance 
1001  20  SEND 550 
1001  30  SEND 520 
1001  5  SEND 515 

Теперь я сначала попытался использовать активный синтаксис записи и выберите последний баланс затем вставить новую строку, вычислил новый баланс. Потом я оказался в состоянии гонки:

user quantity action balance 
1001  20  SEND 550 
1001  30  SEND 520 
1001  5  SEND 545 (the latest balance was not picked up because of a race condition) 

Следующая раствор с помощью одного запроса, чтобы сделать так:

INSERT INTO creditlog (action, quantity, balance, memberId) 
VALUES (:action, :quantity, (SELECT tc.balance from creditlog tc where tc.memberId=:memberId ORDER by tc.id desc limit 1) - :quantity, :memberId); 

Мой скрипт, который проверяет это с 10 Reqs/второй будет бросить следующее сообщение об ошибке для 2/10 запросов:

SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction. The SQL statement executed was: 
INSERT INTO creditlog (action, quantity, reference, balance, memberId)  
VALUES (:action, :quantity, :reference, (SELECT balance from (SELECT tm.* FROM creditlog tm where tm.memberId=:memberId) tc where tc.memberId=:memberId ORDER by tc.id desc limit 1) -:quantity, :memberId, :recipientId);. 
Bound with :action='send', :quantity='10', :reference='Testing:10', :memberId='10001043'. 

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

Имеет ли моя проблема: How to avoid mysql 'Deadlock found when trying to get lock; try restarting transaction'?

Как я могу избежать этой ситуации и превращать параллельные запросы в последовательные операции?

+0

ли вы действительно использовать этот запрос: '(SELECT tc.balance из creditlog ца где tc.memberId =: MemberID ORDER по tc.id предела по убыванию 1)'? В журнале показан другой запрос: '(SELECT balance from (SELECT tm. * FROM creditlog tm, где tm.memberId =: memberId) tc, где tc.memberId =: memberId ORDER по tc.id desc limit 1) -: quantity'. – krokodilko

+0

Я снял некоторые неуместные столбцы. – Samson

ответ

0

Решение # 2

Поскольку я уже использовал Redis, я опробовал обертку для мутетек redis: https://github.com/phpnode/YiiRedis/blob/master/ARedisMutex.php

Это дает мне большую гибкость, позволяя мне блокировать только определенные сегменты (пользователей) таблицы. Также нет никакого риска блокировки, поскольку мьютекс автоматически истекает после X (настраиваемых) секунд.

Вот окончательный вариант:

$this->mutex = new ARedisMutex("balance:lock:".$this->memberId); 
$this->mutex->block(); 

//execute credit transactions for this user 

$this->mutex->unlock(); 
0

Это рабочее решение, возможно, не лучшее, поэтому, пожалуйста, помогите улучшить.

Поскольку транзакции не блокируют другие сеансы от SQL сортирует, я использовал следующий подход:

LOCK TABLES creditlog WRITE; 
//query 1 extracts oldBalance 
"SELECT balance FROM creditlog where memberId=:memberId ORDER BY ID DESC LIMIT 1;"; 
//do my thing with the balance (checks and whatever) 

//query 2 
"INSERT INTO creditlog (action, quantity, balance, memberId) 
VALUES (:action, :quantity, oldBalance- :quantity, :memberId); 
UNLOCK TABLES; 

Результат:

mysql> select * from creditlog order by id desc limit 40; 
+--------+-----------+----------+---------+----------+---------+---------------------+-------------+------------+ 
| id  | memberId | action | quantity | balance | timeAdded | 
+--------+-----------+----------+---------+----------+---------+---------------------+-------------+------------+ 

| 772449 | 10001043 | send | 10.00 | 0.00 | 2013-12-23 16:21:50 | 
| 772448 | 10001043 | send | 10.00 | 10.00 | 2013-12-23 16:21:50 | 
| 772447 | 10001043 | send | 10.00 | 20.00 | 2013-12-23 16:21:50 | 
| 772446 | 10001043 | send | 10.00 | 30.00 | 2013-12-23 16:21:50 | 
| 772445 | 10001043 | send | 10.00 | 40.00 | 2013-12-23 16:21:50 | 
Смежные вопросы