ทำไม MYSQL LIMIT ที่สูงกว่าจึงชดเชยการสืบค้นช้าลง


173

สถานการณ์โดยสังเขป: ตารางที่มีมากกว่า 16 ล้านเร็กคอร์ด [ขนาด 2GB] LIMIT ที่สูงขึ้นชดเชยด้วย SELECT ยิ่งการสืบค้นช้าลงเมื่อใช้ ORDER BY * primary_key *

ดังนั้น

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

ใช้เวลาน้อยกว่า

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

นั่นสั่งเฉพาะระเบียน 30 รายการและเหมือนกันเท่านั้น ดังนั้นไม่ใช่ค่าใช้จ่ายจากคำสั่งซื้อ
ตอนนี้เมื่อดึง 30 แถวล่าสุดจะใช้เวลาประมาณ 180 วินาที ฉันจะเพิ่มประสิทธิภาพข้อความค้นหาธรรมดา ๆ ได้อย่างไร


หมายเหตุ: ฉันเป็นผู้เขียน MySQL ไม่ได้อ้างถึงดัชนี (หลัก) ในกรณีข้างต้น ดูลิงก์ด้านล่างโดยผู้ใช้ "Quassnoi" สำหรับคำอธิบาย
Rahman

คำตอบ:


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 "การค้นหาในแถวแรก" เป็นคำตอบว่าทำไมมันถึงใช้เวลาพูดนานมาก ด้วยเคล็ดลับที่คุณให้มารหัสเฉพาะที่จับคู่ (โดยดัชนีโดยตรง) จะถูกผูกไว้บันทึกการค้นหาแถวที่ไม่จำเป็นของระเบียนจำนวนมากเกินไป นั่นเป็นกลอุบายเหรอ!
Rahman

4
@harald: คุณหมายถึงอะไรโดย "ไม่ทำงาน" นี่คือการปรับปรุงประสิทธิภาพที่บริสุทธิ์ หากไม่มีดัชนีที่ใช้งานได้ORDER BYหรือดัชนีครอบคลุมทุกเขตข้อมูลที่คุณต้องการคุณไม่จำเป็นต้องแก้ไขปัญหานี้
Quassnoi

6
@ f055: คำตอบบอกว่า "เร่งความเร็ว" ไม่ใช่ "ทำทันที" คุณอ่านประโยคแรกของคำตอบแล้วหรือยัง
Quassnoi

3
เป็นไปได้ไหมที่จะเรียกใช้บางอย่างเช่นนี้กับ InnoDB?
NeverEndingQueue

3
@Lanti: โปรดโพสต์เป็นคำถามแยกต่างหากและอย่าลืมติดแท็กด้วย postgresqlกรุณาโพสต์ว่ามันเป็นคำถามที่แยกต่างหากและไม่ลืมที่จะติดแท็กด้วย นี่คือคำตอบเฉพาะ MySQL
Quassnoi

220

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

ดังนั้นสิ่งที่คุณสามารถทำได้คือ:

  1. เก็บ id สุดท้ายของชุดข้อมูล (30) (เช่น lastId = 530)
  2. เพิ่มเงื่อนไข WHERE id > lastId limit 0,30

ดังนั้นคุณสามารถชดเชยค่าศูนย์ได้เสมอ คุณจะประหลาดใจกับการปรับปรุงประสิทธิภาพ


ใช้งานได้หรือไม่หากมีช่องว่าง ถ้าคุณไม่มีคีย์เฉพาะอันเดียว (เช่นคีย์ผสม)?
xaisoft

8
อาจไม่ชัดเจนว่าทั้งหมดนี้ใช้งานได้เฉพาะในกรณีที่ชุดผลลัพธ์ของคุณเรียงลำดับตามคีย์นั้นตามลำดับจากน้อยไปหามาก (สำหรับลำดับจากมากไปหาน้อยที่ความคิดเดียวกันใช้งานได้ แต่เปลี่ยน> lastid เป็น <lastid) คีย์หลักหรือฟิลด์อื่น (หรือกลุ่มของฟิลด์)
Eloff

ทำได้ดีมากชายคนนั้น! ทางออกที่ง่ายมากที่ช่วยแก้ปัญหาของฉัน :-)
oodavid

30
เพียงทราบว่าขีด จำกัด / ชดเชยมักจะใช้ในผลลัพธ์หน้าและถือ lastId อาจเป็นเพราะผู้ใช้สามารถข้ามไปยังหน้าใด ๆ ไม่เสมอไปหน้าถัดไป กล่าวอีกนัยหนึ่งการชดเชยมักจะต้องมีการคำนวณแบบไดนามิกตามหน้าและขีด จำกัด แทนที่จะทำตามรูปแบบต่อเนื่อง
Tom

3
ฉันพูดถึงความยาวมากขึ้นเกี่ยวกับ "การจดจำตำแหน่งที่คุณค้าง
Rick James

17

MySQL ไม่สามารถไปที่ระเบียนที่ 10,000 โดยตรง (หรือ 80000th ไบต์ตามที่คุณแนะนำ) เพราะมันไม่สามารถสรุปได้ว่ามันบรรจุ / สั่งเช่นนั้น (หรือว่ามันมีค่าอย่างต่อเนื่องใน 1 ถึง 10,000) แม้ว่ามันอาจเป็นไปได้ว่าในความเป็นจริง, MySQL ไม่สามารถสรุปได้ว่าไม่มีรหัสหลุม / ช่องว่าง / ลบ

ดังนั้นตามที่บ็อบระบุไว้ MySQL จะต้องดึงข้อมูล 10,000 แถว (หรือสำรวจผ่านรายการที่ 10,000 ของดัชนีในid) ก่อนที่จะหา 30 เพื่อส่งคืน

แก้ไข : เพื่อแสดงจุดของฉัน

โปรดทราบว่าแม้ว่า

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

จะช้า (ER) ,

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

จะเร็ว (er)และจะส่งกลับผลลัพธ์เดียวกันโดยมีเงื่อนไขว่าไม่มีการขาดหายไปid(เช่นช่องว่าง)


2
สิ่งนี้ถูกต้อง แต่เนื่องจากมันถูก จำกัด ด้วย "id" ทำไมมันใช้เวลานานมากเมื่อ id นั้นอยู่ในดัชนี (คีย์หลัก) เครื่องมือเพิ่มประสิทธิภาพควรอ้างถึงดัชนีนั้นโดยตรงแล้วดึงข้อมูลแถวที่มีรหัสที่ตรงกัน (ซึ่งมาจากดัชนีนั้น)
Rahman

1
หากคุณใช้ WHERE clause บน id ก็สามารถไปที่เครื่องหมายนั้นได้ อย่างไรก็ตามหากคุณกำหนดขีด จำกัด ให้เรียงลำดับโดย id มันเป็นเพียงตัวนับสัมพัทธ์ถึงจุดเริ่มต้นดังนั้นจึงต้องข้ามไปตลอดทาง
Riedsio

บทความที่ดีมากeversql.com/…
เริ่ม

ทำงานให้ฉัน @Riedsio ขอบคุณ
mahesh kajale

8

ฉันพบตัวอย่างที่น่าสนใจในการเพิ่มประสิทธิภาพการสืบค้น SELECT เรียงลำดับตาม id LIMIT X, Y ฉันมี 35 ล้านแถวดังนั้นมันใช้เวลา 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 นั่นเป็นเรื่องจริงหากคุณทำงานภายใต้สมมติฐานที่ว่าคิวรีของคุณสามารถทำการค้นหาที่หน้าสุ่มซึ่งฉันไม่เชื่อว่ามีผู้โพสต์นี้ แม้ว่าฉันจะไม่ชอบวิธีการนี้ในกรณีของโลกแห่งความเป็นจริงส่วนใหญ่ แต่วิธีนี้จะใช้ได้กับช่องว่างตราบใดที่คุณยังใช้รหัสประจำตัวสุดท้ายที่ได้รับ
Gremio

5

ส่วนที่ใช้เวลานานของแบบสอบถามทั้งสองกำลังดึงแถวออกจากตาราง การพูดอย่างมีเหตุผลในLIMIT 0, 30รุ่นนี้จะต้องดึงเพียง 30 แถว ในLIMIT 10000, 30เวอร์ชันจะมีการประเมินแถว 10,000 แถวและส่งคืนแถว 30 แถว อาจมีการปรับให้เหมาะสมสามารถทำกระบวนการอ่านข้อมูลของฉันได้ แต่พิจารณาสิ่งต่อไปนี้

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

พิจารณากรณีที่แถวไม่ถูกประมวลผลในลำดับ ORDER BY แถวที่ผ่านการคัดเลือกทั้งหมดต้องถูกเรียงลำดับเพื่อกำหนดว่าแถวใดที่จะส่งคืน


1
แค่สงสัยว่าทำไมมันใช้เวลาในการดึงข้อมูล 10,000 แถว ดัชนีที่ใช้ในฟิลด์นั้น (id ซึ่งเป็นคีย์หลัก) ควรทำการดึงข้อมูลแถวเหล่านั้นอย่างรวดเร็วเท่ากับการค้นหาดัชนี PK นั้นเพื่อบันทึกหมายเลข 10,000 ซึ่งควรจะเร็วเหมือนการค้นหาไฟล์ที่ออฟเซ็ตคูณด้วยความยาวเรคคอร์ดดัชนี (เช่นการค้นหา 10000 * 8 = ไบต์ที่ 80000 - เนื่องจาก 8 คือความยาวเรคคอร์ดดัชนี)
Rahman

@Rahman - วิธีเดียวที่จะนับที่ผ่านมา 10,000 แถวคือการข้ามพวกเขาทีละคน นี่อาจเกี่ยวข้องกับดัชนี แต่ยังคงแถวดัชนีใช้เวลาในการผ่าน นอกจากนี้ไม่มี MyISAM หรือโครงสร้าง InnoDB ที่สามารถได้อย่างถูกต้อง (ในทุกกรณี) "แสวงหา" เพื่อบันทึก 10000 10000 * 8 ข้อเสนอแนะอนุมาน (1) MyISAM (2) ถาวรบันทึกความยาวและ (3) ไม่เคยลบใด ๆ จากตาราง . อย่างไรก็ตามดัชนี MyISAM เป็น BTrees ดังนั้นมันจะไม่ทำงาน
Rick James

ตามคำตอบนี้ฉันเชื่อว่าส่วนที่ช้าจริงๆคือการค้นหาแถวไม่ใช่การสำรวจดัชนี (ซึ่งแน่นอนว่าจะเพิ่มขึ้นเช่นกัน แต่ไม่มีที่ไหนใกล้เท่าการค้นหาแถวบนดิสก์) จากคำค้นหาวิธีแก้ปัญหาที่ให้ไว้สำหรับปัญหานี้ฉันเชื่อว่าการค้นหาแถวมีแนวโน้มที่จะเกิดขึ้นหากคุณเลือกคอลัมน์นอกดัชนี - แม้ว่าจะไม่ได้เป็นส่วนหนึ่งของการสั่งซื้อโดยหรือที่ข้อใดก็ตาม ฉันไม่พบเหตุผลว่าทำไมจึงมีความจำเป็น แต่ดูเหมือนว่าทำไมการแก้ไขปัญหาบางอย่างจึงช่วย
Gremio

1

สำหรับผู้ที่มีความสนใจในการเปรียบเทียบและตัวเลข :)

การทดลองที่ 1: ชุดข้อมูลมีประมาณ 100 ล้านแถว แต่ละแถวประกอบด้วย BIGINT, TINYINT และสองฟิลด์ TEXT (โดยเจตนา) ที่มีประมาณ 1k chars

  • สีน้ำเงิน: = SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • Orange: = @ วิธีการ Quassnoi 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 BIGINT

  • สีเขียว: = สีน้ำเงินมาก่อน
  • สีแดง: = ส้มก่อน

ป้อนคำอธิบายรูปภาพที่นี่

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