У меня есть система php, которая позволяет клиентам покупать вещи (делать заказы) в нашей системе с помощью электронного кошелька (кредит магазина).
вот пример базы данных
**sales_order**
+--------+-------+----------+--------+--------------+-----------+
|order_id| price |product_id| status |already_refund|customer_id|
+--------+-------+----------+--------+--------------+-----------+
| 1 | 1000 | 1 |canceled| 1 | 2 |
| 2 | 2000 | 2 |pending | 0 | 2 |
| 3 | 3000 | 3 |complete| 0 | 1 |
+--------+-------+----------+--------+--------------+-----------+
**ewallet**
+-----------+-------+
|customer_id|balance|
+-----------+-------+
| 1 | 43200 |
| 2 | 22500 |
| 3 | 78400 |
+-----------+-------+
Таблица sales_order содержит заказ, который сделал клиент, а столбецready_refund - для флага, отменявшего заказ, который уже был возвращен.
Я запускаю cron каждые 5 минут, чтобы проверить, можно ли отменить заказ с ожидающим статусом, и после этого он может вернуть деньги на электронный кошелек клиента
function checkPendingOrders(){
$orders = $this->orderCollection->filter(['status'=>'pending']);
foreach($orders as $order){
//check if order is ready to be canceled
$isCanceled = $this->isCanceled($order->getId());
if($isCanceled === false) continue;
if($order->getAlreadyRefund() == '0'){ // check if already refund
$order->setAlredyRefund('1')->save();
$this->refund($order->getId()); //refund the money to customer ewallet
}
$order->setStatus('canceled')->save();
}
}
Проблема в том, что 2 разных расписания cron могут обрабатывать одни и те же данные в одно и то же время, используя эту функцию, и процесс возврата может быть вызван дважды, поэтому клиент получит двойную сумму возврата. Как я могу справиться с такой проблемой, когда одновременно работают две одинаковые функции для обработки одних и тех же данных? предложение if
, которое я сделал, не может справиться с такой проблемой
обновление
я попытался использовать microtime в сеансе в качестве проверки и заблокировать строку таблицы в MySQL, поэтому вначале я установил переменную, которая будет содержать microtime, чем когда я хранился в уникальном сеансе, сгенерированном order_id
, а затем я добавил условие для сопоставления значения microtime с сеансом перед блокировкой строки таблицы и обновлением моей таблицы электронных кошельков
function checkPendingOrders(){
$orders = $this->orderCollection->filter(['status'=>'pending']);
foreach($orders as $order){
//assign unique microtime to session
$mt = round(microtime(true) * 1000);
if(!isset($_SESSION['cancel'.$order->getId()])) $_SESSION['cancel'.$order->getId()] = $mt;
//check if order is ready to be canceled
$isCanceled = $this->isCanceled($order->getId());
if($isCanceled === false) continue;
if($order->getAlreadyRefund() == '0'){ // check if already refund
$order->setAlreadyRefund('1')->save();
//check if microtime is the same as the first one that running
if($_SESSION['cancel'.$order->getId()] == $mt){
//update using lock row
$this->_dbConnection->beginTransaction();
$sqlRaws[] = "SELECT * FROM ewallet WHERE customer_id = ".$order->getCustomerId()." FOR UPDATE;";
$sqlRaws[] = "UPDATE ewallet SET balance =(balance+".$order->getPrice().") WHERE customer_id = ".$order->getCustomerId().";";
foreach ($sqlRaws as $sqlRaw) {
$this->_dbConnection->query($sqlRaw);
}
$this->_dbConnection->commit();
}
}
unset($_SESSION['cancel'.$order->getId()]);
$order->setStatus('canceled')->save();
}
}
но проблема все еще сохраняется, когда я выполняю тест strees, потому что есть случай, когда одна и та же функция обрабатывает одни и те же данные в одно и то же время и запускает транзакцию mysql в одно и то же время