MySQL InnoDB READ COMMITTED में भी डिलीट पर प्राइमरी की


11

प्रस्तावना

हमारा एप्लिकेशन DELETEसमानांतर में प्रश्नों को निष्पादित करने वाले कई थ्रेड चलाता है । प्रश्न पृथक डेटा को प्रभावित करते हैं, अर्थात ऐसी कोई संभावना नहीं होनी चाहिए कि DELETEअलग-अलग थ्रेड्स से समान पंक्तियों पर समवर्ती हो । हालांकि, प्रति प्रलेखन MySQL DELETEबयानों के लिए तथाकथित 'अगली-कुंजी' लॉक का उपयोग करता है , जो मिलान कुंजी और कुछ अंतराल दोनों को लॉक करता है। यह चीज़ डेड-लॉक की ओर ले जाती है और एकमात्र समाधान जो हमने पाया है वह READ COMMITTEDआइसोलेशन स्तर का उपयोग करना है।

समस्या

भारी तालिकाओं के DELETEसाथ जटिल कथनों को निष्पादित करते समय समस्या उत्पन्न होती है JOIN। किसी विशेष मामले में हमारे पास चेतावनी के साथ एक तालिका है जिसमें केवल दो पंक्तियाँ हैं, लेकिन क्वेरी को उन सभी चेतावनियों को छोड़ने की आवश्यकता है जो दो अलग-अलग INNER JOINएड तालिकाओं से कुछ विशेष संस्थाओं से संबंधित हैं । क्वेरी निम्नानुसार है:

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
   ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
   ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=? AND dp.dirty_data=1

जब दिन_पोजिशन टेबल काफी बड़ी होती है (मेरे परीक्षण के मामले में 1448 पंक्तियाँ होती हैं) तो READ COMMITTEDअलगाव मोड के साथ कोई भी लेन-देन पूरी proc_warnings तालिका को ब्लॉक कर देता है ।

इस नमूना डेटा पर समस्या को हमेशा पुन: प्रस्तुत किया जाता है - http://yadi.sk/d/QDuwBtpW1BxB9 दोनों MySQL 5.1 (5.1.59 पर जांचा गया) और MySQL 5.5 (MySQL 5.5.24 पर जाँच) में।

संपादित करें: लिंक किए गए नमूना डेटा में क्वेरी तालिकाओं के लिए स्कीमा और अनुक्रमित शामिल हैं, जो सुविधा के लिए यहां प्रस्तुत किए गए हैं:

CREATE TABLE  `proc_warnings` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `transaction_id` int(10) unsigned NOT NULL,
    `warning` varchar(2048) NOT NULL,
    PRIMARY KEY (`id`),
    KEY `proc_warnings__transaction` (`transaction_id`)
);

CREATE TABLE  `day_position` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `transaction_id` int(10) unsigned DEFAULT NULL,
    `sort_index` int(11) DEFAULT NULL,
    `ivehicle_day_id` int(10) unsigned DEFAULT NULL,
    `dirty_data` tinyint(4) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `day_position__trans` (`transaction_id`),
    KEY `day_position__is` (`ivehicle_day_id`,`sort_index`),
    KEY `day_position__id` (`ivehicle_day_id`,`dirty_data`)
) ;

CREATE TABLE  `ivehicle_days` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `d` date DEFAULT NULL,
    `sort_index` int(11) DEFAULT NULL,
    `ivehicle_id` int(10) unsigned DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `ivehicle_days__is` (`ivehicle_id`,`sort_index`),
    KEY `ivehicle_days__d` (`d`)
);

प्रति लेनदेन क्वेरी निम्नानुसार हैं:

  • लेन-देन १

    set transaction isolation level read committed;
    set autocommit=0;
    begin;
    DELETE pw 
    FROM proc_warnings pw 
    INNER JOIN day_position dp 
        ON dp.transaction_id = pw.transaction_id 
    INNER JOIN ivehicle_days vd 
        ON vd.id = dp.ivehicle_day_id 
    WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;
  • लेन-देन २

    set transaction isolation level read committed;
    set autocommit=0;
    begin;
    DELETE pw 
    FROM proc_warnings pw 
    INNER JOIN day_position dp 
        ON dp.transaction_id = pw.transaction_id 
    INNER JOIN ivehicle_days vd 
        ON vd.id = dp.ivehicle_day_id 
    WHERE vd.ivehicle_id=13 AND dp.dirty_data=1;

उनमें से एक हमेशा 'लॉक वेट टाइमआउट पार हो गया ...' त्रुटि के साथ विफल रहता है। information_schema.innodb_trxनिम्नलिखित पंक्तियां हैं:

| trx_id     | trx_state   | trx_started           | trx_requested_lock_id  | trx_wait_started      | trx_wait | trx_mysql_thread_id | trx_query |
| '1A2973A4' | 'LOCK WAIT' | '2012-12-12 20:03:25' | '1A2973A4:0:3172298:2' | '2012-12-12 20:03:25' | '2'      | '3089'              | 'DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1' |
| '1A296F67' | 'RUNNING'   | '2012-12-12 19:58:02' | NULL                   | NULL | '7' | '3087' | NULL |

information_schema.innodb_locks

| lock_id                | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |

जैसा कि मैं देख सकता हूं कि दोनों प्रश्न Xप्राथमिक कुंजी = 53 के साथ एक पंक्ति पर एक विशेष लॉक चाहते हैं । हालांकि, उनमें से किसी को भी proc_warningsतालिका से पंक्तियों को नहीं हटाना चाहिए । मुझे अभी समझ में नहीं आया है कि सूचकांक क्यों बंद है। इसके अलावा, इंडेक्स को तब भी लॉक नहीं किया जाता है जब proc_warningsटेबल खाली हो या day_positionटेबल में पंक्तियों की संख्या कम हो (यानी एक सौ पंक्तियाँ)।

आगे की जांच EXPLAINसमान SELECTक्वेरी पर चलने की थी । यह दिखाता है कि क्वेरी ऑप्टिमाइज़र क्वेरी proc_warningsटेबल पर इंडेक्स का उपयोग नहीं करता है और यही एकमात्र कारण है जिसकी मैं कल्पना कर सकता हूं कि यह पूरे प्राथमिक कुंजी इंडेक्स को ब्लॉक क्यों करता है।

सरलीकृत मामला

समस्या को एक सरल मामले में भी पुन: प्रस्तुत किया जा सकता है, जब कुछ दो ही तालिकाएँ होती हैं, लेकिन चाइल्ड टेबल में पैरेंट टेबल रेफरी कॉलम पर कोई इंडेक्स नहीं होता है।

parentतालिका बनाएं

CREATE TABLE `parent` (
  `id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

childतालिका बनाएं

CREATE TABLE `child` (
  `id` int(10) unsigned NOT NULL,
  `parent_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

टेबल भरें

INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);

दो समानांतर लेनदेन में परीक्षण:

  • लेन-देन १

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 1;
  • लेन-देन २

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 2;

दोनों मामलों में आम बात यह है कि MySQL सूचकांकों का उपयोग नहीं करता है। मेरा मानना ​​है कि पूरे टेबल के लॉक का कारण है।

हमारा समाधान

एकमात्र समाधान जिसे हम अभी देख सकते हैं, थ्रेड फिनिश को साफ करने के लिए डिफ़ॉल्ट लॉक प्रतीक्षा समय 50 सेकंड से 500 सेकंड तक बढ़ा सकते हैं। फिर उंगलियों को पार करते रहें।

किसी भी मदद की सराहना की।


मेरे पास एक प्रश्न है: क्या आपने किसी भी लेनदेन में COMMIT निष्पादित किया है?
रोलैंडमाइसीडीडीबीए

बेशक। समस्या यह है कि अन्य सभी लेनदेन को तब तक इंतजार करना चाहिए जब तक उनमें से कोई भी बदलाव नहीं करता है। साधारण परीक्षण के मामले में इस मुद्दे को पुन: पेश करने के तरीके को दिखाने के लिए एक प्रतिबद्ध बयान शामिल नहीं है। अगर आप नॉन-वेटिंग ट्रांजैक्शन में कमिट या रोलबैक करते हैं तो यह एक साथ लॉक रिलीज करता है और वेटिंग ट्रांजैक्शन पूरी हो जाती है।
१६

जब आप कहते हैं कि MySQL किसी भी मामले में सूचकांकों का उपयोग नहीं करता है, तो क्या यह इसलिए है क्योंकि वास्तविक परिदृश्य में कोई भी नहीं है? यदि अनुक्रमणिका हैं, तो क्या आप उनके लिए कोड प्रदान कर सकते हैं? क्या नीचे पोस्ट किए गए सूचकांक सुझावों में से किसी को आज़माना संभव है? यदि कोई अनुक्रमणिका नहीं हैं, और किसी भी जोड़ने की कोशिश करना संभव नहीं है, तो MySQL प्रत्येक थ्रेड द्वारा संसाधित डेटा सेट को प्रतिबंधित नहीं कर सकता है। अगर ऐसा है तो एन थ्रेड्स केवल सर्वर कार्यभार को एन टाइम से गुणा करेंगे, और यह केवल एक थ्रेड को पैरामीटर सूची के साथ चलाने के लिए अधिक कुशल होगा, जैसे {WHERE vd.ivehicle_id IN (2, 13) और dD.dirty_data = 1;}।
जेएम हिक्स

ठीक है, पाया गया कि लिंक किए गए सैंपल डेटा फ़ाइल में अनुक्रमणिका दूर है।
जेएम हिक्स

कुछ और प्रश्न: 1) day_positionटेबल में सामान्य रूप से कितनी पंक्तियाँ होती हैं, जब यह इतनी धीमी गति से चलने लगती है कि आपको टाइमआउट की सीमा 500 सेकंड तक काटनी पड़ती है? 2) जब आपके पास केवल नमूना डेटा है, तो इसे चलाने में कितना समय लगेगा?
जेएम हिक्स

जवाबों:


3

नई उत्तर (MySQL शैली गतिशील एसक्यूएल): ठीक है, यह एक दूसरे के वर्णित पोस्टर के तरीके में समस्या से निपटता है - उस क्रम को पलटते हुए जिसमें पारस्परिक रूप से असंगत अनन्य ताले प्राप्त किए जाते हैं ताकि कितने भी घटित हों, वे केवल उसी के लिए होते हैं लेनदेन निष्पादन के अंत में कम से कम समय।

यह कथन के पढ़ने वाले हिस्से को अलग-अलग चयन करके पूरा किया जाता है और गतिशील रूप से एक डिलीट स्टेटमेंट जेनरेट करता है जिसे स्टेटमेंट उपस्थिति के आदेश के कारण अंतिम रूप से चलाने के लिए मजबूर किया जाएगा, और जो केवल proc_warnings तालिका को प्रभावित करेगा।

एक डेमो sql फिडेल पर उपलब्ध है:

यह लिंक स्कीमा w / नमूना डेटा और उस पर मेल खाने वाली पंक्तियों के लिए एक सरल क्वेरी दिखाता है ivehicle_id=2। 2 पंक्तियों के परिणामस्वरूप, उनमें से कोई भी हटाया नहीं गया है।

यह लिंक एक ही स्कीमा, नमूना डेटा दिखाता है, लेकिन DeleteEntries संग्रहीत प्रोग्राम के लिए 2 मान पास करता है, एसपी को proc_warningsप्रविष्टियों को हटाने के लिए कहता है ivehicle_id=2। पंक्तियों के लिए सरल क्वेरी का कोई परिणाम नहीं होता है क्योंकि वे सभी सफलतापूर्वक हटा दिए गए हैं। डेमो लिंक केवल डिमॉस्ट्रेट करता है जो कोड हटाने के उद्देश्य से काम करता है। उचित परीक्षण वातावरण वाला उपयोगकर्ता टिप्पणी कर सकता है कि क्या यह अवरुद्ध धागे की समस्या को हल करता है।

यहाँ सुविधा के लिए कोड भी है:

CREATE PROCEDURE DeleteEntries (input_vid INT)
BEGIN

    SELECT @idstring:= '';
    SELECT @idnum:= 0;
    SELECT @del_stmt:= '';

    SELECT @idnum:= @idnum+1 idnum_col, @idstring:= CONCAT(@idstring, CASE WHEN CHARACTER_LENGTH(@idstring) > 0 THEN ',' ELSE '' END, CAST(id AS CHAR(10))) idstring_col
    FROM proc_warnings
    WHERE EXISTS (
        SELECT 0
        FROM day_position
        WHERE day_position.transaction_id = proc_warnings.transaction_id
        AND day_position.dirty_data = 1
        AND EXISTS (
            SELECT 0
            FROM ivehicle_days
            WHERE ivehicle_days.id = day_position.ivehicle_day_id
            AND ivehicle_days.ivehicle_id = input_vid
        )
    )
    ORDER BY idnum_col DESC
    LIMIT 1;

    IF (@idnum > 0) THEN
        SELECT @del_stmt:= CONCAT('DELETE FROM proc_warnings WHERE id IN (', @idstring, ');');

        PREPARE del_stmt_hndl FROM @del_stmt;
        EXECUTE del_stmt_hndl;
        DEALLOCATE PREPARE del_stmt_hndl;
    END IF;
END;

लेन-देन के भीतर से प्रोग्राम को कॉल करने के लिए यह सिंटैक्स है:

CALL DeleteEntries(2);

मूल उत्तर (अभी भी लगता है कि यह बहुत जर्जर नहीं है) 2 मुद्दों की तरह दिखता है: 1) धीमी क्वेरी 2) अप्रत्याशित ताला व्यवहार

जैसा कि समस्या # 1 के संबंध में, धीमी क्वेरी अक्सर एक ही दो तकनीकों द्वारा मिलकर टैंडम क्वेरी स्टेटमेंट सरलीकरण और इंडेक्स में संशोधन या संशोधन के उपयोगी जोड़ द्वारा हल की जाती है। आपने स्वयं ही पहले ही अनुक्रमणिकाओं से संबंध बना लिया है - उनके बिना ऑप्टिमाइज़र प्रक्रिया के लिए सीमित संख्या में खोज नहीं कर सकता है, और प्रत्येक तालिका से प्रत्येक पंक्ति को प्रति पंक्ति से गुणा करके अतिरिक्त कार्य की मात्रा को स्कैन किया जाना चाहिए जो कि किया जाना चाहिए।

SCHEMA और INDEXES के बाद से पहले पढ़े गए अंक: लेकिन मुझे लगता है कि आपके पास एक अच्छा सूचकांक विन्यास है यह सुनिश्चित करके आप अपनी क्वेरी के लिए सबसे अधिक प्रदर्शन लाभ प्राप्त करेंगे। ऐसा करने के लिए, आप बेहतर प्रदर्शन को हटाने के लिए जा सकते हैं, और संभवतः बेहतर प्रदर्शन को हटा सकते हैं, बड़े अनुक्रमितों के व्यापार के साथ और संभवत: समान तालिकाओं पर ध्यान देने योग्य धीमी प्रदर्शन सम्मिलित करते हैं जिसमें अतिरिक्त सूचकांक संरचना को जोड़ा जाता है।

कुछ - कुछ बेहतर:

CREATE TABLE  `day_position` (
    ...,
    KEY `day_position__id_rvrsd` (`dirty_data`, `ivehicle_day_id`)

) ;


CREATE TABLE  `ivehicle_days` (
    ...,
    KEY `ivehicle_days__vid_no_sort_index` (`ivehicle_id`)
);

यहां रिपोर्ट की गई: चूंकि इसे चलाने में जितना समय लगता है, मैं गंदे_डेटा को इंडेक्स में छोड़ दूंगा, और जब इसे इंडेक्स क्रम में ivehicle_day_id के बाद रखा गया, तो मुझे यह सुनिश्चित करने के लिए गलत भी लगा।

लेकिन अगर मेरे पास इस पर मेरे हाथ थे, तो इसे ठीक करने में डेटा की एक अच्छी मात्रा होनी चाहिए, क्योंकि मुझे यह सुनिश्चित करने के लिए कि मैं सबसे अच्छा अनुक्रमित हो रहा हूं, बस सभी इंडेक्स को कवर करने के लिए जाऊंगा। मेरे समस्या निवारण समय खरीद सकता है, अगर समस्या के उस हिस्से को नियंत्रित करने के लिए और कुछ नहीं।

BEST / COVERING INDEXES:

CREATE TABLE  `day_position` (
    ...,
    KEY `day_position__id_rvrsd_trnsid_cvrng` (`dirty_data`, `ivehicle_day_id`, `transaction_id`)
) ;

CREATE TABLE  `ivehicle_days` (
    ...,
    UNIQUE KEY `ivehicle_days__vid_id_cvrng` (ivehicle_id, id)
);

CREATE TABLE  `proc_warnings` (

    .., /*rename primary key*/
    CONSTRAINT pk_proc_warnings PRIMARY KEY (id),
    UNIQUE KEY `proc_warnings__transaction_id_id_cvrng` (`transaction_id`, `id`)
);

अंतिम दो परिवर्तन सुझावों द्वारा मांगे गए दो प्रदर्शन अनुकूलन लक्ष्य हैं:
1) यदि क्रमिक रूप से एक्सेस टेबल के लिए खोज कुंजी समान नहीं है, तो वर्तमान में एक्सेस की गई तालिका के लिए लौटाए गए कुंजी परिणाम, हम समाप्त कर देते हैं, जिसे बनाने की आवश्यकता होती है इंडेक्स-सीक-इन-स्कैन ऑपरेशन का दूसरा सेट क्लस्टर इंडेक्स पर
2) यदि बाद वाला मामला नहीं है, तो अभी भी कम से कम संभावना है कि ऑप्टिमाइज़र अधिक कुशल जॉइन एल्गोरिथ्म का चयन कर सकता है क्योंकि इंडेक्स अनुरक्षित रहेगा। आवश्यक क्रमबद्ध क्रम में कुंजियाँ सम्मिलित करें।

आपकी क्वेरी के बारे में ऐसा लगता है जैसे कि इसे सरल बनाया जा सकता है (बाद में इसे संपादित किए जाने पर यहां कॉपी किया जाता है):

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
    ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
    ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;

जब तक निश्चित रूप से लिखित ज्वाइन ऑर्डर के बारे में कुछ न हो, जो क्वेरी ऑप्टिमाइज़र के कार्य को प्रभावित करने के तरीके को प्रभावित करता है, जिस स्थिति में आप दूसरों द्वारा प्रदान किए गए कुछ पुनर्लेखन सुझावों की कोशिश कर सकते हैं, जिसमें शायद यह w / index संकेत (वैकल्पिक) भी शामिल है:

DELETE FROM proc_warnings
FORCE INDEX (`proc_warnings__transaction_id_id_cvrng`, `pk_proc_warnings`)
WHERE EXISTS (
    SELECT 0
    FROM day_position
    FORCE INDEX (`day_position__id_rvrsd_trnsid_cvrng`)  
    WHERE day_position.transaction_id = proc_warnings.transaction_id
    AND day_position.dirty_data = 1
    AND EXISTS (
        SELECT 0
        FROM ivehicle_days
        FORCE INDEX (`ivehicle_days__vid_id_cvrng`)  
        WHERE ivehicle_days.id = day_position.ivehicle_day_id
        AND ivehicle_days.ivehicle_id = ?
    )
);

जैसा कि # 2, अप्रत्याशित ताला व्यवहार।

जैसा कि मैं देख सकता हूं कि दोनों प्रश्न प्राथमिक कुंजी = 53 के साथ एक पंक्ति में एक अनन्य एक्स लॉक चाहते हैं। हालांकि, उनमें से किसी को भी proc_warnings तालिका से पंक्तियों को नहीं हटाना चाहिए। मुझे अभी समझ में नहीं आया है कि सूचकांक क्यों बंद है।

मुझे लगता है कि यह वह सूचकांक होगा जो लॉक होता है क्योंकि लॉक की जाने वाली डेटा की पंक्ति एक क्लस्टर इंडेक्स में होती है, यानी डेटा की एकल पंक्ति इंडेक्स में ही रहती है।

यह बंद हो जाएगा, क्योंकि:
1) http://dev.mysql.com/doc/refman/5.1/en/innodb-locks-set.html के अनुसार

... एक DELETE आम तौर पर SQL कथन के प्रसंस्करण में स्कैन किए गए प्रत्येक सूचकांक रिकॉर्ड पर रिकॉर्ड लॉक सेट करता है। इससे कोई फर्क नहीं पड़ता कि बयान में ऐसी कौन सी शर्तें हैं जो पंक्ति को बाहर करेगी। InnoDB को सटीक WHERE की स्थिति याद नहीं है, लेकिन केवल यह जानता है कि कौन सी सूचकांक श्रेणियां स्कैन की गई थीं।

आपने ऊपर भी उल्लेख किया है:

... मेरे लिए READ COMMITTED की मुख्य विशेषता यह है कि यह ताले से कैसे निपटता है। इसे गैर-मिलान वाली पंक्तियों के सूचकांक ताले को छोड़ देना चाहिए, लेकिन यह नहीं है।

और उसके लिए निम्न संदर्भ प्रदान किया:
http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-committed

जो आपको बताता है, सिवाय इसके कि उसी संदर्भ के अनुसार एक शर्त है जिस पर ताला जारी किया जाएगा:

MySQL द्वारा WHERE की स्थिति का मूल्यांकन करने के बाद, नॉनमैटिंग पंक्तियों के लिए रिकॉर्ड लॉक जारी किए जाते हैं।

जिसे इस मैनुअल पेज http://dev.mysql.com/doc/refman/5.1/en/innodb-record-level-locks.html पर दोहराया गया है

READ COMMITTED आइसोलेशन स्तर का उपयोग करने या innodb_locks_unsafe_for_binlog को सक्षम करने के अन्य प्रभाव भी हैं: MySQL द्वारा WHERE की स्थिति का मूल्यांकन करने के बाद नॉनमैचिंग पंक्तियों के लिए रिकॉर्ड लॉक जारी किए जाते हैं।

इसलिए, हमें बताया गया है कि लॉक से संबंधित होने से पहले WHERE की स्थिति का मूल्यांकन किया जाना चाहिए। दुर्भाग्यवश जब हमें यह नहीं बताया जाता है कि WHERE की स्थिति का मूल्यांकन किया गया है और यह संभवतः ऑप्टिमाइज़र द्वारा बनाई गई एक योजना से दूसरी में बदलने के अधीन होगा। लेकिन यह हमें बताता है कि लॉक रिलीज़, क्वेरी निष्पादन के प्रदर्शन पर किसी न किसी तरह निर्भर है, जिसका अनुकूलन हम ऊपर चर्चा करते हैं, बयान के सावधानीपूर्वक लेखन और इंडेक्स के विवेकपूर्ण उपयोग पर निर्भर है। इसे बेहतर टेबल डिज़ाइन द्वारा भी बेहतर बनाया जा सकता है लेकिन संभवत: इसे एक अलग प्रश्न के लिए छोड़ दिया जाएगा।

इसके अलावा, इंडेक्स या तो लॉक नहीं होता है जब proc_warnings टेबल खाली होता है

यदि कोई नहीं है, तो डेटाबेस इंडेक्स के भीतर रिकॉर्ड को लॉक नहीं कर सकता है।

इसके अलावा, सूचकांक तब लॉक नहीं होता है जब ... day_position तालिका में पंक्तियों की संख्या कम होती है (यानी एक सौ पंक्तियाँ)।

इसका अर्थ कई चीजें हो सकता है, लेकिन संभवत: यह सीमित नहीं है: आंकड़ों में बदलाव के कारण एक अलग निष्पादन योजना, बहुत छोटे डेटा सेट के कारण बहुत तेज निष्पादन के कारण एक संक्षिप्त-टू-टू-मनाया-लॉक। ऑपरेशन में शामिल हों।


WHEREहालत का मूल्यांकन जब क्वेरी पूरा करता है। है ना? मुझे लगा कि कुछ समवर्ती प्रश्नों के निष्पादन के बाद ताला सही जारी किया गया है। यही स्वाभाविक व्यवहार है। हालाँकि, ऐसा नहीं होता है। इस थ्रेड में सुझाए गए प्रश्नों में से कोई भी proc_warningsतालिका में क्लस्टर इंडेक्स लॉकिंग से बचने में मदद नहीं करता है । मुझे लगता है कि मैं MySQL के लिए एक बग दर्ज करूंगा। आपकी सहायता के लिए धन्यवाद।
vitalidze

मैं उनसे उम्मीद नहीं करूंगा कि वे लॉकिंग व्यवहार से बचें। मैं इसे लॉक करने की उम्मीद करूंगा क्योंकि मुझे लगता है कि दस्तावेज़ीकरण कहता है कि क्या उम्मीद है, इस तरह से हम क्वेरी को संसाधित करने के लिए चाहते हैं या नहीं। मैं बस उम्मीद करूंगा कि प्रदर्शन की समस्या से छुटकारा पाने के लिए समवर्ती क्वेरी को ऐसे स्पष्ट रूप से (500+ सेकंड टाइमआउट) लंबे समय तक अवरुद्ध करने से बचाए रखेगा।
जेएम हिक्स

हालाँकि, आपका {WHERE} ऐसा लगता है कि इसे जॉइनिंग प्रोसेसिंग के दौरान इस्तेमाल किया जा सकता है, जो कि गणना में शामिल पंक्तियों में शामिल है, को प्रतिबंधित करने के लिए, मैं नहीं देखता कि आपके {WHERE} क्लॉज का मूल्यांकन लॉक की गई पंक्ति में तब तक किया जा सकता है जब तक कि जॉन्स का पूरा सेट नहीं हो जाता। साथ ही गणना की। उस ने कहा, हमारे विश्लेषण के लिए, मुझे संदेह है कि आप सही हैं कि हमें संदेह होना चाहिए "जब क्वेरी पूरी हो जाती है तो स्थिति का मूल्यांकन किया जाता है"। फिर भी जो मुझे एक ही समग्र निष्कर्ष पर ले जाता है, कि प्रदर्शन को हल करने की आवश्यकता है, और फिर संगामिति की स्पष्ट डिग्री आनुपातिक रूप से बढ़ेगी।
जेएम हिक्स

याद रखें कि उचित अनुक्रमणिका किसी भी पूर्ण तालिका स्कैन को संभावित रूप से समाप्त कर सकती हैं जो proc_warnings तालिका पर होती है। ऐसा होने के लिए हमें क्वेरी ऑप्टिमाइज़र की ज़रूरत है ताकि वह हमारे लिए अच्छी तरह से काम कर सके, और हमें अपने अनुक्रमित, क्वेरी और डेटा को अच्छी तरह से सहकर्मी के लिए चाहिए। लक्ष्य मानों में पंक्तियों के अंत में पैरामीटर मानों का मूल्यांकन करना होता है जो दो प्रश्नों के बीच ओवरलैप नहीं होता है। अनुक्रमित को उन पंक्तियों की कुशलता से खोज करने के लिए क्वेरी ऑप्टिमाइज़र को एक साधन के साथ उपलब्ध कराने की आवश्यकता है। हमें संभावित खोज क्षमता का एहसास करने और इस तरह की योजना का चयन करने के लिए आशावादी की आवश्यकता है।
जेएम हिक्स

यदि पैरामीटर मानों, अनुक्रमणिकाओं, गैर-अतिव्यापी परिणामों के बीच सब कुछ अच्छी तरह से चला जाता है, तो proc_warnings तालिका में, और ऑप्टिमाइज़र योजना चयन, भले ही प्रत्येक थ्रेड के लिए क्वेरी निष्पादित करने के लिए आवश्यक समय की अवधि के लिए ताले उत्पन्न हो सकते हैं, उन तालों को, यदि नहीं। ओवरलैपिंग, अन्य थ्रेड्स के लॉक अनुरोधों के साथ संघर्ष नहीं करेगा।
जेएम हिक्स

3

मैं देख सकता हूं कि READ_COMMITTED इस स्थिति का कारण कैसे बन सकता है।

READ_COMMITTED में तीन चीजों की अनुमति है:

  • READ_COMMITTED आइसोलेशन स्तर का उपयोग करके अन्य लेनदेन द्वारा प्रतिबद्ध परिवर्तनों की दृश्यता ।
  • गैर-दोहराने योग्य प्रतिक्रिया: प्रत्येक बार एक अलग परिणाम प्राप्त करने की संभावना के साथ एक ही पुनर्प्राप्ति का प्रदर्शन करने वाला लेनदेन।
  • प्रेत: लेन-देन में पंक्तियाँ हो सकती हैं जहाँ यह पहले से दिखाई नहीं दे रही थी।

यह लेन-देन के लिए एक आंतरिक प्रतिमान बनाता है क्योंकि लेन-देन के साथ संपर्क बनाए रखना चाहिए:

  • InnoDB बफर पूल (जबकि प्रतिबद्ध अभी भी अप्रभावित है)
  • तालिका की प्राथमिक कुंजी
  • संभवत:
    • डबल लिखें बफर
    • टेबल्स को पूर्ववत करें
  • सचित्र प्रतिनिधित्व

यदि दो अलग - अलग READ_COMMITTED लेनदेन समान टेबल्स / पंक्तियों को एक्सेस कर रहे हैं जो एक ही तरह से अपडेट किए जा रहे हैं, तो टेबल लॉक नहीं, बल्कि gen_clust_index (उर्फ क्लस्टर इंडेक्स) के भीतर एक विशेष लॉक की उम्मीद करने के लिए तैयार रहें । आपके सरलीकृत मामले से प्रश्नों को देखते हुए:

  • लेन-देन १

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 1;
  • लेन-देन २

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 2;

आप gen_clust_index में उसी स्थान को लॉक कर रहे हैं। एक कह सकता है, "लेकिन प्रत्येक लेनदेन में एक अलग प्राथमिक कुंजी है।" दुर्भाग्य से, यह InnoDB की नज़र में ऐसा नहीं है। यह सिर्फ इतना होता है कि आईडी 1 और आईडी 2 एक ही पृष्ठ पर रहते हैं।

information_schema.innodb_locksआप प्रश्न में आपूर्ति पर वापस देखो

| lock_id                | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |

के अपवाद के साथ lock_id, lock_trx_idबाकी लॉक विवरण समान है। चूँकि लेन-देन समान स्तर के खेल मैदान (समान लेन-देन अलगाव) पर होता है, यह वास्तव में होना चाहिए

मेरा विश्वास करो, मैंने पहले भी इस तरह की स्थिति को संबोधित किया है। यहाँ इस पर मेरे पिछले पोस्ट हैं:


मैंने MySQL डॉक्स में आपके द्वारा बताई गई चीजों के बारे में पढ़ा है। लेकिन जैसा कि मेरे लिए READ COMMITTED की मुख्य विशेषता यह है कि यह ताले से कैसे निपटता है । इसे गैर-मिलान वाली पंक्तियों के सूचकांक ताले को छोड़ देना चाहिए, लेकिन यह नहीं है।
vitalidze

यदि किसी त्रुटि के परिणामस्वरूप सिर्फ एक एसक्यूएल स्टेटमेंट को वापस रोल किया जाता है, तो स्टेटमेंट द्वारा निर्धारित कुछ तालों को संरक्षित किया जा सकता है। यह इसलिए होता है क्योंकि एक प्रारूप में InnoDB भंडार पंक्ति ताले ऐसी है कि वह बाद में पता नहीं कर सकते हैं जो ताला जो बयान से स्थापित किया गया था: dev.mysql.com/doc/refman/5.5/en/innodb-deadlock-detection.html
RolandoMySQLDBA

कृपया ध्यान दें कि मैंने लॉक करने के लिए एक ही पृष्ठ में मौजूद दो पंक्तियों की संभावना का उल्लेख किया है (देखें Look back at information_schema.innodb_locks you supplied in the Question)
RolandoMySQLDBA

सिंगल स्टेटमेंट को वापस करने के बारे में - मुझे यह समझ में आता है कि अगर सिंगल स्टेटमेंट सिंगल ट्रांजेक्शन में विफल हो जाता है तो भी यह लॉक हो सकता है। ठीक है। मेरा बड़ा सवाल यह है कि यह सफलतापूर्वक प्रसंस्करण DELETEविवरण के बाद गैर-मिलान पंक्ति ताले को रिलीज़ क्यों नहीं करता है ।

दो पूर्ण ताले के साथ, एक को वापस रोल करना होगा। यह संभव है कि ताले अस्तर कर सकते हैं। वर्किंग थ्योरी: लेन-देन जो पीछे लुढ़क गया, वह पुन: प्रयास कर सकता है और पिछले लेन-देन से पुराने लॉक का सामना कर सकता है।
रोलैंडमाइसीडीडीबीए

2

मैंने प्रश्न और व्याख्या को देखा। मुझे यकीन नहीं है, लेकिन एक आंत की भावना है, कि समस्या निम्नलिखित है। आइए प्रश्न को देखें:

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
   ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
   ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=? AND dp.dirty_data=1;

समतुल्य चयन है:

SELECT pw.id
FROM proc_warnings pw
INNER JOIN day_position dp
   ON dp.transaction_id = pw.transaction_id
INNER JOIN ivehicle_days vd
   ON vd.id = dp.ivehicle_day_id
WHERE vd.ivehicle_id=16 AND dp.dirty_data=1;

यदि आप इसके स्पष्टीकरण को देखते हैं, तो आप देखेंगे कि निष्पादन योजना proc_warningsतालिका से शुरू होती है । इसका मतलब है कि MySQL तालिका में प्राथमिक कुंजी को स्कैन करता है और यदि स्थिति सही है, तो प्रत्येक पंक्ति जांच के लिए और यदि यह है - पंक्ति हटा दी जाती है। वह MySQL के लिए पूरी प्राथमिक कुंजी लॉक करना है।

आपको जोइन ऑर्डर को पलटना होगा, वह सभी ट्रांजेक्शन आईडी ढूंढना है vd.ivehicle_id=16 AND dp.dirty_data=1और उन्हें proc_warningsटेबल पर जोड़ना है।

यह है कि आपको सूचकांकों में से एक को पैच करना होगा:

ALTER TABLE `day_position`
 DROP INDEX `day_position__id`,
 ADD INDEX `day_position__id`
   USING BTREE (`ivehicle_day_id`, `dirty_data`, `transaction_id`);

और डिलीट क्वेरी फिर से लिखें:

DELETE pw
FROM (
  SELECT DISTINCT dp.transaction_id
  FROM ivehicle_days vd
  JOIN day_position dp ON dp.ivehicle_day_id = vd.id
  WHERE vd.ivehicle_id=? AND dp.dirty_data=1
) as tr_id
JOIN proc_warnings pw ON pw.transaction_id = tr_id.transaction_id;

दुर्भाग्य से यह मदद नहीं करता है, यानी पंक्तियाँ proc_warningsअभी भी बंद हैं। फिर भी धन्यवाद।
14

2

जब आप लेन-देन का स्तर निर्धारित करते हैं, तो जिस तरह से आप इसे पढ़ते हैं, वह केवल अगले लेनदेन के लिए प्रतिबद्ध होता है, इस प्रकार (ऑटो कमिट सेट करें)। ऑटोकॉमिट = 0 के बाद इसका मतलब है, आप रीड कमिटेड नहीं हैं। मैं इसे इस तरह लिखूंगा:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
DELETE c FROM child c
INNER JOIN parent p ON
    p.id = c.parent_id
WHERE p.id = 1;

आप जाँच कर सकते हैं कि आप किस अलगाव स्तर में क्वेरी कर रहे हैं

SELECT @@tx_isolation;

यह सच नहीं है। SET AUTOCOMMIT=0अगले ट्रांजेक्शन के लिए आइसोलेशन लेवल क्यों रीसेट करना चाहिए? मेरा मानना ​​है कि यह एक नया लेनदेन शुरू करता है यदि कोई भी पहले शुरू नहीं किया गया था (जो मेरा मामला है)। इसलिए, अधिक सटीक होने के लिए अगला START TRANSACTIONया BEGINकथन आवश्यक नहीं है। ऑटोकॉमिट को अक्षम करने का मेरा उद्देश्य DELETEबयान के निष्पादन के बाद खोले गए लेनदेन को छोड़ना है ।
vitalidze

1
@SqlKiwi इस इस पोस्ट को संपादित करने के लिए रास्ता था, और इस पर ;-) टिप्पणी करने के लिए एक था
jcolebrand
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.