価格の再インデックスにより、チェックアウト中にDBデッドロックが発生する


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()への呼び出しは、このデッドロックが発生する場所の前ではなく、後に発生するため、支払いモジュールのロジックは実際に障害があります(以下のIvanの回答による)。ただし、トランザクションはデッドロックによってブロックされます(ただし、クレジットカードへの誤った請求はありません)。

  • この関数呼び出し$stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true);Mage_CatalogInventory_Model_Stock::registerProductsSale()ロック読み取りを行いますが、非ロック読み取りにすることはどれほど危険ですか?

  • Webで回答を検索する際に、サイトがホットな間は完全な再インデックスを実行しないことをいくつかの場所が提案しました。良い解決策とは思えない。テーブルのデッドロックとロックの競合を引き起こすインデックス作成の問題は、Magentoの既知の問題ですが、回避策はありますか?

編集: ここに残っている質問は3番目の質問からのものであるようです。テーブルのデッドロックを引き起こすインデックスの再作成。このための回避策を探しています。

編集:デッドロックは問題ではなく、それ自体が問題であるという概念ですが、むしろデッドロックへの応答が焦点である必要があり、多くの意味があります。さらに調査して、デッドロック例外をキャッチし、要求を再発行するコード内のポイントを見つけます。Zend Framework DBアダプターレベルでこれを行うことは1つのアプローチですが、Magentoコードでこれを行う方法を探しており、保守性が向上しています。

このスレッドには興味深いパッチがあります:http : //www.magentocommerce.com/boards/viewthread/31666/P0/これは、関連するデッドロック状態を解決するようです(ただし、これは特にそうではありません)。

編集:明らかにデッドロックはCE 1.8 Alphaである程度対処されました。このバージョンがアルファ版になるまで回避策を探しています


最近、同様の問題に取り組んでいますが、どの支払い延長を使用していますか?
ピーターオキャラハン

これはカスタムコード化された拡張機能です
-Roscius

1
@kalenjordan 1.13のインデックス作成の改善と、以下のphilwinkleのような再試行スキームにより、この問題は大幅に軽減されました。
ロスキウス14年

1
@Rosciusは、おおよそどの程度軽減しましたか?何らかのDBの障害(接続タイムアウト、ロック待機タイムアウト、デッドロック)が注文の約0.2%に影響するのを確認しています。非常にまれですが、本当に完全に解決したいです。
kalenjordan

回答:


16

お支払い方法が誤って支払いを処理している可能性はかなりあります。

Magentoの注文の保存プロセスは非常に簡単です。

  • 価格や製品情報など、見積品目から注文品目に転送する必要のあるすべてのデータを準備します。その後、価格の取得を呼び出しません。
  • 注文送信イベントの前に呼び出しcheckout_type_onepage_save_ordersales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() このイベントオブザーバで呼び出されます
  • DBトランザクションを開始
  • 呼び出しの$order->place()呼び出しによって支払いを処理する方法$paymentMethod->authorize()$paymentMethod->capture()または$paymentMethod->initialize()そのロジックに依存します。
  • 処理された注文をDBテーブルに保存する$ order-> save()メソッドを呼び出しsales_flat_order_*ます。
  • DBトランザクションのコミット(このステップで、DBはインベントリテーブルのロックを解除します)

ご覧のように、在庫のロックと製品価格または製品情報の読み取りの前に、その支払い方法はお金を請求します。

支払い操作のAPI呼び出しが実行された後、支払い方法がそのような方法で実装されている場合にのみ可能です。

これが問題のデバッグに役立つことを願っています。

支払い方法にこの問題がない場合、インデックスの再作成については安全です。ロックに依存する読み取り操作は、お金が請求される前に実行されるためです。


1
おかげで、カスタム支払いモジュールのロジックが少しずれているように見えます。ただし、インデックス作成プロセスが例外を発生させてチェックアウトをブロックするように見えますregisterProductsSale()(カスタム支払いモジュールの修正により、顧客のカードへの請求の問題が解消されることを理解しています)。
ロジアス

8

これはカスタム拡張であるため、コアファイルを編集せずに保存を再試行するためのカスタム回避策(読み取り:ハック)を見つけることができます。

次の2つのメソッドをヘルパークラスに追加することで、デッドロックの問題をすべて解決しました。呼び出す代わりに、$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);
}

これにより、2つの明確なことが行われます。デッドロックが発生したときに再試行をキューに入れ、その再試行に対して指数関数的に増加するタイムアウトを設定します。また、トランザクション分離レベルも設定します。MySQLのトランザクション分離レベルの詳細については、SOおよびDBA.SEに多くの情報があります。

FWIW、私はそれ以来、デッドロックに遭遇していません。


1
@Mage :: getModel( 'core / resource')@は新しい接続を作成する必要があります。現在のトランザクション分離レベルをどのように変更できるかわかりません。
-giftnuss

@giftnussまあまあ。確かにシングルトンでなければなりません。これを
GitHubの

@philwinkleこの男に感謝します。EE 1.13のアップグレードで問題が解決するのか、これも検討する必要があるのか​​を把握しようとしています。1.13が非同期でインデックスを作成することは素晴らしいことですが、同じ基になるクエリが関係している場合、非同期だけでデッドロックが発生するのを防ぐ方法がわかりません。
kalenjordan

1
@kalenjordanは、非同期と1.8 / 1.13で更新されたvarien dbの変更の組み合わせであり、デッドロックの可能性を減らします。
philwinkle 14年

私はあなたが合格するのを忘れて考えて$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;
    }

ご覧のとおり、変更されているのは、$ triesがループの外側に移動したことだけです。

いつものように、開発/テスト環境でこれを試して、本番環境にこの修正を即座に展開しないことをお勧めします。


2
基礎となるフレームワークファイルの編集が心配です。代わりにMagentoコードレベルで再試行が行われるようです。
ロジアス

提案された修正を試してみましたが、この特定のデッドロックが問題を引き起こすのを実際に防ぎました。また、sales_flat_order_gridへの保存でデッドロックが発生していましたが、この修正を適用すると、代わりに整合性違反がスローされます。これは明らかに良くありません。
ピーターオキャラハン

2

Magento 1.11サイトでも同じ問題があり、2012年11月12日以降、Magentoのオープンチケットがあります。彼らはそれが問題であることを確認し、パッチを作成していると思われます。

私の質問は、現時点で価格を再インデックスする必要があるのはなぜですか?私はこれが必要だとは思わない:

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

1
製品が在庫切れになり、在庫切れの製品がカタログに表示されない場合、価格がそれに関連付けられたときに製品コレクションから除外される価格指数レコードがないというメリットに隠れていると思います。
-davidalger

これは質問に答えません。元の質問に追加情報を追加しようとしているようです。おそらく、この情報は元の質問に対するコメントとしてより良いでしょう。
ルーク・ミルズ

キム、君と一緒だ。2011年11月から同じチケットを開いています。
フィルウィンクル

これは技術的には答えではなくサブ質問であることは知っていますが、この質問を重複として参照する質問には答えます!それで、キンバリー・トーマスとダビダルジャーは、「なぜ価格の再インデックス付けをするのか」という私の具体的な答えに賛成です。私が質問している;現在グーグルだ!ありがとう!
シグナスデジタル

0

インデックスの再作成中に特定の呼び出しが行われると、同様のデッドロックの問題が発生しました。私たちにとっては、主に顧客がカートに何かを追加するときに現れました。おそらく実際の根本的な問題は修正されていませんが、非同期のインデックス再作成を実装することで、これまで見ていたすべてのデッドロック呼び出しが完全に停止しました。根本的な問題が修正され、EE / CEエディションにプッシュされるまで、一時的なギャップとして機能するはずです(結局、拡張機能を購入することになりました)。


0

Philwinkle DeadlockRetryをインストールすることをお勧めします。私たちのデータベースで機能しました。

https://github.com/philwinkle/Philwinkle_DeadlockRetry

また、Web APIにアクセスする外部プログラムを確認することをお勧めします。製品のQTYを更新していて、多くのデッドロックを引き起こしていたものがありました。私たちはそれを書き直し、データベースに直接行きました。


1
このレポはサポートされなくなりましたが、幸いなことにgithub.com/AOEpeople/Aoe_DbRetryを置き換えることをお勧めします。
グース

-1

昨年、デッドロックの問題に何度も遭遇しましたが、インデックス作成プロセスはすべてのリソースを消費するため、サーバーのメモリを増やすだけで解決しました。

また、私はmiravistを使用した非同期インデックス再作成ソリューションをお勧めします

システムをより安定させるには、バックエンドをフロントエンドから分離して、互いのRAMを消費しないようにすることを検討する必要があります。

私の経験では、ソースコードの問題ではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.