मूल्यों पर MySQL "IN" ऑपरेटर प्रदर्शन (बड़ी?) की संख्या


93

मैं हाल ही में Redis और MongoDB के साथ प्रयोग कर रहा हूं और ऐसा लगता है कि अक्सर ऐसे मामले होते हैं जहां आप id की एक सरणी को MongoDB या Redis में संग्रहीत करते हैं । जब से मैं MySQL IN ऑपरेटर के बारे में पूछ रहा हूँ, मैं इस सवाल के लिए रेडिस के साथ रहूँगा ।

मैं सोच रहा था कि IN ऑपरेटर के अंदर आईडी की एक बड़ी संख्या (300-3000) को सूचीबद्ध करना कितना अच्छा है , जो इस तरह दिखाई देगा:

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)

एक उत्पाद और श्रेणियों की तालिका के रूप में कुछ के रूप में सरल कल्पना करो, जो आप आम तौर पर एक निश्चित श्रेणी से उत्पादों को प्राप्त करने के लिए एक साथ जोड़ सकते हैं । ऊपर दिए गए उदाहरण में आप देख सकते हैं कि Redis ( ) में दिए गए श्रेणी के अंतर्गत मैं श्रेणी 4 से सभी उत्पाद आईडी वापस करता हूं, और उन्हें ऑपरेटर के अंदर उपरोक्त क्वेरी में रखता हूं ।category:4:product_idsSELECTIN

यह कैसा प्रदर्शन है?

क्या यह "यह निर्भर करता है" स्थिति है? या वहाँ एक ठोस "यह है (संयुक्त राष्ट्र) स्वीकार्य" या "तेज" या "धीमी" या मुझे एक जोड़ना चाहिए LIMIT 25, या क्या वह मदद नहीं करता है?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 3000)
LIMIT 25

या क्या मुझे Redis द्वारा 25 तक सीमित करने के लिए उत्पाद आईडी की सरणी को ट्रिम करना चाहिए और केवल 3000 के बजाय 25 आईडी को क्वेरी में जोड़ना होगा और LIMITइसे क्वेरी के अंदर से 25 तक करना होगा?

SELECT id, name, price
FROM products
WHERE id IN (1, 2, 3, 4, ...... 25)

किसी भी सुझाव / प्रतिक्रिया बहुत सराहना की है!


मुझे यकीन नहीं है कि तुम क्या पूछ रहे हो? "Id IN (1,2,3, ... 3000)) के साथ एक प्रश्न" id = मान "के साथ 3000 प्रश्नों से अधिक तेज है। लेकिन "श्रेणी = 4" के साथ एक जुड़ाव उपरोक्त दोनों की तुलना में तेज होगा।
रॉननिस 12

हालांकि, चूंकि एक उत्पाद कई श्रेणियों से संबंधित हो सकता है इसलिए मैं "श्रेणी = 4" नहीं कर सकता। Redis का उपयोग करते हुए मैं उन सभी उत्पादों को संग्रहीत करूंगा जो एक निश्चित श्रेणियों में हैं और फिर उस पर क्वेरी की गई है। मुझे लगता है कि असली सवाल यह है कि, id IN (1,2,3 ... 3000)JOIN टेबल की तुलना में प्रदर्शन कैसा होगा products_categories। या यह कि आप क्या कह रहे थे?
माइकल वान रूइजेन


बेशक कोई कारण नहीं है कि यह अनुक्रमित पंक्तियों को पुनः प्राप्त करने की किसी अन्य विधि के रूप में कुशल नहीं होना चाहिए; यह सिर्फ इस बात पर निर्भर करता है कि डेटाबेस लेखकों ने इसके लिए परीक्षण और अनुकूलन किया है या नहीं। कम्प्यूटेशनल जटिलता के संदर्भ में हम INक्लॉज़ पर सबसे खराब O (n लॉग एन) सॉर्ट करने जा रहे हैं (यह एल्गोरिथ्म के आधार पर आपके जैसी सॉर्ट की गई सूची पर रेखीय भी हो सकता है), और फिर रैखिक चौराहा / लुकअप ।
18

जवाबों:


39

आम तौर पर, यदि INसूची बहुत बड़ी हो जाती है ('बहुत बड़ी' के कुछ गैर-परिभाषित मूल्य के लिए जो आमतौर पर 100 या छोटे क्षेत्र में होती है), तो जुड़ने के लिए उपयोग करने के लिए यह अधिक कुशल हो जाता है, अगर जरूरत हो तो एक अस्थायी तालिका बनाएं। संख्या रखने के लिए।

यदि संख्या एक सघन सेट है (कोई अंतराल नहीं है - जो नमूना डेटा बताता है), तो आप इसके साथ भी बेहतर कर सकते हैं WHERE id BETWEEN 300 AND 3000

हालाँकि, संभवतः सेट में अंतराल हैं, जिस बिंदु पर सभी के बाद वैध मूल्यों की सूची के साथ जाना बेहतर हो सकता है (जब तक कि अंतराल अपेक्षाकृत कम संख्या में न हों, जिस स्थिति में आप उपयोग कर सकते हैं:

WHERE id BETWEEN 300 AND 3000 AND id NOT BETWEEN 742 AND 836

या जो भी अंतराल हैं।


46
क्या आप "एक अस्थायी तालिका बनाने में शामिल होने का उपयोग करने" का उदाहरण दे सकते हैं?
जेक

यदि डेटा सेट एक इंटरफ़ेस (बहु-चयन तत्व) से आया है और चयनित डेटा में अंतराल है और यह अंतराल अनुक्रमिक अंतर नहीं है (लापता: 457, 490, 658, ..) तो AND id NOT BETWEEN XXX AND XXXकाम नहीं करेगा और यह बेहतर है छड़ी के साथ बराबर (x = 1 OR x = 2 OR x = 3 ... OR x = 99)@David Fells ने लिखा।
डीपसेल

मेरे अनुभव में - ई-कॉमर्स वेबसाइटों पर काम करते हुए, हमें ~ 50 असंबंधित उत्पाद आईडी के खोज परिणाम दिखाने होंगे, हमारे पास "1. 50 अलग-अलग प्रश्न", बनाम "के साथ बेहतर परिणाम थे। 2." में एक मान के साथ कई क्वेरी खंड ""। मेरे पास फिलहाल इसे साबित करने का कोई तरीका नहीं है, सिवाय इसके कि क्वेरी # 2 हमेशा हमारे मॉनिटरिंग सिस्टम में धीमी क्वेरी के रूप में दिखाई देगी, जबकि # 1 कभी भी दिखाई नहीं देगा, भले ही निष्पादन की मात्रा में हो लाखों ... किसी को भी एक ही अनुभव है? (हम इसे बेहतर कैशिंग से संबंधित कर सकते हैं, या अन्य प्रश्नों को प्रश्नों के बीच अंतर करने की अनुमति दे सकते हैं ...)
चैम कलार

24

मैं कुछ परीक्षण कर रहा हूं, और जैसा कि डेविड फेल्स अपने जवाब में कहते हैं , यह काफी अच्छी तरह से अनुकूलित है। एक संदर्भ के रूप में, मैंने 1,000,000 रजिस्टरों के साथ एक InnoDB तालिका बनाई है और 500,000 यादृच्छिक संख्याओं के साथ "IN" ऑपरेटर का चयन करते हुए, यह मेरे मैक पर केवल 2.5 सेकंड लेता है; केवल रजिस्टरों का चयन करने में 0.5 सेकंड लगते हैं।

मेरे पास एकमात्र समस्या यह है कि मुझे फ़ाइल max_allowed_packetसे पैरामीटर बढ़ाना था my.cnf। यदि नहीं, तो एक रहस्यमय "MYSQL दूर चला गया है" त्रुटि उत्पन्न होती है।

यहाँ PHP कोड है जो मैं परीक्षण करने के लिए उपयोग करता हूं:

$NROWS =1000000;
$SELECTED = 50;
$NROWSINSERT =15000;

$dsn="mysql:host=localhost;port=8889;dbname=testschema";
$pdo = new PDO($dsn, "root", "root");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$pdo->exec("drop table if exists `uniclau`.`testtable`");
$pdo->exec("CREATE  TABLE `testtable` (
        `id` INT NOT NULL ,
        `text` VARCHAR(45) NULL ,
        PRIMARY KEY (`id`) )");

$before = microtime(true);

$Values='';
$SelValues='(';
$c=0;
for ($i=0; $i<$NROWS; $i++) {
    $r = rand(0,99);
    if ($c>0) $Values .= ",";
    $Values .= "( $i , 'This is value $i and r= $r')";
    if ($r<$SELECTED) {
        if ($SelValues!="(") $SelValues .= ",";
        $SelValues .= $i;
    }
    $c++;

    if (($c==100)||(($i==$NROWS-1)&&($c>0))) {
        $pdo->exec("INSERT INTO `testtable` VALUES $Values");
        $Values = "";
        $c=0;
    }
}
$SelValues .=')';
echo "<br>";


$after = microtime(true);
echo "Insert execution time =" . ($after-$before) . "s<br>";

$before = microtime(true);  
$sql = "SELECT count(*) FROM `testtable` WHERE id IN $SelValues";
$result = $pdo->prepare($sql);  
$after = microtime(true);
echo "Prepare execution time =" . ($after-$before) . "s<br>";

$before = microtime(true);

$result->execute();
$c = $result->fetchColumn();

$after = microtime(true);
echo "Random selection = $c Time execution time =" . ($after-$before) . "s<br>";



$before = microtime(true);

$sql = "SELECT count(*) FROM `testtable` WHERE id %2 = 1";
$result = $pdo->prepare($sql);
$result->execute();
$c = $result->fetchColumn();

$after = microtime(true);
echo "Pairs = $c Exdcution time=" . ($after-$before) . "s<br>";

और परिणाम:

Insert execution time =35.2927210331s
Prepare execution time =0.0161771774292s
Random selection = 499102 Time execution time =2.40285992622s
Pairs = 500000 Exdcution time=0.465420007706s

दूसरों की खातिर, मैं अपने लेट 2013 MBP पर VirtualBox (CentOS) में रनिंग को एक i7 के साथ जोड़ दूंगा, आउटपुट की तीसरी पंक्ति (सवाल के लिए प्रासंगिक) थी: रैंडम चयन = 500744 समय निष्पादन समय = 53.458173036575s .. आपके आवेदन के आधार पर 53 सेकंड सहन करने योग्य हो सकते हैं। मेरे उपयोगों के लिए, वास्तव में नहीं। इसके अलावा, ध्यान दें कि सम संख्याओं के लिए परीक्षण हाथ में सवाल के लिए प्रासंगिक नहीं है क्योंकि यह %एक समान ऑपरेटर ( =) के बजाय modulo ऑपरेटर ( ) का उपयोग करता है IN()
रिनोगो

यह प्रासंगिक है क्योंकि यह इस कार्यक्षमता के बिना एक समान क्वेरी वाले IN ऑपरेटर के साथ क्वेरी की तुलना करने का एक तरीका है। हो सकता है कि आपको मिलने वाला हाइगर समय हो, क्योंकि यह डाउनलोड का समय है, क्योंकि आपकी मशीन स्वैपिंग है या किसी अन्य वर्चुअल मशीन में काम कर रही है।
jbaylina

14

आप एक अस्थायी तालिका बना सकते हैं जहाँ आप किसी भी संख्या में आईडी डाल सकते हैं और नेस्टेड क्वेरी उदाहरण चला सकते हैं:

CREATE [TEMPORARY] TABLE tmp_IDs (`ID` INT NOT NULL,PRIMARY KEY (`ID`));

और विकल्प:

SELECT id, name, price
FROM products
WHERE id IN (SELECT ID FROM tmp_IDs);

6
उप

3
@ लूपकिन क्या आप समझा सकते हैं कि आप कृपया एक उपश्रेणी बनाम ज्वाइन के साथ यह कैसे करेंगे?
जेफ सोलोमन

3
@JffSolomon SELECT products.id, name, price FROM उत्पादों से JOIN tmp_IDs पर products.id = tmp_IDs.ID;
scharette

इस उत्तर दें! मैं क्या देख रहा था, बहुत लंबे समय से रजिस्ट्रियों के लिए बहुत तेज है
Damián Rafael Lattenero

बहुत बहुत धन्यवाद, यार। यह सिर्फ अविश्वसनीय रूप से तेजी से काम करता है।
श्रीफल

4

INरिकॉर्ड की एक बड़ी सूची पर सेट एक बड़े पैरामीटर के साथ उपयोग करना वास्तव में धीमा होगा।

इस मामले में कि मैंने हाल ही में हल किया था मेरे पास दो थे जहां क्लॉस, 2,50 मापदंडों में से एक और 3,500 मापदंडों के साथ, 40 मिलियन रिकॉर्ड की एक तालिका को क्वेरी करते हुए।

मेरी क्वेरी को मानक का उपयोग करते हुए 5 मिनट लगे WHERE INआईएन स्टेटमेंट के लिए एक उपश्रेणी का उपयोग करने के बजाय (मापदंडों को अपनी अनुक्रमित तालिका में डालकर), मैंने क्वेरी को दो सेकंड के लिए नीचे कर दिया।

मेरे अनुभव में MySQL और Oracle दोनों के लिए काम किया।


1
मुझे आपकी बात पर "IN स्टेटमेंट के लिए एक उपश्रेणी का उपयोग करने के बजाय (अपनी खुद की अनुक्रमित तालिका में पैरामीटर डालकर)" नहीं मिला। क्या आपका मतलब यह था कि "WHERE ID IN (1,2,3)" का उपयोग करने के बजाय हमें "WHERE ID IN (Select id FROM xxx)" का उपयोग करना चाहिए?
इस्तियाक दर्जी

4

INठीक है, और अच्छी तरह से अनुकूलित है। सुनिश्चित करें कि आप इसे एक अनुक्रमित क्षेत्र पर उपयोग करते हैं और आप ठीक हैं।

यह कार्यात्मक रूप से इसके बराबर है:

(x = 1 OR x = 2 OR x = 3 ... OR x = 99)

जहां तक ​​DB इंजन की बात है।


1
जरूरी नहि। मैं डीबी से 5k रिकॉर्ड लाने के लिए क्लॉज का उपयोग करता हूं। IN clouse में PKs की सूची शामिल है, इसलिए संबंधित कॉलम को अनुक्रमित किया जाता है और विशिष्ट होने की गारंटी दी जाती है। EXPLAIN का कहना है कि, "पंद्रह-कतार-समान" शैली में PK लुकअप का उपयोग करके पूर्ण तालिका स्कैन किया जाता है।
एंटोनियोसे

MySQL पर मुझे विश्वास नहीं है कि वे "कार्यात्मक रूप से समकक्ष" हैंINबेहतर प्रदर्शन के लिए अनुकूलन का उपयोग करता है।
जोशुआ पिंटर

1
जोश, जवाब 2011 से था - मुझे यकीन है कि चीजें तब से बदल गई हैं, लेकिन दिन में वापस फ्लैट में ओआर बयानों की एक श्रृंखला के रूप में परिवर्तित कर दिया गया था।
डेविड ने 19-22 को

1
यह उत्तर सही नहीं है। से उच्च निष्पादन MySQL ऐसा नहीं है MySQL में, जिसमें () सूची में मानों सॉर्ट करता है और देखने के लिए कि क्या मान सूची में है एक तेजी से बाइनरी खोज का उपयोग करता है:। यह सूची के आकार में O (log n) है, जबकि सूची के आकार में OR (cl) के बराबर श्रृंखला O (n) है (यानी, बड़ी सूचियों के लिए बहुत धीमी)।
बर्ट

बर्ट - हाँ। यह उत्तर अप्रचलित है। एक सुझाव देने के लिए स्वतंत्र महसूस करें।
डेविड फेल्स

-2

जब आप INऑपरेटर के लिए कई मान प्रदान करते हैं तो उसे पहले डुप्लिकेट को निकालने के लिए इसे सॉर्ट करना होगा। कम से कम मुझे उस पर शक है। इसलिए बहुत अधिक मान प्रदान करना अच्छा नहीं होगा, क्योंकि छंटनी में एन लॉग एन समय लगता है।

मेरे अनुभव ने साबित किया कि मूल्यों के समुच्चय को छोटे उपसमूह में खिसकाने और आवेदन में सभी प्रश्नों के परिणामों को संयोजित करने से सर्वश्रेष्ठ प्रदर्शन मिलता है। मैं मानता हूं कि मैंने एक अलग डेटाबेस (व्यापक) पर अनुभव इकट्ठा किया, लेकिन सभी इंजनों पर भी यही लागू हो सकता है। प्रति सेट मूल्यों की मेरी गिनती 500-1000 थी। कम या ज्यादा काफी धीमी थी।


मुझे पता है कि यह 7 साल का है, लेकिन इस जवाब के साथ समस्या बस यह है कि यह एक शिक्षित अनुमान पर आधारित टिप्पणी है।
Giacomo1968
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.