Il reindicizzazione dei prezzi provoca deadlock DB durante il checkout


47

Sto riscontrando un problema in cui ritengo che il processo di reindicizzazione del prezzo del prodotto stia causando un'eccezione di deadlock nel processo di pagamento.

Ho riscontrato questa eccezione nel processo di pagamento:

Eccezione di conversione ordine: SQLSTATE [40001]: errore di serializzazione: 1213 deadlock trovato durante il tentativo di ottenere il blocco; prova a riavviare la transazione

Sfortunatamente non ho una traccia dello stack completo a causa di dove è stata rilevata l'eccezione, ma controllando lo stato INNODB sono stato in grado di rintracciare il deadlock:

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` 

Il blocco della tabella di richiesta SQL viene in definitiva generato da Mage_CatalogInventory_Model_Stock::registerProductsSale()quando sta cercando di ottenere il conteggio corrente dell'inventario per ridurlo.

Al momento in cui si è verificato il deadlock, il processo di reindicizzazione del prezzo del prodotto era in esecuzione e suppongo che avesse un blocco di lettura sul catalog_product_entity tableche ha causato il deadlock. Se sto capendo correttamente il deadlock, qualsiasi blocco di lettura causerà un deadlock, ma il reindicizzazione del prezzo del prodotto mantiene il blocco per un tempo equo poiché il sito ha circa 50.000 prodotti.

Sfortunatamente, a questo punto nel flusso del codice di pagamento è stata addebitata la carta di credito del cliente (tramite un modulo di pagamento personalizzato) e la creazione dell'oggetto ordine corrispondente non è riuscita.

Le mie domande sono:

  • La logica del modulo di pagamento personalizzato è difettosa? es. Esiste un flusso accettato per garantire che Magento possa convertire gratuitamente il preventivo in un'eccezione dell'ordine prima di impegnare l'addebito sul metodo di pagamento (carta di credito)?

Modifica: sembra che la logica del modulo di pagamento sia effettivamente difettosa poiché la chiamata a $ paymentmethod-> autorize () dovrebbe avvenire dopo il luogo in cui si verifica questo deadlock, non prima (come da risposta di Ivan in basso). Tuttavia, la transazione verrà comunque bloccata dal deadlock (anche se senza addebito errato sulla carta di credito).

  • Questa funzione chiama $stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true);in Mage_CatalogInventory_Model_Stock::registerProductsSale()una lettura bloccata, quanto sarebbe pericoloso renderla una lettura non bloccante?

  • Nella ricerca di una risposta sul Web, un paio di posizioni suggeriscono di non eseguire un reindicizzazione completa mentre il sito è caldo; difficilmente sembra una buona soluzione; il problema dell'indicizzazione che causa deadlock delle tabelle e blocco dei conflitti è un problema noto in Magento, ci sono soluzioni alternative?

Modifica: sembra che la domanda rimanente qui sia quella della terza domanda; reindicizzazione causando deadlock della tabella. Alla ricerca di soluzioni alternative per questo.

Modifica: il concetto che i deadlock non sono di per sé problemi, ma piuttosto la risposta a questi dovrebbe essere al centro, ha molto senso. Indagare ulteriormente per trovare un punto nel codice per rilevare l'eccezione di deadlock ed emettere nuovamente la richiesta. Fare questo a livello di adattatore DB Zend Framework è un approccio, ma sto anche cercando un modo per farlo nel codice Magento per facilitare la manutenibilità.

C'è una patch interessante in questo thread: http://www.magentocommerce.com/boards/viewthread/31666/P0/ che sembra risolvere una condizione di deadlock correlata (ma non questa in particolare).

Modifica: apparentemente il deadlock è stato indirizzato ad un grado in CE 1.8 Alpha. Sto ancora cercando una soluzione alternativa fino a quando questa versione non è uscita da Alpha


Recentemente abbiamo affrontato un problema simile, quale estensione di pagamento stai usando?
Peter O'Callaghan,

È un'estensione codificata personalizzata
Roscius

1
@kalenjordan I miglioramenti dell'indicizzazione dell'1.11 e uno schema di riprogrammazione come quello di Philwinkle di seguito mi hanno ampiamente mitigato.
Roscius

1
@Roscius all'incirca quanto l'hanno mitigato? Vedo che errori del DB di qualche tipo (timeout di connessione, timeout di attesa blocco, deadlock) incidono su circa lo 0,2% dei miei ordini. Molto raro ma voglio davvero risolverlo completamente.
Kalenjordan,

Risposte:


16

È molto probabile che il tuo metodo di pagamento stia elaborando il pagamento in modo errato.

Il processo di salvataggio degli ordini di Magento è abbastanza semplice:

  • Prepara tutti i dati che devono essere trasferiti dall'articolo preventivo all'ordine, compresi i prezzi e le informazioni sul prodotto, in seguito non invoca il recupero dei prezzi.
  • Richiamare prima di inviare eventi checkout_type_onepage_save_orderesales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() viene invocato a questo osservatore di eventi
  • Avvia transazione DB
  • Invoke $order->place()metodo che elabora il pagamento chiamando $paymentMethod->authorize(), $paymentMethod->capture()o $paymentMethod->initialize()dipende dalla sua logica.
  • Richiamare il metodo $ order-> save () che salva l'ordine elaborato nelle tabelle DB sales_flat_order_*.
  • Conferma transazione DB (a questo punto DB rilascia il blocco sulla tabella di inventario)

Quindi, come vedi, non potrebbe essere possibile che quel metodo di pagamento addebiti denaro prima del blocco dell'inventario e legga i prezzi dei prodotti o le informazioni sui prodotti.

È possibile solo nel caso in cui il metodo di pagamento sia implementato in modo tale che esegua il caricamento dei prodotti stessi con i prezzi, dopo che è stata eseguita la chiamata API per l'operazione di addebito.

Spero che questo ti possa aiutare nel debug del tuo problema.

Per quanto riguarda la reindicizzazione, dovrebbe essere sicuro, se non si riscontra questo problema con il metodo di pagamento. Poiché le operazioni di lettura che dipendono dai blocchi vengono eseguite prima dell'addebito del denaro.


1
Grazie, sembra che la logica del modulo di pagamento personalizzato sia leggermente disattivata. Sembra tuttavia che un processo di indicizzazione bloccherà il pagamento causando un'eccezione registerProductsSale()(capire che con le correzioni al modulo di pagamento personalizzato rimuoverà il problema di addebitare sulla carta del cliente).
Roscius,

8

Poiché si tratta di un'estensione personalizzata, possiamo trovare una soluzione personalizzata (leggi: hack) per ritentare il salvataggio senza modificare i file core.

Ho risolto tutti i miei problemi di deadlock con i seguenti due metodi aggiunti a una classe di supporto. Invece di chiamare $product->save()ora chiamo 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);
}

Ciò compie due cose distinte: mette in coda un nuovo tentativo quando si verifica un deadlock e imposta un timeout esponenzialmente crescente per quel nuovo tentativo. Imposta inoltre il livello di isolamento della transazione. Ci sono molte informazioni su SO e su DBA.SE per ulteriori informazioni sui livelli di isolamento delle transazioni di MySQL.

FWIW, non ho riscontrato un deadlock da allora.


1
@Mage :: getModel ('core / resource') @ dovrebbe creare una nuova connessione. Non capisco come possa cambiare l'attuale livello di isolamento delle transazioni.
regalo

@giftnuss abbastanza giusto. Dovrebbe essere singleton di sicuro. Sentiti libero di contribuire con questo sul mio modulo deadlock su github
philwinkle

@philwinkle grazie per quest'uomo. Sto cercando di capire se un aggiornamento EE 1.13 risolverà i miei guai o se dovrei esaminare anche questo. So che 1.13 esegue l'indicizzazione in modo asincrono, il che è fantastico, ma se sono coinvolte le stesse query sottostanti, faccio fatica a capire come il solo asincrono impedirebbe che si verifichino deadlock.
Kalenjordan,

1
@kalenjordan è una combinazione di asincrono e il varien db modificato in 1.8 / 1.13 che riduce la probabilità di deadlock.
Filwinkle,

Penso che ti sei dimenticato di passare $triesa questa funzionesleep($this->getDelay());
Tahir Yasin,

3

Nei forum di Magento parlano della modifica di un file di libreria Zend: lib / Zend / Db / Statement / Pdo.php

La funzione _execute originale:

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
    }

Dopo la modifica:

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

Come puoi vedere, l'unica cosa che è stata cambiata è che $ try è stato spostato al di fuori del ciclo.

Come sempre, si consiglia di provarlo in un ambiente di sviluppo / test e di non distribuire immediatamente questa correzione in un ambiente di produzione.


2
Mi preoccupo di modificare i file di framework sottostanti, sembra che il tentativo dovrebbe invece avvenire a livello di codice Magento.
Roscius,

Abbiamo provato la soluzione suggerita e in realtà ha impedito a questo particolare deadlock di causare problemi. Stavamo anche ricevendo deadlock sui salvataggi di sales_flat_order_grid, con questa soluzione in atto, che invece lanciano violazioni di integrità, il che ovviamente non va bene.
Peter O'Callaghan,

2

Ho questo stesso problema su un sito Magento 1.11 e ho un biglietto aperto con Magento su di esso dall'11 / 12/2012. Hanno confermato che si tratta di un problema e si suppone che stia creando una patch.

La mia domanda è: perché il prezzo deve essere reindicizzato in questo momento? Non penso sia necessario:

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

1
Se un prodotto esaurisce le scorte e non si prevede che i prodotti esauriti vengano mostrati nel catalogo, credo che siano nascosti per merito di non avere record di indice dei prezzi che finiscono per escluderli dalla raccolta di prodotti quando il prezzo viene unito su di esso .
davidalger,

Questo non risponde alla domanda. Sembra che tu stia cercando di aggiungere ulteriori informazioni alla domanda originale. Forse questa informazione sarebbe migliore come commento alla domanda originale.
Luke Mills,

Sono con te, Kim. Ho aperto lo stesso biglietto dall'11 / 2011.
Filwinkle,

So che tecnicamente non è una risposta ma una sotto-domanda, tuttavia risponde alla domanda che fa riferimento a questa domanda come duplicata! Quindi Kimberly Thomas e davidalger ottengono il mio voto per aver risposto al mio specifico "Perché sono i prezzi di reindicizzazione?" domanda che io '; attualmente sto googling! Grazie!
cygnus digital il

0

Abbiamo avuto un problema di deadlock simile quando sono state effettuate determinate chiamate durante un reindicizzazione. Per noi si è manifestato soprattutto quando un cliente avrebbe aggiunto qualcosa al carrello. Sebbene probabilmente non stia risolvendo l'effettivo problema di fondo, l'implementazione della reindicizzazione asincrona ha completamente bloccato tutte le chiamate di deadlock che stavamo vedendo in precedenza. Dovrebbe funzionare come uno stop-gap fino a quando il problema di fondo non viene risolto e trasferito alle edizioni EE / CE (abbiamo finito per acquistare un'estensione per farlo).


0

Ti suggerisco di installare Philwinkle DeadlockRetry. Ha funzionato per il nostro database.

https://github.com/philwinkle/Philwinkle_DeadlockRetry

Suggerirei anche di guardare i tuoi programmi esterni che colpiscono la tua API Web. Ne avevamo uno che stava aggiornando il QTY per i prodotti e causava molti deadlock. L'abbiamo riscritto e siamo andati direttamente al database.


1
Questo repository non è più supportato, ma per fortuna ne consiglia la sostituzione github.com/AOEpeople/Aoe_DbRetry .
Oca,

-1

L'anno scorso ho incontrato il problema dei deadlock molte volte l'ho risolto semplicemente aumentando la memoria per il nostro server perché il processo di indicizzazione consuma tutte le risorse.

Dovresti anche noi asincraggiare la soluzione reindex che ho usato miravist

Per un sistema più stabile, dovresti pensare di separare il tuo backend dal frontend in modo che non si mangino reciprocamente la RAM.

Per la mia esperienza, non è un problema di codice sorgente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.