क्यों MYSQL उच्च सीमा ऑफसेट धीमा क्वेरी को धीमा करता है?


173

संक्षेप में परिदृश्य: 16 मिलियन से अधिक रिकॉर्ड [2GB आकार में] के साथ एक तालिका। चयन के साथ उच्च सीमा ऑफसेट, धीमी गति से क्वेरी बन जाती है, जब ORDER BY * प्रायमरी_की * का उपयोग करता है

इसलिए

SELECT * FROM large ORDER BY `id`  LIMIT 0, 30 

से बहुत कम लेता है

SELECT * FROM large ORDER BY `id` LIMIT 10000, 30 

वह केवल 30 रिकॉर्ड और एक ही आदेश देता है। तो यह ORDER BY से ओवरहेड नहीं है।
अब जब नवीनतम 30 पंक्तियों को लाने में लगभग 180 सेकंड लगते हैं। मैं उस सरल क्वेरी को कैसे अनुकूलित कर सकता हूं?


नोट: मैं लेखक हूँ। MySQL उपरोक्त मामलों में सूचकांक (PRIMARY) का उल्लेख नहीं करता है। स्पष्टीकरण के लिए उपयोगकर्ता "क्वासोई" द्वारा नीचे दिया गया लिंक देखें।
रहमान

जवाबों:


197

यह सामान्य है कि उच्चतर ऑफ़सेट क्वेरी को धीमा कर देते हैं, क्योंकि क्वेरी को पहले OFFSET + LIMITरिकॉर्ड्स की गणना करने की आवश्यकता होती है (और LIMITउनमें से केवल ले लो )। यह मान जितना अधिक होगा, क्वेरी उतनी ही लंबी चलेगी।

क्वेरी सही नहीं जा सकती OFFSET , क्योंकि पहले, रिकॉर्ड अलग-अलग लंबाई के हो सकते हैं, और दूसरे, हटाए गए रिकॉर्ड से अंतराल हो सकते हैं। इसके रास्ते में प्रत्येक रिकॉर्ड को जांचना और गिनना होगा।

यह मानते हुए कि idहै एक PRIMARY KEYएक की MyISAMमेज, तो आप इस चाल का उपयोग करके तेजी लाने के कर सकते हैं:

SELECT  t.*
FROM    (
        SELECT  id
        FROM    mytable
        ORDER BY
                id
        LIMIT 10000, 30
        ) q
JOIN    mytable t
ON      t.id = q.id

इस लेख को देखें:


7
MySQL "शुरुआती पंक्ति लुकअप" व्यवहार का उत्तर था कि यह इतनी लंबी बात क्यों कर रहा है। आपके द्वारा दी गई चाल से, केवल मिलान किए गए आईडी (सीधे सूचकांक द्वारा) बाध्य हैं, बहुत सारे रिकॉर्डों की अनावश्यक पंक्ति लुकअप को सहेजते हुए। यही चाल चली, हुर्रे!
रहमान

4
@harald: वास्तव में "काम नहीं" से आपका क्या मतलब है? यह एक शुद्ध प्रदर्शन में सुधार है। यदि कोई अनुक्रमणिका प्रयोग करने योग्य नहीं है ORDER BYया अनुक्रमणिका आपके लिए आवश्यक सभी क्षेत्रों को शामिल करती है, तो आपको इस समाधान की आवश्यकता नहीं है।
क्वासोनि

6
@ f055: उत्तर कहता है "गति बढ़ाओ", न कि "तत्काल बनाओ"। क्या आपने उत्तर का पहला वाक्य पढ़ा है?
क्वासोनी

3
क्या InnoDB के लिए ऐसा कुछ चलाना संभव है?
NeverEndingQueue

3
@ लंती: कृपया इसे एक अलग प्रश्न के रूप में पोस्ट करें और इसे टैग करना न भूलें postgresql। यह एक MySQL- विशिष्ट उत्तर है।
Quassnoi

220

मुझे खुद भी वही समस्या थी। इस तथ्य को देखते हुए कि आप इस डेटा की एक बड़ी राशि एकत्र करना चाहते हैं और 30 का एक विशिष्ट सेट नहीं है, आप शायद एक लूप चला रहे हैं और 30 से ऑफसेट बढ़ाते हैं।

तो इसके बजाय आप क्या कर सकते हैं:

  1. डेटा के एक सेट की अंतिम आईडी (30) (जैसे lastId = 530)
  2. शर्त जोड़ें WHERE id > lastId limit 0,30

तो आप हमेशा एक शून्य ऑफसेट कर सकते हैं। प्रदर्शन में सुधार से आप चकित रह जाएंगे।


क्या यह काम अगर अंतराल हैं? क्या होगा यदि आपके पास एक एकल कुंजी (उदाहरण के लिए एक संयुक्त कुंजी) नहीं है?
xaisoft

8
यह सभी के लिए स्पष्ट नहीं हो सकता है कि यह केवल तभी काम करता है जब आपका परिणाम सेट उस कुंजी द्वारा क्रमबद्ध होता है, आरोही क्रम में (एक ही विचार कार्य के लिए अवरोही क्रम के लिए, लेकिन> lastid से <lastid।) में परिवर्तन होता है, अगर यह बात नहीं है। प्राथमिक कुंजी, या कोई अन्य फ़ील्ड (या फ़ील्ड्स का समूह)
Eloff

अच्छा किया उस आदमी ने! एक बहुत ही सरल उपाय जिसने मेरी समस्या हल कर दी है :-)
oodavid

30
बस एक ध्यान दें कि सीमा / ऑफसेट का उपयोग अक्सर पृष्ठांकित परिणामों में किया जाता है, और lastId को धारण करना संभवतः संभव नहीं है क्योंकि उपयोगकर्ता किसी भी पृष्ठ पर कूद सकता है, हमेशा अगले पृष्ठ पर नहीं। दूसरे शब्दों में, ऑफसेट को अक्सर एक निरंतर पैटर्न का पालन करने के बजाय, पृष्ठ और सीमा के आधार पर गतिशील रूप से गणना करने की आवश्यकता होती है।
टॉम


17

MySQL सीधे 10000 वें रिकॉर्ड (या आपके सुझाव के अनुसार 80000 वें बाइट) पर नहीं जा सकता है क्योंकि यह मान नहीं सकता है कि यह पैक किया गया है / जैसा आदेश दिया गया है (या यह 1 से 10000 में निरंतर मान है)। हालाँकि यह वास्तविकता में ऐसा हो सकता है, MySQL यह नहीं मान सकता है कि कोई छेद / अंतराल / हटाए गए आईडी नहीं हैं।

इसलिए, जैसा कि नोट किया गया था, MySQL idको वापस लौटने के लिए 30 खोजने से पहले 10000 पंक्तियों (या सूचकांक की 10000 वीं प्रविष्टियों के माध्यम से ट्रैवर्स) लाना होगा ।

संपादित करें : मेरी बात को समझाने के लिए

ध्यान दें कि हालांकि

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

धीमा होगा (er) ,

SELECT * FROM large WHERE id >  10000 ORDER BY id LIMIT 30 

होगा तेजी से (ईआर) , और एक ही परिणाम प्रदान नहीं याद आ रही देखते हैं कि वापसी होगी idरों (यानी अंतराल)।


2
यह सही है। लेकिन चूंकि यह "आईडी" द्वारा सीमित है, इसलिए जब आईडी एक इंडेक्स (प्राथमिक कुंजी) के भीतर होता है, तो इसमें इतना समय क्यों लगता है? ऑप्टिमाइज़र को सीधे उस इंडेक्स का उल्लेख करना चाहिए, और फिर मैच्योर आईडी के साथ पंक्तियों को लाना चाहिए (जो उस इंडेक्स से आया है)
रहमान

1
यदि आपने आईडी पर WHERE क्लॉज का उपयोग किया है, तो यह उस चिह्न पर सही जा सकता है। हालाँकि, यदि आप इस पर एक सीमा लगाते हैं, तो आईडी द्वारा आदेश दिया गया है, यह केवल शुरुआत के लिए एक सापेक्ष काउंटर है, इसलिए इसे पूरे रास्ते से पार करना होगा।
Riedsio

बहुत अच्छा लेख eversql.com/…
पाओजुट

मेरे लिए काम किया @Riedsio धन्यवाद।
महेश काजले

8

मुझे आई-एलआईटी एक्स, वाई द्वारा सेलेक्ट क्वेरीज ऑडर को ऑप्टिमाइज़ करने के लिए एक दिलचस्प उदाहरण मिला। मेरे पास 35million पंक्तियाँ हैं इसलिए पंक्तियों की एक श्रृंखला को खोजने में 2 मिनट का समय लगा।

यहाँ चाल है:

select id, name, address, phone
FROM customers
WHERE id > 990
ORDER BY id LIMIT 1000;

बस WHERE को अंतिम आईडी के साथ रखें जिससे आपको प्रदर्शन में वृद्धि हुई है। मेरे लिए यह 2 मिनट से 1 सेकंड तक था :)

अन्य रोचक ट्रिक्स यहाँ: http://www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

यह तार के साथ भी काम करता है


1
यह केवल तालिकाओं के लिए काम करता है, जहां कोई डेटा हटाए नहीं जाते हैं
miro

1
@miro यह केवल तभी सच है जब आप इस धारणा के तहत काम कर रहे हैं कि आपकी क्वेरी यादृच्छिक पृष्ठों पर लुकअप कर सकती है, जो मुझे नहीं लगता कि यह पोस्टर ग्रहण कर रहा है। जबकि मैं इस विधि को अधिकांश वास्तविक दुनिया के मामलों के लिए पसंद नहीं करता हूं, यह अंतराल के साथ काम करेगा जब तक कि आप हमेशा इसे प्राप्त अंतिम आईडी को बंद कर रहे हैं।
ग्रेमियो जूल

5

दो प्रश्नों का समय लेने वाला हिस्सा तालिका से पंक्तियों को निकाल रहा है। तार्किक रूप से, LIMIT 0, 30संस्करण में, केवल 30 पंक्तियों को पुनर्प्राप्त करने की आवश्यकता है। में LIMIT 10000, 30संस्करण, 10000 पंक्तियों मूल्यांकन किया जाता है और 30 पंक्तियों लौटाए जाते हैं। कुछ अनुकूलन हो सकते हैं मेरी डेटा-रीडिंग प्रक्रिया हो सकती है, लेकिन निम्नलिखित पर विचार करें:

क्या होगा अगर आपके पास प्रश्नों में एक खंड था? इंजन को योग्य होने वाली सभी पंक्तियों को वापस करना चाहिए, और फिर डेटा को सॉर्ट करना चाहिए, और अंत में 30 पंक्तियों को प्राप्त करना चाहिए।

उस मामले पर भी विचार करें जहां पंक्तियों को ORDER BY अनुक्रम में संसाधित नहीं किया गया है। कौन सी पंक्तियों को वापस करना है यह निर्धारित करने के लिए सभी योग्य पंक्तियों को क्रमबद्ध करना चाहिए।


1
बस सोच रहा था कि उन 10000 पंक्तियों को लाने में समय क्यों लगता है। उस फ़ील्ड (आईडी, जो कि एक प्राथमिक कुंजी है) पर प्रयुक्त इंडेक्स को उन पंक्तियों को तेजी से प्राप्त करना चाहिए जो रिकॉर्ड पीके के लिए उस पीके इंडेक्स की मांग करते हैं। 10000, जो बदले में तेजी से माना जाता है कि उस फाइल को सूचकांक रिकॉर्ड लंबाई से गुणा करने के लिए फ़ाइल की मांग की जाती है (यानी, 10000 * 8 = बाइट नहीं 80000 की मांग करते हुए - यह देखते हुए कि 8 सूचकांक रिकॉर्ड लंबाई है)
रहमान

@ रहमान - 10000 पंक्तियों को गिनने का एकमात्र तरीका है कि एक-एक करके उन पर कदम रखा जाए। इसमें सिर्फ एक इंडेक्स शामिल हो सकता है , लेकिन फिर भी इंडेक्स पंक्तियों को चरणबद्ध होने में समय लगता है। नहीं है कोई MyISAM या InnoDB संरचना है कि सही ढंग से (सभी मामलों में) "तलाश" कर सकते हैं रिकॉर्ड करने के लिए 10000 10000 * 8 सुझाव मान लिया गया है (1) MyISAM, (2) निश्चित लंबाई रिकॉर्ड, और (3) मेज से कभी नहीं किसी भी हटाए गए । वैसे भी, MyISAM इंडेक्स BTrees हैं, इसलिए यह काम नहीं करेगा।
रिक जेम्स

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

1

उन लोगों के लिए जो एक तुलना और आंकड़ों में रुचि रखते हैं :)

प्रयोग 1: डेटासेट में लगभग 100 मिलियन पंक्तियाँ होती हैं। प्रत्येक पंक्ति में कई BIGINT, TINYINT, साथ ही दो TEXT फ़ील्ड (जानबूझकर) जिसमें लगभग 1k वर्ण होते हैं।

  • नीला: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • नारंगी: = @ क्वासोई की विधि। SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • बेशक, तीसरी विधि, ... WHERE id>xxx LIMIT 0,5यहां दिखाई नहीं देती है क्योंकि यह निरंतर समय होना चाहिए।

प्रयोग 2: इसी तरह की बात, सिवाय इसके कि एक पंक्ति में केवल 3 बिजी हैं।

  • हरा: = पहले नीला
  • लाल: = पहले नारंगी

यहां छवि विवरण दर्ज करें

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