Переиндексация цены вызывает взаимные блокировки БД при оформлении заказа

47

У меня возникла проблема, когда я считаю, что процесс повторной индексации цены продукта вызывает исключение тупиковой ситуации в процессе оформления заказа.

Я поймал это исключение в процессе оформления заказа:

Исключение преобразования заказа: SQLSTATE [40001]: ошибка сериализации: 1213 Обнаружена тупиковая ситуация при попытке получить блокировку; попробуйте перезапустить транзакцию

К сожалению, у меня нет полной трассировки стека из-за того, где было обнаружено исключение, но, проверяя состояние INNODB, я смог отследить тупик:

SELECT `si`.*, `p`.`type_id` FROM `cataloginventory_stock_item` AS `si` 
INNER JOIN `catalog_product_entity` AS `p` ON p.entity_id=si.product_id     
WHERE (stock_id=1) 
AND (product_id IN(47447, 56678)) FOR UPDATE

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 0 page no 329624 n bits 352 index 
`PRIMARY` of table `xxxx`.`catalog_product_entity` 

SQL запрашивающая блокировка таблицы в конечном итоге генерируется, Mage_CatalogInventory_Model_Stock::registerProductsSale()когда он пытается получить текущий инвентарный счет, чтобы уменьшить его.

В то время, когда возникла взаимоблокировка, выполнялся процесс переиндексации цены продукта, и я предполагаю, что на нем была блокировка чтения, catalog_product_entity tableкоторая вызвала взаимоблокировку. Если я правильно понимаю взаимоблокировку, любая блокировка чтения вызовет взаимоблокировку, но переиндексация цены продукта удерживает блокировку в течение справедливого времени, так как на сайте около 50 000 товаров.

К сожалению, к этому моменту в потоке кода проверки была снята оплата с кредитной карты клиента (через пользовательский модуль оплаты), и создание соответствующего объекта заказа не удалось.

Мои вопросы:

  • Является ли логика пользовательского платежного модуля неисправной? т.е. есть ли принятый поток для гарантии того, что Magento может преобразовать квоту в исключение заказа бесплатно до совершения платежа в способ оплаты (кредитная карта)?

Редактировать: Похоже, логика платежного модуля действительно неверна, так как вызов $ paymentmethod-> authorize () должен произойти после того места, где возникает этот тупик, а не до (согласно ответу Ивана ниже). Тем не менее, транзакция по-прежнему блокируется тупиком (хотя и без ошибочного списания средств с кредитной карты).

  • Вызов этой функции $stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true);в Mage_CatalogInventory_Model_Stock::registerProductsSale()делает его блокировки чтения, насколько опасно было бы сделать это без блокировки чтения?

  • При поиске ответа в сети несколько мест предложили не проводить полную переиндексацию, пока сайт горячий; вряд ли кажется хорошим решением; Является ли проблема индексации, вызывающая взаимные блокировки таблиц и конфликты блокировок, известной проблемой в Magento, есть ли обходные пути?

Редактировать: Кажется, что оставшийся вопрос здесь является вопросом от третьего вопроса; переиндексация, вызывающая взаимные блокировки таблиц. Нужны обходные пути для этого.

Изменить: концепция, что взаимоблокировки не сами по себе проблемы, а ответ на них должны быть в центре внимания, имеет большой смысл. Дальнейшее расследование, чтобы найти точку в коде, чтобы перехватить исключение взаимоблокировки и повторить запрос. Выполнение этого на уровне адаптера Zend Framework DB является одним из подходов, но я также ищу способ сделать это в коде Magento для упрощения сопровождения.

В этой теме есть интересный патч: http://www.magentocommerce.com/boards/viewthread/31666/P0/, который, кажется, решает соответствующее условие взаимоблокировки (но не этот конкретно).

Изменить: Очевидно, блокировка была решена до определенной степени в CE 1.8 Альфа. Все еще ищу обходной путь, пока эта версия не выходит из альфы

Росций
источник
Недавно мы боролись с подобной проблемой, какое расширение оплаты вы используете?
Питер О'Каллаган
Это расширение с пользовательским кодом
Roscius
1
@kalenjordan Улучшения индексации в версии 1.13 и схема повторных попыток, например, приведенные ниже в статье Филвинкля, в значительной степени смягчают эту проблему для меня.
Росций
1
@ Росциус, грубо говоря, насколько они это смягчили? Я вижу, что сбои БД в некотором роде (тайм-аут соединения, тайм-аут ожидания блокировки, взаимоблокировка) затрагивают около 0,2% моих заказов. Очень редко, но я действительно хочу, чтобы это было полностью решено.
Календжордан
1
Возможные дубликаты. Какова причина запуска переиндексации цены после размещения заказа?
Теджа Бхагаван Коллепара

Ответы:

16

Существует довольно большая вероятность того, что ваш метод оплаты обрабатывает платеж неправильно.

Magento Order Save Процесс довольно прост:

  • Подготавливает все данные, которые должны быть перенесены из позиции цитаты в позицию заказа, включая цены и информацию о продукте, после чего не вызывает извлечение цены.
  • Вызвать перед заказом отправить события checkout_type_onepage_save_orderиsales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() вызывается на этом событии наблюдателя
  • Начать транзакцию БД
  • Вызовите $order->place()метод, который обрабатывает платеж, вызвав его $paymentMethod->authorize(), $paymentMethod->capture()или $paymentMethod->initialize()зависит от его логики.
  • Вызвать метод $ order-> save (), который сохраняет обработанный заказ в таблицы БД sales_flat_order_*.
  • Зафиксировать транзакцию БД (на этом шаге БД снимает блокировку таблицы инвентаризации)

Таким образом, как вы видите, такой способ оплаты не может быть платным до блокировки инвентаря и считывания цен на продукт или информации о продукте.

Это возможно только в том случае, если способ оплаты реализован таким образом, что он сам выполняет загрузку продуктов с ценами после выполнения вызова API для операции начисления платы.

Надеюсь, что это поможет вам в отладке вашей проблемы.

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

Иван Чепурный
источник
1
Спасибо, похоже, логика пользовательского модуля оплаты немного отключена. Тем не менее, похоже, что процесс индексации заблокирует оформление заказа, вызвав исключение в registerProductsSale()(понимая, что с исправлениями в модуле пользовательских платежей будет устранена проблема с зарядкой карты клиента).
Росций
8

Поскольку это пользовательское расширение, мы можем найти собственный обходной путь (читай: взломать), чтобы повторить попытку сохранения без редактирования основных файлов.

Я решил все мои тупиковые проблемы с помощью следующих двух методов, добавленных в вспомогательный класс. Вместо звонка $product->save()я сейчас звоню Mage::helper('mymodule')->saferSave($product):

/**
 * Save with a queued retry upon deadlock, set isolation level
 * @param  stdClass $obj object must have a pre-defined save() method
 * @return n/a      
 */
public function saferSave($obj)
{

    // Deadlock Workaround
    $adapter = Mage::getModel('core/resource')->getConnection('core_write');
    // Commit any existing transactions (use with caution!)
    if ($adapter->getTransactionLevel > 0) {
        $adapter->commit();
    }
    $adapter->query('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');

    //begin a retry loop that will recycle should a deadlock pop up
    $tries = 0;
        do {
            $retry = false;
            try {
                $obj->save();
            } catch (Exception $e) {
                if ($tries < 10 and $e->getMessage()=='SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction') {
                    $retry = true;
                } else {
                    //we tried at least 10 times, go ahead and throw exception
                    throw new Zend_Db_Statement_Exception($e->getMessage());
                }
                sleep($this->getDelay());
                $tries++;
            }
        } while ($retry);

    //free resources
    unset($adapter);
    return;
}

public function getDelay($tries){
    return (int) pow(2, $tries);
}

Это выполняет две разные вещи - он ставит повторную попытку в очередь при обнаружении тупика и устанавливает экспоненциально увеличивающееся время ожидания для этой повторной попытки. Он также устанавливает уровень изоляции транзакции. Существует много информации о SO и DBA.SE для получения дополнительной информации об уровнях изоляции транзакций MySQL.

FWIW, я не сталкивался с тупиком с тех пор.

philwinkle
источник
1
@Mage :: getModel ('core / resource') @ должен создать новое соединение. Я не понимаю, как это может изменить текущий уровень изоляции транзакции.
giftnuss
@ giftnuss достаточно справедливо. Должен быть одноразовым наверняка. Не стесняйтесь вносить это в мой тупиковый модуль на github
philwinkle
@philwinkle спасибо за этого человека. Я пытаюсь выяснить, решит ли мое обновление EE 1.13 мои проблемы или я тоже должен разобраться в этом. Я знаю, что 1.13 выполняет индексирование асинхронно, что замечательно, но если задействованы одни и те же базовые запросы, мне трудно понять, как только асинхронность может предотвратить возникновение взаимоблокировок.
Календжордан
1
@kalenjordan - это сочетание асинхронных и обновленных изменений varien db в 1.8 / 1.13, что уменьшает вероятность взаимоблокировок.
Philwinkle
Я думаю, что вы забыли перейти $triesк этой функцииsleep($this->getDelay());
Тахир Ясин
3

На форумах Magento говорят о редактировании файла библиотеки Zend: lib / Zend / Db / Statement / Pdo.php

Оригинальная функция _execute:

public function _execute(array $params = null)
    {
        // begin changes
        $tries = 0;
        do {
            $retry = false;
            try {
                if ($params !== null) {
                    return $this->_stmt->execute($params);
                } else {
                    return $this->_stmt->execute();
                }
            } catch (PDOException $e) {
                #require_once 'Zend/Db/Statement/Exception.php';
                if ($tries < 10 and $e->getMessage()=='SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction') {
                    $retry = true;
                } else {
                    throw new Zend_Db_Statement_Exception($e->getMessage());
                }
                $tries++;
            }
        } while ($retry);
        // end changes
    }

После модификации:

public function _execute(array $params = null)
    {
        $tries = 0;
        do {
            $retry = false;
            try {
                $this->clear_result();
                $result = $this->getConnection()->query($sql);
                $this->clear_result();
            }
            catch (Exception $e) {
                if ($tries < 10 and $e->getMessage()=='SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction') {
                    $retry = true;
                } else {
                    throw $e;
                }
                $tries++;
            }
        } while ($retry);

        return $result;
    }

Как вы можете видеть, единственное, что было изменено, это то, что $ try был перемещен за пределы цикла.

Как всегда, предлагается попробовать это в среде разработки / тестирования, а не мгновенно развертывать это исправление в производственной среде.

Kenny
источник
2
Я беспокоюсь о редактировании базовых файлов фреймворка, кажется, что попытка должна произойти на уровне кода Magento.
Росций
Мы попробовали предлагаемое исправление, и оно действительно предотвратило возникновение проблем. Мы также получали взаимоблокировки при сохранении в sales_flat_order_grid, и с этим исправлением они вместо этого выдают нарушения целостности, что явно нехорошо.
Питер О'Каллаган
2

У меня есть такая же проблема на сайте Magento 1.11, и у меня есть открытый билет с Magento на нем с 12.11.2012. Они подтвердили, что это проблема, и предположительно создают патч.

Мой вопрос: почему в это время нужно переиндексировать цену? Я не думаю, что это необходимо:

#8 /var/www/html/app/code/core/Mage/CatalogInventory/Model/Observer.php(689): Mage_Catalog_Model_Resource_Product_Indexer_Price->reindexProductIds(Array)
Кимберели Томас
источник
1
Если товара нет в наличии и нет в наличии товаров, которые не должны отображаться в каталоге, я полагаю, что они скрыты заслугой отсутствия записей индекса цен, которые в итоге исключают их из коллекции товаров, когда к ним присоединяются цены. ,
Давидгер
Это не отвечает на вопрос. Похоже, вы пытаетесь добавить дополнительную информацию к исходному вопросу. Возможно, эта информация будет лучше в качестве комментария к исходному вопросу.
Люк Миллс
Я с тобой, Ким. У меня был один и тот же билет, открытый с 11/2011.
Philwinkle
Я знаю, что это технически не ответ, а подвопрос, однако он отвечает на вопрос, который ссылается на этот вопрос как на дубликат! Таким образом, Кимберли Томас и Дэвиджергер получили мое одобрение за то, что я ответил на мое конкретное "Почему это переиндексирует цены?" вопрос, который я сейчас гуглю! Спасибо!
Cygnus Digital
0

У нас была похожая тупиковая проблема, когда во время переиндексации были сделаны определенные звонки. Для нас это проявилось, в основном, когда покупатель будет что-то добавлять в корзину. Хотя, вероятно, не удалось устранить реальную проблему, реализация асинхронной переиндексации полностью остановила все вызовы взаимоблокировок, которые мы видели ранее. Должен работать в качестве временного промежутка до тех пор, пока основная проблема не будет устранена и перенесена в выпуски EE / CE (в итоге мы купили расширение для этого).

fr0x
источник
0

Я предлагаю вам установить Philwinkle DeadlockRetry. Это работает для нашей базы данных.

https://github.com/philwinkle/Philwinkle_DeadlockRetry

Я бы также посоветовал посмотреть на любые сторонние программы, попадающие в ваш веб-интерфейс. У нас был один, который обновлял QTY для продуктов, и это вызывало много тупиков. Мы переписали это и пошли прямо в базу данных.

Крис Розенау
источник
1
Этот репо больше не поддерживается, но, к счастью, он рекомендует заменить github.com/AOEpeople/Aoe_DbRetry .
Гусь
-1

Я сталкивался с проблемой взаимоблокировки в прошлом году много раз, когда решал ее, просто увеличивая память для нашего сервера, потому что процесс индексации поглощает все ресурсы.

Вы также должны использовать решение async reindex, которое я использовал miravist

Для более стабильной системы вы должны подумать о том, чтобы отделить ваш бэкэнд от внешнего интерфейса, чтобы они не поглощали оперативную память друг друга.

По моему опыту, это не проблема исходного кода.

phanvugiap
источник