ดัชนีราคาใหม่เป็นสาเหตุทำให้ 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 รายการ

น่าเสียดายที่ ณ จุดนี้ในขั้นตอนการชำระเงินบัตรเครดิตของลูกค้าถูกเรียกเก็บเงิน (ผ่านโมดูลการชำระเงินที่กำหนดเอง) และการสร้างวัตถุใบสั่งที่สอดคล้องกันนั้นล้มเหลว

คำถามของฉันคือ:

  • ตรรกะโมดูลการชำระเงินที่กำหนดเองเป็นความผิดพลาดหรือไม่? ie มีกระแสยอมรับหรือไม่ที่จะทำให้มั่นใจได้ว่าวีโอไอพีสามารถแปลงใบเสนอราคาเป็นข้อยกเว้นการสั่งซื้อได้ฟรีก่อนที่จะทำการเรียกเก็บเงินเป็นวิธีการชำระเงิน (บัตรเครดิต)?

แก้ไข: ดูเหมือนว่าตรรกะโมดูลการชำระเงินเป็นความผิดพลาดอย่างแน่นอนเนื่องจากการเรียกไปยัง $ paymentmethod-> Authorize () ควรเกิดขึ้นหลังจากที่สถานที่ซึ่งการหยุดชะงักนี้เกิดขึ้นไม่ใช่ก่อนหน้า (ตามคำตอบของ Ivan ด้านล่าง) อย่างไรก็ตามการทำธุรกรรมจะยังคงถูกปิดกั้นโดยการหยุดชะงัก (แม้ว่าจะไม่มีการเรียกเก็บเงินจากบัตรเครดิต)

  • ฟังก์ชั่นนี้เรียกใช้$stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true);ในMage_CatalogInventory_Model_Stock::registerProductsSale()การอ่านล็อคว่ามันอันตรายแค่ไหนที่จะทำให้มันอ่านไม่ล็อค?

  • ในการค้นหาคำตอบของเว็บไซต์สองแห่งไม่แนะนำให้ใช้การจัดทำดัชนีแบบเต็มใหม่ในขณะที่ไซต์กำลังร้อนแรง ดูเหมือนว่าจะเป็นทางออกที่ดี เป็นปัญหาของการจัดทำดัชนีที่ทำให้เกิดการหยุดชะงักของตารางและล็อคการช่วงชิงปัญหาที่ทราบใน Magento มีวิธีแก้ไขหรือไม่

แก้ไข: ดูเหมือนคำถามที่เหลืออยู่ที่นี่เป็นคำถามจากคำถามที่สาม; การสร้างดัชนีใหม่ทำให้ตารางหยุดชะงัก กำลังมองหาวิธีแก้ปัญหาสำหรับสิ่งนี้

แก้ไข:แนวคิดที่การหยุดชะงักไม่ได้อยู่ในตัวของมันเอง แต่การตอบสนองต่อพวกมันควรจะเป็นจุดสนใจ การตรวจสอบเพิ่มเติมเพื่อหาจุดในรหัสเพื่อตรวจจับข้อยกเว้น deadlock และออกคำร้องขอใหม่ การทำสิ่งนี้ในระดับอแด็ปเตอร์ Zend Framework DB เป็นวิธีหนึ่ง แต่ฉันกำลังมองหาวิธีการทำเช่นนี้ในรหัส Magento เพื่อความสะดวกในการบำรุงรักษา

มีแพทช์ที่น่าสนใจในหัวข้อนี้: http://www.magentocommerce.com/boards/viewthread/31666/P0/ที่ดูเหมือนว่าจะแก้ปัญหาการหยุดชะงักที่เกี่ยวข้อง (แต่ไม่ใช่อันนี้โดยเฉพาะ)

แก้ไข: การหยุดชะงักอย่างเห็นได้ชัดได้รับการแก้ไขในระดับหนึ่งใน CE 1.8 Alpha ยังคงมองหาวิธีแก้ปัญหาจนกว่ารุ่นนี้จะไม่ใช้ Alpha


เราได้ต่อสู้กับปัญหาที่คล้ายกันเมื่อเร็ว ๆ นี้ส่วนขยายการชำระเงินที่คุณใช้อยู่คืออะไร
Peter O'Callaghan

มันเป็นนามสกุลที่กำหนดเอง
Roscius

1
@kalenjordan การปรับดัชนีใน 1.13 และรูปแบบการลองอีกครั้งเช่น philwinkle ด้านล่างช่วยลดปัญหาส่วนใหญ่สำหรับฉันได้
Roscius

1
@ Oscius ประมาณพวกเขาลดลงเท่าไหร่? ฉันเห็นความล้มเหลวของการเรียงลำดับฐานข้อมูล (หมดเวลาการเชื่อมต่อหมดเวลารอการล็อคและการหยุดชะงัก) มีผลต่อคำสั่งซื้อของฉันประมาณ 0.2% หายากมาก แต่ฉันต้องการได้รับการแก้ไขอย่างสมบูรณ์
kalenjordan

คำตอบ:


16

มีความเป็นไปได้ค่อนข้างใหญ่ที่วิธีการชำระเงินของคุณกำลังประมวลผลการชำระเงินผิดพลาด

กระบวนการบันทึก Magento Order นั้นค่อนข้างง่าย:

  • เตรียมข้อมูลทั้งหมดที่ควรจะโอนจากรายการเสนอราคาไปยังรายการสั่งซื้อรวมถึงราคาและข้อมูลผลิตภัณฑ์หลังจากนั้นจะไม่เรียกใช้การดึงราคา
  • เรียกใช้ก่อนสั่งซื้อส่งกิจกรรมcheckout_type_onepage_save_orderและsales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() ถูกเรียกใช้ที่ผู้สังเกตการณ์เหตุการณ์นี้
  • เริ่มธุรกรรม DB
  • เรียก$order->place()วิธีการที่ประมวลผลการชำระเงินโดยการเรียก$paymentMethod->authorize(), $paymentMethod->capture()หรือ$paymentMethod->initialize()ขึ้นอยู่กับตรรกะของ
  • วิงวอน $ การสั่งซื้อ> บันทึก () sales_flat_order_*วิธีการที่จะช่วยประหยัดการประมวลผลเพื่อให้ตาราง
  • ทำรายการฐานข้อมูล DB (ในขั้นตอนนี้ DB จะปลดล็อคในตารางสินค้าคงคลัง)

ดังที่คุณเห็นเป็นไปไม่ได้วิธีการชำระเงินจะเรียกเก็บเงินก่อนล็อคสินค้าคงคลังและอ่านราคาผลิตภัณฑ์หรือข้อมูลผลิตภัณฑ์

เป็นไปได้เฉพาะในกรณีที่วิธีการชำระเงินถูกดำเนินการในลักษณะที่จะทำการโหลดสินค้าด้วยราคาหลังจากการเรียก API สำหรับการดำเนินการเรียกเก็บเงิน

หวังว่านี่จะช่วยคุณในการแก้ไขข้อบกพร่องของคุณ

สำหรับการทำดัชนีใหม่นั้นควรปลอดภัยหากคุณไม่มีปัญหาในการชำระเงิน เนื่องจากการดำเนินการอ่านที่ขึ้นอยู่กับการล็อคจะดำเนินการก่อนที่เงินจะถูกเรียกเก็บเงิน


1
ขอบคุณดูเหมือนว่าตรรกะโมดูลการชำระเงินที่กำหนดเองนั้นปิดไปเล็กน้อย อย่างไรก็ตามดูเหมือนว่ากระบวนการจัดทำดัชนีจะบล็อกการชำระเงินโดยทำให้เกิดข้อยกเว้นregisterProductsSale()(เข้าใจว่าการแก้ไขโมดูลการชำระเงินที่กำหนดเองจะช่วยขจัดปัญหาการเรียกเก็บเงินจากบัตรของลูกค้า)
Roscius

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 ทำดัชนีแบบอะซิงโครนัสซึ่งยอดเยี่ยม แต่ถ้ามีการสืบค้นพื้นฐานเดียวกันฉันมีเวลายากที่จะเข้าใจว่า async เพียงอย่างเดียวจะป้องกันไม่ให้เกิดการหยุดชะงักได้อย่างไร
kalenjordan

1
@kalenjordan มันเป็นการรวมกันของ async และการเปลี่ยนแปลงฐานข้อมูลการเปลี่ยนแปลงที่ปรับปรุงแล้วใน 1.8 / 1.13 ที่ลดโอกาสในการหยุดชะงัก
philwinkle

ฉันคิดว่าคุณลืมส่ง$triesต่อฟังก์ชั่นนี้sleep($this->getDelay());
Tahir Yasin

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

ในขณะที่คุณเห็นสิ่งเดียวที่มีการเปลี่ยนแปลงคือ $ try ถูกย้ายออกไปนอกวง

เช่นเคยขอแนะนำให้ลองทำสิ่งนี้ในสภาพแวดล้อมการพัฒนา / ทดสอบและไม่ปรับใช้การแก้ไขนี้ทันทีบนสภาพแวดล้อมการผลิต


2
ฉันกังวลเกี่ยวกับการแก้ไขไฟล์เฟรมเวิร์กพื้นฐานดูเหมือนว่าการลองใหม่ควรเกิดขึ้นในระดับรหัสวีโอไอพีแทน
Roscius

เราได้ลองแก้ไขที่แนะนำแล้วและมันก็ป้องกันไม่ให้เกิดการหยุดชะงักโดยเฉพาะอย่างยิ่งจากสาเหตุของปัญหา นอกจากนี้เรายังได้รับการหยุดชะงักเมื่อบันทึกลงใน sales_flat_order_grid ด้วยการแก้ไขนี้พวกเขาแทนที่จะโยนการละเมิดความซื่อสัตย์ซึ่งเป็นสิ่งที่เห็นได้ชัดว่าไม่ดี
Peter O'Callaghan

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

ฉันรู้ว่านี่ไม่ใช่เทคนิคคำตอบ แต่เป็นคำถามย่อย แต่จะตอบคำถามที่อ้างอิงคำถามนี้เป็นคำถามซ้ำ! ดังนั้น Kimberly Thomas และ davidalger ทำให้ upvote ของฉันตอบคำถามเฉพาะของฉัน "ทำไมจึงต้องทำดัชนีราคาใหม่" คำถามที่ I '; กำลัง googling! ขอบคุณ!
Cygnus digital

0

เรามีปัญหาการหยุดชะงักคล้ายกันเมื่อมีการโทรบางสายระหว่างการสร้างดัชนีใหม่ สำหรับเราแล้วมันประจักษ์เองเป็นส่วนใหญ่เมื่อลูกค้าจะเพิ่มบางสิ่งบางอย่างในรถเข็น ในขณะที่อาจไม่แก้ไขปัญหาพื้นฐานที่แท้จริงการใช้การทำดัชนีแบบอะซิงโครนัสใหม่ได้หยุดการโทรที่หยุดชะงักทั้งหมดที่เราเคยเห็นมาก่อน ควรทำงานเป็น stop-gap จนกว่าปัญหาพื้นฐานจะได้รับการแก้ไขและส่งไปยังรุ่น EE / CE (เราสิ้นสุดการซื้อส่วนขยายเพื่อดำเนินการ)


0

ฉันแนะนำให้คุณติดตั้ง Philwinkle DeadlockRetry มันใช้งานได้กับฐานข้อมูลของเรา

https://github.com/philwinkle/Philwinkle_DeadlockRetry

ฉันยังขอแนะนำให้ดูที่โปรแกรมภายนอกของคุณกดปุ่ม API บนเว็บของคุณ เรามีสิ่งหนึ่งที่ได้รับการอัปเดตจำนวนสำหรับผลิตภัณฑ์และทำให้เกิดการหยุดชะงักจำนวนมาก เราเขียนใหม่อีกครั้งและไปที่ฐานข้อมูลโดยตรง


1
repo นี้ไม่ได้รับการสนับสนุน แต่โชคดีที่มันแนะนำมันทดแทนgithub.com/AOEpeople/Aoe_DbRetry
Goose

-1

ฉันพบปัญหาการหยุดชะงักเมื่อปีที่แล้วหลายครั้งที่ฉันแยกแยะโดยเพิ่มหน่วยความจำสำหรับเซิร์ฟเวอร์ของเราเพราะกระบวนการจัดทำดัชนีกินทรัพยากรทั้งหมด

คุณควรที่จะแก้ปัญหา async reindex ของฉันด้วย miravist

สำหรับระบบที่มีเสถียรภาพมากขึ้นคุณควรคิดถึงการแยกแบ็กเอนด์ออกจากส่วนหน้าดังนั้นพวกเขาจึงไม่กิน RAM ของกันและกัน

สำหรับประสบการณ์ของฉันมันไม่ใช่ปัญหาของซอร์สโค้ด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.