ประสิทธิภาพของตัวดำเนินการ MySQL“ IN” บนจำนวนค่า (ใหญ่?)


94

ฉันได้ทดลองกับ Redis และ MongoDB เมื่อไม่นานมานี้และดูเหมือนว่ามักจะมีหลายกรณีที่คุณจะเก็บอาร์เรย์ของidไว้ใน MongoDB หรือ Redis ฉันจะติดกับ Redis สำหรับคำถามนี้ตั้งแต่ฉันถามเกี่ยวกับ MySQL ในการประกอบการ

ฉันสงสัยว่านักแสดงเป็นอย่างไรในการแสดงรายการIDจำนวนมาก (300-3000) ในตัวดำเนินการ IN ซึ่งจะมีลักษณะดังนี้:

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

ลองนึกภาพง่ายๆอย่างตารางผลิตภัณฑ์และหมวดหมู่ซึ่งโดยปกติคุณอาจเข้าร่วมด้วยกันเพื่อรับผลิตภัณฑ์จากหมวดหมู่หนึ่งๆ ในตัวอย่างด้านบนคุณจะเห็นว่าภายใต้หมวดหมู่ที่กำหนดใน Redis ( category:4:product_ids) ฉันส่งคืนรหัสผลิตภัณฑ์ทั้งหมดจากหมวดหมู่ด้วยรหัส 4 และวางไว้ในSELECTข้อความค้นหาด้านบนภายในตัวINดำเนินการ

นักแสดงเป็นอย่างไร?

นี่คือสถานการณ์ "ขึ้นอยู่กับ" หรือไม่? หรือมีรูปธรรม "นี่คือ (ไม่) ยอมรับ" หรือ "เร็ว" หรือ "ช้า" หรือฉันควรเพิ่ม a LIMIT 25หรือไม่ช่วย?

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

หรือฉันควรตัดอาร์เรย์ของรหัสผลิตภัณฑ์ที่ Redis ส่งคืนเพื่อ จำกัด ไว้ที่ 25 และเพิ่มเพียง 25 id ในแบบสอบถามแทนที่จะเป็น 3000 และกำหนดLIMITให้เป็น 25 จากภายในแบบสอบถาม

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

ข้อเสนอแนะ / ข้อเสนอแนะใด ๆ ชื่นชมมาก!


ฉันไม่แน่ใจว่าคุณกำลังถามอะไร? หนึ่งคำค้นหาที่มี "id IN (1,2,3, ... 3000))" เร็วกว่า 3000 คำค้นหาที่มี "id = value" แต่การเข้าร่วมด้วย "category = 4" จะเร็วกว่าทั้งสองข้อข้างต้น
Ronnis

ถูกต้องแม้ว่าเนื่องจากผลิตภัณฑ์สามารถอยู่ในหลายหมวดหมู่ฉันจึงไม่สามารถทำ "หมวดหมู่ = 4" ได้ การใช้ Redis ฉันจะจัดเก็บ id ทั้งหมดของผลิตภัณฑ์ที่อยู่ในหมวดหมู่หนึ่งแล้วค้นหาสิ่งนั้น ฉันเดาว่าคำถามที่แท้จริงคือประสิทธิภาพจะเป็นอย่างไรid IN (1,2,3 ... 3000)เมื่อเทียบกับตาราง JOIN ของproducts_categories. หรือนั่นคือสิ่งที่คุณกำลังพูด?
Michael van Rooijen

ระวังให้ดีจากข้อผิดพลาดใน MySql stackoverflow.com/questions/3417074/…
Itay Moav -Malimovka

แน่นอนว่าไม่มีเหตุผลว่าทำไมจึงไม่น่าจะมีประสิทธิภาพเท่ากับวิธีอื่น ๆ ในการดึงข้อมูลแถวที่จัดทำดัชนี ขึ้นอยู่กับว่าผู้เขียนฐานข้อมูลได้ทดสอบและปรับให้เหมาะสมหรือไม่ ในแง่ของความซับซ้อนในการคำนวณเราจะจัดเรียง O (n log N) ที่แย่ที่สุดในINประโยค (สิ่งนี้อาจเป็นเส้นตรงในรายการที่เรียงลำดับเช่นที่คุณแสดงขึ้นอยู่กับอัลกอริทึม) จากนั้นจึงตัดกันเชิงเส้น / การค้นหา .
jberryman

คำตอบ:


40

โดยทั่วไปถ้าINรายการมีขนาดใหญ่เกินไป (สำหรับค่าที่กำหนดไว้ไม่ถูกต้องของ 'ใหญ่เกินไป' ซึ่งมักจะอยู่ในพื้นที่ 100 หรือน้อยกว่า) การใช้การรวมจะมีประสิทธิภาพมากขึ้นการสร้างตารางชั่วคราวหากจำเป็น เพื่อเก็บตัวเลข

ถ้าตัวเลขที่มีการตั้งค่าที่มีความหนาแน่นสูง (ช่องว่างไม่มี - ซึ่งข้อมูลตัวอย่างที่แสดงให้เห็น) WHERE id BETWEEN 300 AND 3000จากนั้นคุณสามารถทำได้ดียิ่งขึ้นด้วย

อย่างไรก็ตามสันนิษฐานว่ามีช่องว่างในชุด ณ จุดนี้อาจเป็นการดีกว่าที่จะไปกับรายการค่าที่ถูกต้องหลังจากทั้งหมด (เว้นแต่ช่องว่างจะมีจำนวนค่อนข้างน้อยซึ่งในกรณีนี้คุณสามารถใช้:

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

หรืออะไรก็ตามที่มีช่องว่าง


46
คุณช่วยยกตัวอย่าง "ใช้การรวมสร้างตารางชั่วคราว" ได้ไหม
Jake

หากชุดข้อมูลมาจากอินเทอร์เฟซ (องค์ประกอบแบบเลือกได้หลายรายการ) และมีช่องว่างในข้อมูลที่เลือกและช่องว่างนี้ไม่ใช่ช่องว่างตามลำดับ (หายไป: 457, 490, 658, .. ) AND id NOT BETWEEN XXX AND XXXจะไม่ทำงานและจะดีกว่า ยึดติดกับสิ่งที่เทียบเท่า(x = 1 OR x = 2 OR x = 3 ... OR x = 99)กับที่ @David Fells เขียนไว้
deepcell

จากประสบการณ์ของฉัน - การทำงานบนเว็บไซต์อีคอมเมิร์ซเราต้องแสดงผลการค้นหา ~ 50 รหัสผลิตภัณฑ์ที่ไม่เกี่ยวข้องเราได้ผลลัพธ์ที่ดีกว่าด้วย "1. 50 คำค้นหาที่แยกจากกัน" เทียบกับ "2. ข้อความค้นหาหนึ่งรายการที่มีหลายค่าในช่อง" IN อนุประโยค "" ฉันไม่มีทางพิสูจน์ได้ในขณะนี้ยกเว้นว่าข้อความค้นหา # 2 จะแสดงเป็นข้อความค้นหาที่ช้าในระบบการตรวจสอบของเราเสมอในขณะที่ # 1 จะไม่ปรากฏขึ้นเลยไม่ว่าจำนวนการดำเนินการจะอยู่ใน คนล้าน ... มีใครเคยมีประสบการณ์แบบเดียวกันบ้างไหม? (เราอาจจะเชื่อมโยงกับแคชที่ดีกว่านี้หรือปล่อยให้การสืบค้นอื่น ๆ แทรกระหว่างข้อความค้นหา ... )
Chaim Klar

24

ฉันได้ทำการทดสอบบางอย่างและดังที่ David Fells กล่าวในคำตอบของเขามันก็ค่อนข้างเหมาะสม เพื่อเป็นข้อมูลอ้างอิงฉันได้สร้างตาราง InnoDB ที่มีการลงทะเบียน 1,000,000 รายการและทำการเลือกด้วยโอเปอเรเตอร์ "IN" ที่มีตัวเลขสุ่ม 500,000 หมายเลขใช้เวลาเพียง 2.5 วินาทีบน MAC ของฉัน การเลือกเฉพาะรีจิสเตอร์ใช้เวลา 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

เพื่อประโยชน์ของผู้อื่นฉันจะเพิ่มการทำงานใน VirtualBox (CentOS) บน MBP ปลายปี 2013 ของฉันด้วย i7 บรรทัดที่สาม (บรรทัดที่เกี่ยวข้องกับคำถาม) ของผลลัพธ์คือ: การเลือกแบบสุ่ม = 500744 เวลาดำเนินการตามเวลา = 53.458173036575s .. 53 วินาทีอาจพอทนได้ขึ้นอยู่กับการใช้งานของคุณ สำหรับการใช้งานของฉันไม่ได้จริงๆ นอกจากนี้ยังทราบว่าการทดสอบสำหรับตัวเลขแม้ไม่เกี่ยวข้องกับคำถามที่อยู่ในมือเพราะมันใช้ตัวดำเนินการแบบโมดูโล ( %) ที่มีค่าเท่ากับผู้ประกอบการ ( =) IN()แทน
rinogo

มีความเกี่ยวข้องเนื่องจากเป็นวิธีเปรียบเทียบข้อความค้นหากับตัวดำเนินการ IN ที่มีข้อความค้นหาที่คล้ายกันโดยไม่มีฟังก์ชันนี้ อาจเป็นเวลาที่คุณได้รับมากขึ้นเนื่องจากเป็นเวลาดาวน์โหลดเนื่องจากเครื่องของคุณกำลังแลกเปลี่ยนหรือทำงานในเครื่องเสมือนเครื่องอื่น
jbaylina

14

คุณสามารถสร้างตารางชั่วคราวที่คุณสามารถใส่ ID จำนวนเท่าใดก็ได้และเรียกใช้แบบสอบถามที่ซ้อนกันตัวอย่าง:

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
จะดีกว่าที่จะเข้าร่วมตารางชั่วคราวของคุณแทนที่จะใช้แบบสอบถามย่อย
scharette

3
@loopkin คุณช่วยอธิบายได้ไหมว่าคุณจะทำสิ่งนี้กับการเข้าร่วมกับแบบสอบถามย่อยได้อย่างไร
Jeff Solomon

3
@jeffSolomon SELECT products.id, ชื่อ, ราคาจากผลิตภัณฑ์ JOIN tmp_IDs บน products.id = tmp_IDs.ID;
scharette

คำตอบนี้! คือสิ่งที่ฉันกำลังมองหาเร็วมากสำหรับการลงทะเบียนที่ยาวนาน
Damián Rafael Lattenero

ขอบคุณมากผู้ชาย มันทำงานได้เร็วอย่างไม่น่าเชื่อ
mrHalfer

4

การใช้INกับพารามิเตอร์ขนาดใหญ่ที่กำหนดไว้ในรายการบันทึกจำนวนมากในความเป็นจริงจะช้า

ในกรณีที่ฉันแก้ไขเมื่อเร็ว ๆ นี้ฉันมีสองโดยที่ส่วนคำสั่งหนึ่งมี 2,50 พารามิเตอร์และอีก 3,500 พารามิเตอร์ค้นหาตารางที่มีบันทึก 40 ล้านรายการ

แบบสอบถามของฉันเอา 5 WHERE INนาทีโดยใช้มาตรฐาน โดยใช้แบบสอบถามย่อยสำหรับคำสั่งIN แทน (ใส่พารามิเตอร์ในตารางที่จัดทำดัชนีของตัวเอง) ฉันได้แบบสอบถามลงเหลือสองวินาที

ทำงานสำหรับทั้ง MySQL และ Oracle จากประสบการณ์ของฉัน


1
ฉันไม่เข้าใจประเด็นของคุณที่ "โดยแทนที่จะใช้คำค้นหาย่อยสำหรับคำสั่ง IN (ใส่พารามิเตอร์ในตารางที่จัดทำดัชนีของพวกเขาเอง)" คุณหมายความว่าแทนที่จะใช้ "WHERE ID IN (1,2,3)" เราควรใช้ "WHERE ID IN (SELECT id FROM xxx)" หรือไม่
Istiyak Tailor

4

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

เทียบเท่ากับการทำงาน:

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

เท่าที่เครื่องยนต์ DB เป็นห่วง


1
ไม่จริง ฉันใช้ IN clouse เพื่อดึงข้อมูล 5k จาก DB IN clouse มีรายการ PK ดังนั้นคอลัมน์ที่เกี่ยวข้องจึงถูกจัดทำดัชนีและรับประกันว่าจะไม่ซ้ำ EXPLAIN กล่าวว่าการสแกนแบบเต็มตารางจะดำเนินการโดยใช้การค้นหา PK ในรูปแบบ "fifo-que-alike"
Antoniossss

บน MySQL ผมไม่เชื่อว่าพวกเขาจะเทียบเท่า "หน้าที่" INใช้การเพิ่มประสิทธิภาพเพื่อประสิทธิภาพที่ดีขึ้น
Joshua Pinter

1
Josh คำตอบคือตั้งแต่ปี 2011 - ฉันแน่ใจว่ามีการเปลี่ยนแปลงตั้งแต่นั้นมา แต่ย้อนกลับไปในวันที่ IN ถูกแบนโดยแปลงเป็นชุดของคำสั่ง OR
David Fells

1
คำตอบนี้ไม่ถูกต้อง จากMySQL ประสิทธิภาพสูง : ไม่เป็นเช่นนั้นใน MySQL ซึ่งจะเรียงลำดับค่าในรายการ IN () และใช้การค้นหาแบบไบนารีอย่างรวดเร็วเพื่อดูว่ามีค่าอยู่ในรายการหรือไม่ นี่คือ O (log n) ในขนาดของรายการในขณะที่ชุดคำสั่ง OR ที่เทียบเท่ากันคือ O (n) ในขนาดของรายการ (เช่นช้ากว่ามากสำหรับรายการขนาดใหญ่)
เบิร์ต

เบิร์ต - ใช่ คำตอบนี้ล้าสมัย อย่าลังเลที่จะแนะนำการแก้ไข
David Fells

-2

เมื่อคุณระบุหลายค่าสำหรับตัวINดำเนินการอันดับแรกจะต้องเรียงลำดับเพื่อลบรายการที่ซ้ำกัน อย่างน้อยฉันก็สงสัยว่า ดังนั้นจึงไม่เป็นการดีที่จะระบุค่ามากเกินไปเนื่องจากการเรียงลำดับใช้เวลา N log N

ประสบการณ์ของฉันพิสูจน์แล้วว่าการแบ่งชุดของค่าออกเป็นชุดย่อยที่เล็กลงและการรวมผลลัพธ์ของการสืบค้นทั้งหมดในแอปพลิเคชันให้ประสิทธิภาพที่ดีที่สุด ฉันยอมรับว่าฉันรวบรวมประสบการณ์ในฐานข้อมูลอื่น (Pervasive) แต่สิ่งเดียวกันนี้อาจนำไปใช้กับเอ็นจิ้นทั้งหมด จำนวนค่าต่อชุดของฉันคือ 500-1000 มากหรือน้อยช้าลงอย่างมาก


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