Переіндексація цін спричиняє тупикові місця БД під час оформлення замовлення


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-> autize () повинен відбуватися після того місця, де відбувається цей тупик, а не раніше (відповідно до відповіді Івана нижче). Однак транзакція все-таки буде заблокована тупиком (хоча і не вимагає стягнення з кредитної картки).

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

  • У пошуках відповіді в Інтернеті кілька місць запропонували не проводити повну переіндексацію, поки сайт гарячий; навряд чи здається хорошим рішенням; це питання індексації, що спричиняє тупіки таблиці та суперечки щодо блокування, відома проблема в Magento, чи існують обхідні шляхи?

Редагувати: Здається, питання, що залишилося тут, - це питання третього; повторна індексація, що спричиняє тупикові таблиці. Шукаєте обхідні шляхи для цього.

Редагувати: Концепція того, що тупики не є самими проблемами, а скоріше, саме у відповіді на них має бути фокус, має багато сенсу. Далі досліджуємо, щоб знайти крапку в коді, щоб зафіксувати виняток із тупикової ситуації та повторно подати запит. Робити це на рівні адаптера DB Zend Framework - це один підхід, але я також шукаю спосіб зробити це в коді Magento для полегшення ремонту.

У цій темі є цікавий виправлення: http://www.magentocommerce.com/boards/viewthread/31666/P0/, який, здається, вирішує пов'язану з цим ситуацію (але не конкретно).

Редагувати: Очевидно, тупик був вирішений на ступінь CE 1.8 Alpha. Досі шукаю вирішення, поки ця версія не вийде з Альфи


Ми останнім часом боролися з подібною проблемою, яке розширення платежів ви використовуєте?
Peter O'Callaghan

Це призначене для користувача кодове розширення
Росцій

1
@kalenjordan Поліпшення індексації в 1.13 та схема повторної спроби, як нижче, ніж philwinkle, значною мірою пом'якшили проблему для мене.
Роцій

1
@Roscius приблизно, наскільки вони це пом'якшили? Я бачу, деякі збої в БД (тайм-аут підключення, час очікування блокування очікування, тупик) впливають приблизно на 0,2% моїх замовлень. Дуже рідко, але мені дуже хочеться всебічно вирішити.
kalenjordan

Відповіді:


16

Існує досить велика ймовірність того, що ваш спосіб оплати неправильно обробляє платіж.

Процес збереження замовлення Magento досить простий:

  • Готує всі дані, які слід перенести з пункту котирування до товару замовлення, включаючи ціни та інформацію про товар, після чого він не посилається на отримання ціни.
  • Викликайте перед тим, як замовлення надсилає події checkout_type_onepage_save_orderтаsales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() викликається у цього спостерігача
  • Почніть операцію з БД
  • Invoke $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, з тих пір я не стикався з тупиком.


1
@Mage :: getModel ('core / resource') @ повинен створити нове з'єднання. Я не розумію, як це може змінити поточний рівень ізоляції транзакцій.
giftnuss

@giftnuss досить справедливо. Слід бути однотонним точно. Не соромтеся внести це в мій модуль «тупик» на
Github

@philwinkle дякую цій людині. Я намагаюся розібратися, чи оновлення EE 1.13 вирішить мої негаразди чи чи слід мені також займатися цим. Я знаю, що 1.13 індексує асинхронно, що чудово, але якщо задіяні ті самі основні запити, я важко розумію, як саме асинхронізація запобігає виникненню тупикових ситуацій.
kalenjordan

1
@kalenjordan - це комбінація асинхронізації та оновленого варіанту 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;
    }

Як ви бачите, єдине, що було змінено - це те, що $ спроби були переміщені за межі циклу.

Як завжди, пропонується спробувати це на середовищі розробки / тестування, а не миттєво розгортати це виправлення у виробничому середовищі.


2
Я переживаю за редагування базових файлів фреймворку, здається, що повторне повторення повинно відбуватися на рівні коду Magento.
Роцій

Ми спробували виправити запропоновані виправлення, і це дійсно запобігло виникненню проблем із цим глухим кутом. Ми також отримували тупикові місця на заощадженнях до sales_flat_order_grid, і це виправлення на місці, вони замість цього кидають порушення контрактності на цілісність, що, очевидно, не добре.
Пітер О'Каллаган

2

У мене цей самий випуск на сайті Magento 1.11, і я маю відкритий квиток з Magento на нього з 11.12.2012. Вони підтвердили, що це проблема, і, мабуть, створюють виправлення.

Моє запитання, чому в цей час потрібно переробляти ціну? Я не думаю, що це потрібно:

#8 /var/www/html/app/code/core/Mage/CatalogInventory/Model/Observer.php(689): Mage_Catalog_Model_Resource_Product_Indexer_Price->reindexProductIds(Array)

1
Якщо товар випадає на складі, а продукція, що не є на складі, не повинна відображатись у каталозі, я вважаю, що вони приховані заслугою відсутності записів індексу цін, які в кінцевому підсумку виключають їх із колекції товару, коли ціноутворення приєднується до нього .
davidalger

Це не відповідає на запитання. Схоже, ви намагаєтесь додати додаткову інформацію до початкового питання. Можливо, ця інформація була б кращою як коментар до оригінального питання.
Люк Міллс

Я з тобою, Кім. У мене такий самий квиток відкритий з 11/2011.
philwinkle

Я знаю, що це технічно не відповідь, а під питання, однак воно відповідає на питання, яке посилається на це питання як на дублікат! Тож Кімберлі Томас та Девіддалгер отримують мою нагороду за відповідь на мою конкретну записку "Чому це перевстановлює ціни?" питання, що я зараз ходя в Google! Дякую!
cygnus digital

0

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


0

Я пропоную вам встановити Philwinkle DeadlockRetry. Це працювало для нашої бази даних.

https://github.com/philwinkle/Philwinkle_DeadlockRetry

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


1
Це репо вже не підтримується, але, на щастя, рекомендує замінити його github.com/AOEpeople/Aoe_DbRetry .
Гуска

-1

У минулому році я зустрічався з проблемою тупикового зв'язку, багато разів я її розбирав, просто збільшуючи пам'ять для нашого сервера, оскільки процес індексації їсть усі ресурси.

Ви також повинні нам async reindex рішення я використав miravist

Для більш стабільної системи слід подумати про відокремлення вашого бекенда від інтерфейсу, щоб вони не їли оперативну пам’ять один одного.

На мій досвід, це не проблема вихідного коду.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.