La réindexation des prix entraîne des blocages de base de données lors de la validation de la commande.


47

Je rencontre un problème pour lequel je pense que le processus de réindexation du prix du produit est à l'origine d'une exception d'interblocage dans le processus de paiement.

J'ai attrapé cette exception dans le processus de commande:

Exception de conversion de commande: SQLSTATE [40001]: Échec de la sérialisation: 1213 Impasse trouvée lors de la tentative d'obtention du verrouillage; essayez de redémarrer la transaction

Malheureusement, je n'ai pas de trace de pile complète car l'exception a été interceptée, mais en vérifiant l'état INNODB, j'ai pu localiser l'impasse:

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` 

Le verrou de table demandant SQL est finalement généré à partir du Mage_CatalogInventory_Model_Stock::registerProductsSale()moment où il tente d'obtenir le nombre d'inventaire actuel afin de le décrémenter.

Au moment où le blocage s'est produit, le processus de réindexation du prix du produit était en cours d'exécution et je suppose qu'il existait un verrou de lecture sur le catalog_product_entity tablequi a provoqué le blocage. Si je comprends correctement le blocage, tout verrou de lecture entraîne un blocage, mais le réindexage du prix du produit le verrouille pendant un temps raisonnable, car le site contient environ 50 000 produits.

Malheureusement, à ce stade du flux de code de paiement, la carte de crédit du client a été débitée (via un module de paiement personnalisé) et la création de l'objet de commande correspondant a échoué.

Mes questions sont:

  • La logique du module de paiement personnalisé est-elle défectueuse? Par exemple, existe-t-il un flux accepté permettant à Magento de convertir le devis en une exception de commande sans frais préalable avant de valider le paiement par le moyen de paiement (carte de crédit)?

Edit: Il semble que la logique du module de paiement soit effectivement défectueuse, car l'appel à $ paymentmethod-> authorize () devrait avoir lieu après le lieu où cette impasse s'est produite, pas avant (comme l'indique la réponse d'Ivan ci-dessous). Cependant, la transaction sera toujours bloquée par l’impasse (même sans les frais erronés sur la carte de crédit).

  • Cet appel de fonction $stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true);en Mage_CatalogInventory_Model_Stock::registerProductsSale()fait une lecture verrouillée, quel danger y aurait-il à en faire une lecture non verrouillable?

  • En recherchant une réponse sur le Web, plusieurs endroits ont suggéré de ne pas exécuter une réindexation complète tant que le site est à chaud; semble à peine être une bonne solution; La question de l'indexation provoquant des blocages de table et des conflits de verrous est-elle un problème connu dans Magento? Existe-t-il des solutions de contournement?

Edit: Il semble que la question restante ici est celle de la troisième question; réindexation provoquant des blocages de table. Vous cherchez des solutions de contournement pour cela.

Edit: Le concept selon lequel les blocages ne sont pas des problèmes en soi, mais plutôt que la réponse à ces problèmes devrait être au centre des débats, a beaucoup de sens. Enquêtant plus avant pour trouver un point dans le code pour attraper l'exception d'interblocage et réémettre la demande. Faire cela au niveau de l’adaptateur de base de données Zend Framework est une approche, mais je cherche aussi un moyen de le faire dans le code Magento pour faciliter la maintenabilité.

Il y a un correctif intéressant dans ce fil de discussion: http://www.magentocommerce.com/boards/viewthread/31666/P0/ qui semble résoudre une situation de blocage similaire (mais pas celle-ci en particulier).

Edit: Apparemment, l’impasse a été corrigée dans CE 1.8 Alpha. Je cherche toujours une solution de contournement jusqu'à ce que cette version soit sortie de Alpha


Nous nous débattons récemment avec un problème similaire. Quelle extension de paiement utilisez-vous?
Peter O'Callaghan

C'est une extension codée sur mesure
Roscius

1
@kalenjordan L'amélioration de l'indexation dans la version 1.13 et un schéma de réessai comme celui de philwinkle ci-dessous ont largement atténué le problème pour moi.
Roscius

1
@Roscius à peu près combien l'ont-ils atténué? Je vois des défaillances de base de données (délai de connexion, délai d'attente de verrouillage, blocage) affectant environ 0,2% de mes commandes. Très rare mais je veux vraiment le résoudre complètement.
kalenjordan

Réponses:


16

Il est fort probable que votre méthode de paiement traite mal le paiement.

Le processus de sauvegarde des commandes Magento est assez simple:

  • Prépare toutes les données qui doivent être transférées d'un article de devis à un article de commande, y compris les prix et les informations sur le produit. Après cela, la récupération du prix n'est pas invoquée.
  • Invoquer avant que l'ordre soumette des événements checkout_type_onepage_save_orderetsales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() est appelé à cet événement observateur
  • Démarrer la transaction de base de données
  • Invoke $order->place()méthode qui traite le paiement en appelant $paymentMethod->authorize(), $paymentMethod->capture()ou $paymentMethod->initialize()dépend de sa logique.
  • Invoquez la méthode $ order-> save () qui enregistre l'ordre traité dans les tables de base de données sales_flat_order_*.
  • Commit DB transaction (À cette étape, la base de données libère le verrouillage de la table d'inventaire)

Donc, comme vous le voyez, il est impossible que le mode de paiement charge l’argent avant le verrouillage des stocks et la lecture des prix ou des informations sur les produits.

Cela n’est possible que si le mode de paiement est implémenté de manière à charger les produits lui-même avec les prix, une fois que l’appel API pour la facturation a été effectué.

J'espère que cela vous aidera à résoudre votre problème.

Quant à la réindexation, elle devrait être sûre, si vous n’avez pas ce problème avec la méthode de paiement. Depuis l'opération de lecture, les verrous sont effectués avant que l'argent ne soit débité.


1
Merci, il semble que la logique du module de paiement personnalisé soit un peu désactivée. Il semble toutefois toujours qu'un processus d'indexation bloquera la commande en provoquant une exception registerProductsSale()(comprendre que, avec les corrections apportées au module de paiement personnalisé, le problème de la facturation de la carte du client disparaîtra).
Roscius

8

S'agissant d'une extension personnalisée, nous pouvons trouver une solution de contournement personnalisée (read: hack) pour réessayer l'enregistrement sans modifier les fichiers de base.

J'ai résolu tous mes problèmes d'impasse avec les deux méthodes suivantes ajoutées à une classe d'assistance. Au lieu d'appeler, $product->save()j'appelle maintenant 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);
}

Cela accomplit deux choses distinctes: il met en file d'attente une nouvelle tentative lorsqu'un blocage est rencontré et il définit un délai exponentiellement croissant pour cette nouvelle tentative. Il définit également le niveau d'isolation de transaction. Il existe de nombreuses informations sur SO et sur DBA.SE pour plus d'informations sur les niveaux d'isolation des transactions de MySQL.

FWIW, je n’ai pas rencontré d’impasse depuis.


1
@Mage :: getModel ('core / resource') @ devrait créer une nouvelle connexion. Je ne comprends pas comment cela peut changer le niveau d'isolation actuel de la transaction.
cadeau

@giftnuss assez juste. Devrait être singleton à coup sûr. N'hésitez pas à contribuer ceci sur mon module d'interblocage sur github
philwinkle

@philwinkle merci pour cet homme. J'essaie de déterminer si une mise à niveau EE 1.13 résoudra mes problèmes ou si je devrais également examiner la question. Je sais que la 1.13 indexe de manière asynchrone, ce qui est très bien, mais si les mêmes requêtes sous-jacentes sont impliquées, j'ai du mal à comprendre comment le simple async pourrait empêcher les blocages de se produire.
Kalenjordan

1
@kalenjordan c’est une combinaison de modifications asynchrones et variennes mises à jour dans la version 1.8 / 1.13 qui réduit le risque d’impasses.
philwinkle

Je pense que vous avez oublié de passer $triesà cette fonctionsleep($this->getDelay());
Tahir Yasin

3

Sur les forums Magento, ils parlent de l'édition d'un fichier de bibliothèque Zend: lib / Zend / Db / Statement / Pdo.php

La fonction _execute d'origine:

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
    }

Après modification:

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

Comme vous pouvez le constater, la seule chose qui a changé est que le $ try a été déplacé en dehors de la boucle.

Comme toujours, il est suggéré de l'essayer sur un environnement de développement / test et de ne pas déployer instantanément ce correctif sur un environnement de production.


2
Je m'inquiète de la modification des fichiers de structure sous-jacents, il semble que la nouvelle tentative devrait plutôt se produire au niveau du code Magento.
Roscius

Nous avons essayé le correctif suggéré et cela a effectivement empêché cette impasse particulière de causer des problèmes. Nous recevions également des impasses sur les sauvegardes de sales_flat_order_grid. Avec ce correctif en place, ils lancent à la place des violations de contre-intégrité, ce qui n'est évidemment pas bon.
Peter O'Callaghan le

2

J'ai le même problème sur un site Magento 1.11 et j'ai un ticket ouvert avec Magento dessus depuis le 11/12/2012. Ils ont confirmé qu'il s'agissait d'un problème et sont supposés créer un correctif.

Ma question est la suivante: pourquoi le prix doit-il être réindexé en ce moment? Je ne pense pas que cela soit nécessaire:

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

1
Si un produit tombe en rupture de stock et que les produits en rupture de stock ne sont pas censés figurer dans le catalogue, je pense qu'ils sont cachés par le fait de ne pas avoir d'indices de prix indexés qui les excluent de la collection lorsque le prix est associé. .
davidalger

Cela ne répond pas à la question. On dirait que vous essayez d'ajouter des informations supplémentaires à la question initiale. Peut-être que cette information serait mieux comme commentaire sur la question initiale.
Luke Mills

Je suis avec toi, Kim. J'ai le même billet ouvert depuis 11/2011.
philwinkle

Je sais que ce n’est pas techniquement une réponse, mais une sous-question, mais cela répond à la question qui fait référence à cette question en double! Kimberly Thomas et davidalger obtiennent donc mon vote positif en réponse à ma question "Pourquoi réindexe-t-il les prix?" question que je suis actuellement googler! Merci!
Cygnus numérique

0

Nous avons eu une impasse similaire lorsque certains appels ont été passés lors d'une réindexation. Pour nous, cela se manifestait surtout lorsqu'un client ajoutait quelque chose au panier. Bien que le problème sous-jacent réel ne soit probablement pas résolu, la mise en œuvre de la réindexation asynchrone a complètement stoppé tous les appels d'interblocage précédemment observés. Devrait fonctionner comme une pause jusqu'à ce que le problème sous-jacent soit résolu et transmis aux éditions EE / CE (nous avons fini par acheter une extension pour le faire).


0

Je vous suggère d'installer Philwinkle DeadlockRetry. Cela a fonctionné pour notre base de données.

https://github.com/philwinkle/Philwinkle_DeadlockRetry

Je suggérerais également de regarder vos programmes extérieurs sur votre API Web. Nous en avions un qui mettait à jour la QTY pour les produits et causait de nombreuses impasses. Nous avons réécrit cela et sommes allés directement à la base de données.


1
Ce référentiel n'est plus pris en charge, mais heureusement, il recommande son remplacement github.com/AOEpeople/Aoe_DbRetry .
Goose

-1

L’année dernière, j’ai rencontré plusieurs problèmes de blocage, j’ai réglé ce problème simplement en augmentant la mémoire de notre serveur, car le processus d’indexation consomme toutes les ressources.

Vous devriez également utiliser la solution de réindexation asynchrone que j'ai utilisée miravist

Pour un système plus stable, vous devriez penser à séparer votre serveur principal du serveur principal afin qu’ils ne mangent pas la RAM de l’autre.

Pour mon expérience, ce n'est pas un problème de code source.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.