Die Neuindizierung des Preises führt zu DB-Deadlocks während der Kaufabwicklung

47

Ich habe ein Problem, bei dem der Prozess der erneuten Indizierung des Produktpreises meines Erachtens eine Deadlock-Ausnahme im Checkout-Prozess verursacht.

Ich habe diese Ausnahme beim Auschecken festgestellt:

Ausnahme bei der Auftragskonvertierung: SQLSTATE [40001]: Serialisierungsfehler: 1213 Deadlock beim Versuch, eine Sperre abzurufen; Starten Sie die Transaktion erneut

Leider habe ich keinen vollständigen Stack-Trace, da die Ausnahme abgefangen wurde, aber ich habe den INNODB-Status überprüft und konnte den Deadlock aufspüren:

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` 

Die SQL-Anfragetabellensperre wird letztendlich generiert, Mage_CatalogInventory_Model_Stock::registerProductsSale()wenn versucht wird, die aktuelle Inventurzählung abzurufen, um sie zu dekrementieren.

Zu dem Zeitpunkt, als der Deadlock auftrat, wurde der Produktpreis-Neuindexierungsprozess ausgeführt, und ich gehe davon aus, dass er eine Lesesperre für die hatte catalog_product_entity table, die den Deadlock verursacht hat. Wenn ich den Deadlock richtig verstehe, wird durch jede Lesesperre ein Deadlock ausgelöst, aber der Produktpreis-Neuindex hält die Sperre für eine angemessene Zeit aufrecht, da auf der Site ~ 50.000 Produkte vorhanden sind.

Leider wurde zu diesem Zeitpunkt im Bestellcode-Fluss die Kreditkarte des Kunden belastet (über ein benutzerdefiniertes Zahlungsmodul), und die Erstellung des entsprechenden Bestellobjekts schlug fehl.

Meine Fragen sind:

  • Ist die Logik des benutzerdefinierten Zahlungsmoduls fehlerhaft? Dh Gibt es einen akzeptierten Ablauf, um sicherzustellen, dass Magento das Angebot in eine Bestellungsausnahme umwandeln kann, bevor die Belastung der Zahlungsmethode (Kreditkarte) zugewiesen wird?

Bearbeiten: Es scheint, dass die Logik des Zahlungsmoduls tatsächlich fehlerhaft ist, da der Aufruf von $ paymentmethod-> authorize () nach dem Ort erfolgen sollte, an dem dieser Deadlock auftritt, und nicht vorher (wie in der Antwort von Ivan unten angegeben). Die Transaktion wird jedoch weiterhin durch den Deadlock blockiert (allerdings ohne die fehlerhafte Belastung der Kreditkarte).

  • Dieser Funktionsaufruf $stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true);in Mage_CatalogInventory_Model_Stock::registerProductsSale()macht es zu einem Sperr lesen, wie gefährlich es wäre es ein nicht sperr gelesen werden , um zu machen?

  • Beim Durchsuchen des Webs nach einer Antwort haben einige Stellen vorgeschlagen, keine vollständige Neuindizierung durchzuführen, solange die Site noch in Betrieb ist. scheint kaum eine gute Lösung zu sein; Ist das Problem der Indizierung, das zu Tabellen-Deadlocks und Sperrenkonflikten führt, in Magento ein bekanntes Problem? Gibt es Problemumgehungen?

Bearbeiten: Es scheint, die verbleibende Frage hier ist die aus der dritten Frage; Neuindizierung führt zu Table Deadlocks. Suchen Sie nach Problemumgehungen dafür.

Bearbeiten: Das Konzept, dass Deadlocks nicht an sich Probleme sind, sondern die Reaktion auf sie im Mittelpunkt stehen sollte, ist sehr sinnvoll. Weitere Untersuchungen, um einen Punkt im Code zu finden, um die Deadlock-Ausnahme abzufangen und die Anforderung erneut auszustellen. Dies auf der Ebene des Zend Framework DB-Adapters zu tun, ist ein Ansatz, aber ich suche auch nach einer Möglichkeit, dies im Magento-Code zu tun, um die Wartbarkeit zu vereinfachen.

Es gibt einen interessanten Patch in diesem Thread: http://www.magentocommerce.com/boards/viewthread/31666/P0/ , der eine damit verbundene Deadlock-Bedingung zu lösen scheint (aber nicht diese speziell).

Edit: Anscheinend wurde Deadlocking bis zu einem gewissen Grad in CE 1.8 Alpha behoben. Suchen Sie noch nach einer Problemumgehung, bis diese Version von Alpha ist

Roscius
quelle
Wir haben in letzter Zeit mit einem ähnlichen Problem zu kämpfen. Welche Zahlungserweiterung verwenden Sie?
Peter O'Callaghan
Es ist eine benutzerdefinierte codierte Erweiterung
Roscius
1
@kalenjordan Die Indexverbesserungen in 1.13 und ein Wiederholungsschema wie das folgende von philwinkle haben das Problem für mich weitgehend entschärft.
Roscius
1
@ Roscius in etwa wie stark haben sie es gemildert? Ich sehe, dass DB-Fehler (Verbindungs-Timeout, Wartezeit für Sperren, Deadlock) etwa 0,2% meiner Bestellungen betreffen. Sehr selten, aber ich möchte es wirklich vollständig gelöst bekommen.
Kalenjordan

Antworten:

16

Es ist sehr wahrscheinlich, dass Ihre Zahlungsmethode die Zahlung falsch verarbeitet.

Das Speichern von Magento-Bestellungen ist ganz einfach:

  • Bereitet alle Daten vor, die von Angebotsposition zu Auftragsposition übertragen werden sollen, einschließlich Preise und Produktinformationen, und ruft anschließend keine Preisabfrage auf.
  • Rufen Sie vor der Bestellung Ereignisse auf checkout_type_onepage_save_orderund senden Siesales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() wird bei diesem Ereignisbeobachter aufgerufen
  • Starten Sie die DB-Transaktion
  • Invoke $order->place()Methode, die die Zahlung durch den Aufruf verarbeitet $paymentMethod->authorize(), $paymentMethod->capture()oder $paymentMethod->initialize()hängt von seiner Logik.
  • Rufen Sie die Methode $ order-> save () auf, die den verarbeiteten Auftrag in DB-Tabellen speichert sales_flat_order_*.
  • DB-Transaktion festschreiben (In diesem Schritt gibt der DB die Sperre für die Bestandsliste frei.)

Wie Sie sehen, könnte es nicht möglich sein, dass die Zahlungsmethode vor dem Sperren des Lagerbestands Geld belastet und Produktpreise oder Produktinformationen liest.

Es ist nur möglich, wenn die Zahlungsmethode so implementiert ist, dass sie das Laden von Produkten selbst mit Preisen durchführt, nachdem der API-Aufruf für den Ladevorgang ausgeführt wurde.

Hoffe, dies wird Ihnen beim Debuggen Ihres Problems helfen.

Was die Neuindizierung angeht, sollte es sicher sein, wenn Sie dieses Problem mit der Zahlungsmethode nicht haben. Da Lesevorgänge, die von Sperren abhängen, ausgeführt werden, bevor Geld belastet wird.

Ivan Chepurnyi
quelle
1
Vielen Dank, es sieht so aus, als ob die Logik des benutzerdefinierten Zahlungsmoduls ein wenig abweicht. Es sieht jedoch immer noch so aus, als würde ein Indizierungsprozess das Auschecken blockieren, indem er eine Ausnahme registerProductsSale()auslöst.
Roscius
8

Da es sich um eine benutzerdefinierte Erweiterung handelt, können wir eine benutzerdefinierte Problemumgehung (read: hack) finden, um das Speichern erneut zu versuchen, ohne die Kerndateien zu bearbeiten.

Ich habe alle meine Deadlock-Probleme mit den folgenden zwei Methoden gelöst, die einer Helferklasse hinzugefügt wurden. Anstatt $product->save()anzurufen, rufe ich jetzt an 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);
}

Dadurch werden zwei verschiedene Dinge erreicht: Es wird eine Wiederholung in die Warteschlange gestellt, wenn ein Deadlock auftritt, und es wird ein exponentiell ansteigendes Zeitlimit für diese Wiederholung festgelegt. Außerdem wird die Transaktionsisolationsstufe festgelegt. Es gibt viele Informationen zu SO und DBA.SE, um weitere Informationen zu den Transaktionsisolationsstufen von MySQL zu erhalten.

FWIW, seitdem bin ich keinem Deadlock mehr begegnet.

Philwinkle
quelle
1
@Mage :: getModel ('core / resource') @ sollte eine neue Verbindung erstellen. Ich verstehe nicht, wie es die aktuelle Transaktionsisolationsstufe ändern kann.
Giftnuss
@giftnuss fair genug. Sollte auf jeden Fall Singleton sein. Fühlen Sie sich frei, dies auf meinem Deadlock-Modul über auf github
philwinkle 30.11.13
@philwinkle danke für diesen Mann. Ich versuche herauszufinden, ob ein Upgrade auf EE 1.13 meine Probleme löst oder ob ich dies ebenfalls prüfen sollte. Ich weiß, dass 1.13 asynchron indiziert, was großartig ist, aber wenn die gleichen zugrunde liegenden Abfragen beteiligt sind, kann ich nur schwer verstehen, wie allein durch Async Deadlocks verhindert werden können.
Kalenjordan
1
@kalenjordan ist eine Kombination aus Asynchronität und den aktualisierten Änderungen der Variablen db in 1.8 / 1.13, die die Wahrscheinlichkeit von Deadlocks verringert.
Philwinkle
Ich denke, Sie haben vergessen, $triesdiese Funktion zu übergebensleep($this->getDelay());
Tahir Yasin
3

In den Magento-Foren wird über das Bearbeiten einer Zend-Bibliotheksdatei gesprochen: lib / Zend / Db / Statement / Pdo.php

Die ursprüngliche Funktion _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
    }

Nach der Modifikation:

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;
    }

Wie Sie sehen können, wurde nur geändert, dass die $ try außerhalb der Schleife verschoben wurden.

Wie immer wird empfohlen, dies in einer Entwicklungs- / Testumgebung auszuprobieren und diesen Fix nicht sofort in einer Produktionsumgebung bereitzustellen.

Kenny
quelle
2
Ich mache mir Sorgen um die Bearbeitung der zugrunde liegenden Framework-Dateien. Es scheint, dass der Wiederholungsversuch stattdessen auf der Magento-Code-Ebene erfolgen sollte.
Roscius
Wir haben das vorgeschlagene Update ausprobiert, und es hat in der Tat verhindert, dass dieser bestimmte Deadlock Probleme verursacht. Wir erhielten außerdem Deadlocks für Save-Vorgänge im sales_flat_order_grid. Mit dieser Korrektur werden stattdessen Verstöße gegen Integritätsrichtlinien ausgelöst, was offensichtlich nicht gut ist.
Peter O'Callaghan
2

Ich habe das gleiche Problem auf einer Magento 1.11-Site und habe seit dem 12.11.2012 ein offenes Ticket mit Magento. Sie haben bestätigt, dass es sich um ein Problem handelt, und es wird angenommen, dass sie einen Patch erstellen.

Meine Frage ist, warum der Preis zu diesem Zeitpunkt neu indiziert werden muss. Ich denke nicht, dass das nötig ist:

#8 /var/www/html/app/code/core/Mage/CatalogInventory/Model/Observer.php(689): Mage_Catalog_Model_Resource_Product_Indexer_Price->reindexProductIds(Array)
Kimberly Thomas
quelle
1
Wenn ein Produkt nicht mehr vorrätig ist und nicht mehr vorrätige Produkte nicht im Katalog angezeigt werden sollen, sind sie meines Erachtens verborgen, da sie keine Preisindexdatensätze haben, die sie aus der Produktkollektion ausschließen, wenn die Preiskalkulation hinzugefügt wird .
Davidalger
Dies beantwortet die Frage nicht. Sie versuchen anscheinend, der ursprünglichen Frage zusätzliche Informationen hinzuzufügen. Vielleicht wäre diese Information besser als Kommentar zur ursprünglichen Frage.
Luke Mills
Ich bin bei dir, Kim. Ich habe das gleiche Ticket seit 11/2011 geöffnet.
Philwinkle
Ich weiß, dass dies technisch gesehen keine Antwort, sondern eine Unterfrage ist. Sie beantwortet jedoch die Frage, die auf diese Frage als Duplikat verweist! Also bekommen Kimberly Thomas und Davidalger meine Gegenstimme für die Beantwortung meiner spezifischen Frage "Warum werden die Preise neu indiziert?" frage das ich gerade google! Vielen Dank!
Cygnus Digital
0

Wir hatten ein ähnliches Deadlock-Problem, als bestimmte Aufrufe während einer Neuindizierung getätigt wurden. Für uns zeigte es sich meistens, wenn ein Kunde etwas in den Warenkorb legen würde. Obwohl das eigentliche zugrunde liegende Problem wahrscheinlich nicht behoben wurde, hat die Implementierung einer asynchronen Neuindizierung alle zuvor aufgetretenen Deadlock-Aufrufe vollständig gestoppt. Sollte eine Lücke schließen, bis das zugrunde liegende Problem behoben und auf die EE / CE-Editionen verschoben wurde (wir haben uns dafür eine Erweiterung gekauft).

fr0x
quelle
0

Ich schlage vor, dass Sie Philwinkle DeadlockRetry installieren. Es funktionierte für unsere Datenbank.

https://github.com/philwinkle/Philwinkle_DeadlockRetry

Ich würde auch vorschlagen, Ihre externen Programme zu betrachten, die auf Ihre Web-API treffen. Wir hatten eine, die die Menge der Produkte aktualisierte und viele Deadlocks verursachte. Wir haben das umgeschrieben und sind direkt zur Datenbank gegangen.

Chris Rosenau
quelle
1
Dieses Repo wird nicht mehr unterstützt, aber zum Glück wird empfohlen, es durch github.com/AOEpeople/Aoe_DbRetry zu ersetzen .
Gans
-1

Ich bin letztes Jahr mehrfach auf ein Deadlock-Problem gestoßen und habe es einfach durch Erhöhen des Speichers für unseren Server behoben, da der Indizierungsprozess alle Ressourcen verschlingt.

Sie sollten auch uns asynchrone Reindex-Lösung verwenden, die ich miravist

Für ein stabileres System sollten Sie daran denken, Ihr Backend vom Frontend zu trennen, damit sie nicht gegenseitig den Arbeitsspeicher belasten.

Nach meiner Erfahrung ist es kein Problem des Quellcodes.

phanvugiap
quelle