การแบ่งหน้า MySQL โดยไม่ต้องค้นหาซ้ำ


115

ฉันสงสัยว่ามีวิธีรับจำนวนผลลัพธ์จากแบบสอบถาม MySQL หรือไม่และในเวลาเดียวกันก็ จำกัด ผลลัพธ์ด้วย

วิธีการแบ่งหน้า (ตามที่ฉันเข้าใจ) ก่อนอื่นฉันจะทำสิ่งที่ชอบ

query = SELECT COUNT(*) FROM `table` WHERE `some_condition`

หลังจากที่ฉันได้รับ num_rows (แบบสอบถาม) ฉันมีจำนวนผลลัพธ์ แต่เพื่อ จำกัด ผลลัพธ์ของฉันจริงๆฉันต้องทำแบบสอบถามที่สองเช่น:

query2 = SELECT COUNT(*) FROM `table` WHERE `some_condition` LIMIT 0, 10

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


8
แม้ว่าคุณจะไม่มี COUNT (*) ในแบบสอบถาม 2
dlofrodloh

คำตอบ:


66

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

วิธีอื่น ๆ คือการใช้ประโยคและโทรแล้วSQL_CALC_FOUND_ROWS SELECT FOUND_ROWS()นอกเหนือจากข้อเท็จจริงที่คุณต้องวางFOUND_ROWS()สายในภายหลังยังมีปัญหากับสิ่งนี้: มีข้อบกพร่องใน MySQLที่สิ่งนี้ORDER BYทำให้เกิดการสืบค้นที่ส่งผลต่อการสืบค้นทำให้ช้าลงบนตารางขนาดใหญ่มากกว่าวิธีการไร้เดียงสาของสองแบบสอบถาม


2
อย่างไรก็ตามยังไม่สามารถพิสูจน์สภาพการแข่งขันได้เว้นแต่คุณจะทำการสืบค้นสองข้อภายในธุรกรรม โดยทั่วไปแล้วสิ่งนี้ไม่ใช่ปัญหา
NickZoic

โดย "เชื่อถือได้" ฉันหมายความว่า SQL จะส่งคืนผลลัพธ์ที่คุณต้องการเสมอและด้วยการ "กันกระสุน" ฉันหมายความว่าไม่มีจุดบกพร่องของ MySQL ขัดขวางสิ่งที่คุณสามารถใช้ได้ ไม่เหมือนกับการใช้ SQL_CALC_FOUND_ROWS กับ ORDER BY และ LIMIT ตามข้อบกพร่องที่ฉันกล่าวถึง
สถิติ

5
ในแบบสอบถามที่ซับซ้อนการใช้ SQL_CALC_FOUND_ROWS เพื่อดึงข้อมูลจำนวนในแบบสอบถามเดียวกันมักจะช้ากว่าการทำแบบสอบถามสองรายการแยกกัน เนื่องจากหมายความว่าทุกแถวจะต้องได้รับการดึงข้อมูลทั้งหมดโดยไม่คำนึงถึงขีด จำกัด จากนั้นจะส่งกลับเฉพาะที่ระบุในส่วนคำสั่ง LIMIT เท่านั้น ดูคำตอบของฉันซึ่งมีลิงก์
thomasrutter

ขึ้นอยู่กับเหตุผลที่คุณต้องการสิ่งนี้คุณอาจต้องคิดว่าแค่ไม่ได้รับผลลัพธ์ทั้งหมด การใช้วิธีการเพจอัตโนมัติเป็นเรื่องปกติมากขึ้น เว็บไซต์เช่น Facebook, Twitter, Bing และ Google ใช้วิธีนี้มานานแล้ว
Thomas B

68

ฉันแทบไม่เคยทำแบบสอบถามสองครั้ง

เพียงส่งกลับมากกว่าหนึ่งแถวที่ต้องการเพียงแสดง 10 ในหน้าและหากมีมากกว่าที่แสดงอยู่ให้แสดงปุ่ม "ถัดไป"

SELECT x, y, z FROM `table` WHERE `some_condition` LIMIT 0, 11
// iterate through and display 10 rows.

// if there were 11 rows, display a "Next" button.

คำถามของคุณควรส่งคืนตามลำดับที่เกี่ยวข้องมากที่สุดก่อน มีโอกาสที่คนส่วนใหญ่จะไม่สนใจที่จะไปที่หน้า 236 จาก 412

เมื่อคุณค้นหาโดย Google และผลลัพธ์ของคุณไม่อยู่ในหน้าแรกคุณน่าจะไปที่หน้าที่สองไม่ใช่เก้า


42
จริงๆแล้วถ้าฉันไม่พบในหน้าแรกของข้อความค้นหาของ Google ฉันมักจะข้ามไปที่หน้าเก้า
ฟิลิป

3
@ ฟิลฉันได้ยินมาก่อน แต่ทำไมทำแบบนั้น
TK123

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

4
COUNTเป็นฟังก์ชันรวม คุณส่งคืนจำนวนและผลลัพธ์ทั้งหมดในแบบสอบถามเดียวได้อย่างไร ข้อความค้นหาด้านบนจะแสดงผลเพียง 1 แถวไม่ว่าLIMITจะตั้งค่าไว้ที่ใดก็ตาม หากคุณเพิ่มGROUP BYมันจะส่งกลับผลลัพธ์ทั้งหมด แต่COUNTจะไม่ถูกต้อง
pixelfreak

2
นี่คือหนึ่งในแนวทางที่แนะนำโดย Percona: percona.com/blog/2008/09/24/…
techdude

27

อีกวิธีหนึ่งในการหลีกเลี่ยงการสืบค้นซ้ำคือการดึงข้อมูลแถวทั้งหมดสำหรับหน้าปัจจุบันโดยใช้ส่วนคำสั่ง LIMIT ก่อนจากนั้นทำการสืบค้น COUNT (*) ที่สองเท่านั้นหากมีการเรียกจำนวนแถวสูงสุด

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

ตัวอย่างเช่นคำตอบของคำถาม stackoverflow แทบจะไม่ปรากฏในหน้าที่สอง ความคิดเห็นเกี่ยวกับคำตอบมักจะล้นเกินขีด จำกัด 5 ข้อหรือมากกว่านั้นที่จำเป็นในการแสดงทั้งหมด

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


1
@thomasrutter ฉันมีแนวทางเดียวกัน แต่ค้นพบข้อบกพร่องในวันนี้ จากนั้นหน้าสุดท้ายของผลลัพธ์จะไม่มีข้อมูลการแบ่งหน้า กล่าวคือสมมติว่าแต่ละหน้าควรมี 25 ผลลัพธ์หน้าสุดท้ายจะมีจำนวนไม่มากสมมติว่ามี 7 ... ซึ่งหมายความว่า count (*) จะไม่ถูกเรียกใช้และจะไม่มีการแสดงเลขหน้าให้กับ ผู้ใช้
duellsy

2
ไม่ - ถ้าคุณบอกว่าผลลัพธ์ 200 รายการคุณจะค้นหา 25 รายการถัดไปและคุณได้รับกลับมาเพียง 7 รายการซึ่งจะบอกคุณว่าจำนวนผลลัพธ์ทั้งหมดคือ 207 ดังนั้นคุณไม่จำเป็นต้องทำการสืบค้นอีกครั้งด้วย COUNT (*) เพราะคุณรู้อยู่แล้วว่ามันจะพูดอะไร คุณมีข้อมูลทั้งหมดที่จำเป็นในการแสดงเลขหน้า หากคุณมีปัญหากับการแบ่งหน้าไม่แสดงต่อผู้ใช้แสดงว่าคุณมีข้อบกพร่องที่อื่น
thomasrutter

15

ในสถานการณ์ส่วนใหญ่จะเร็วกว่ามากและใช้ทรัพยากรน้อยกว่าที่จะทำในสองแบบสอบถามแยกกันมากกว่าที่จะทำในหนึ่งแม้ว่าจะดูเหมือนว่าใช้งานง่าย

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

หากคุณใช้วิธีการสืบค้นสองวิธีแบบแรกดึงข้อมูล COUNT (*) เพียงอย่างเดียวและไม่ดึงข้อมูลจริงและข้อมูลจริงสิ่งนี้สามารถตอบสนองได้เร็วกว่ามากเพราะโดยปกติจะใช้ดัชนีและไม่ต้องดึงข้อมูลแถวจริงสำหรับ ทุกแถวที่ดู จากนั้นแบบสอบถามที่สองจะต้องดูเฉพาะแถวแรก $ offset + $ limit จากนั้นจึงกลับมา

โพสต์จากบล็อกประสิทธิภาพ MySQL นี้อธิบายเพิ่มเติม:

http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการเพิ่มประสิทธิภาพการแบ่งหน้าโปรดดูที่โพสต์นี้และโพสต์นี้


2

คำตอบของฉันอาจจะช้า แต่คุณสามารถข้ามแบบสอบถามที่สองได้ (ด้วยขีด จำกัด ) และเพียงแค่กรองข้อมูลผ่านสคริปต์ส่วนหลังของคุณ ตัวอย่างเช่นใน PHP คุณสามารถทำสิ่งต่างๆเช่น:

if($queryResult > 0) {
   $counter = 0;
   foreach($queryResult AS $result) {
       if($counter >= $startAt AND $counter < $numOfRows) {
            //do what you want here
       }
   $counter++;
   }
}

แต่แน่นอนว่าเมื่อคุณมีบันทึกหลายพันรายการที่ต้องพิจารณามันจะไม่มีประสิทธิภาพอย่างรวดเร็ว การนับที่คำนวณล่วงหน้าอาจเป็นความคิดที่ดีที่จะพิจารณา

อ่านเรื่องนี้ได้ดี: http://www.percona.com/ppc2009/PPC2009_mysql_pagination.pdf


ลิงค์ตายแล้วฉันเดาว่านี่คือสิ่งที่ถูกต้อง: percona.com/files/presentations/ppc2009/… . จะไม่แก้ไขเพราะไม่แน่ใจว่าใช่หรือไม่
hectorg87

1
query = SELECT col, col2, (SELECT COUNT(*) FROM `table`) AS total FROM `table` WHERE `some_condition` LIMIT 0, 10

16
แบบสอบถามนี้ส่งกลับจำนวนระเบียนทั้งหมดในตาราง ไม่ใช่จำนวนระเบียนที่ตรงกับเงื่อนไข
Lawrence Barsanti

1
จำนวนระเบียนทั้งหมดคือสิ่งที่จำเป็นสำหรับการแบ่งหน้า (@Lawrence)
imme

อ้อเพียงแค่เพิ่มส่วนwhereคำสั่งลงในข้อความค้นหาด้านในและคุณจะได้ "ผลรวม" ที่ถูกต้องควบคู่ไปกับผลลัพธ์แบบเพจ (เพจถูกเลือกด้วยlimitอนุประโยค
Erenor Paz

จำนวนการสืบค้นย่อย (*) จะต้องเหมือนกันโดยที่อนุประโยคมิฉะนั้นจะไม่ส่งคืนจำนวนผลลัพธ์ที่ถูกต้อง
AKrush95

1

สำหรับใครที่กำลังมองหาคำตอบในปี 2020 ตามเอกสารของ MySQL:

"ตัวปรับเปลี่ยนแบบสอบถาม SQL_CALC_FOUND_ROWS และฟังก์ชัน FOUND_ROWS () ที่มาพร้อมกับฟังก์ชันนั้นเลิกใช้แล้วเมื่อ MySQL 8.0.17 และจะถูกลบออกในเวอร์ชัน MySQL ในอนาคตโดยแทนที่ด้วยการพิจารณาดำเนินการสืบค้นของคุณด้วย LIMIT จากนั้นแบบสอบถามที่สองที่มี COUNT (*) และไม่ จำกัด จำนวนเพื่อตรวจสอบว่ามีแถวเพิ่มเติมหรือไม่ "

ฉันเดาว่ามันคงที่

https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_found-rows


0

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

SELECT Movie.*, (
    SELECT Count(1) FROM Movie
        INNER JOIN MovieGenre 
        ON MovieGenre.MovieId = Movie.Id AND MovieGenre.GenreId = 11
    WHERE Title LIKE '%s%'
) AS Count FROM Movie 
    INNER JOIN MovieGenre 
    ON MovieGenre.MovieId = Movie.Id AND MovieGenre.GenreId = 11
WHERE Title LIKE '%s%' LIMIT 8;

โปรดทราบว่าฉันไม่ใช่ผู้เชี่ยวชาญด้านฐานข้อมูลและฉันหวังว่าใครบางคนจะสามารถเพิ่มประสิทธิภาพให้ดีขึ้นได้เล็กน้อย ในขณะที่มันทำงานได้โดยตรงจากอินเทอร์เฟซบรรทัดคำสั่ง SQL ทั้งคู่ใช้เวลา ~ 0.02 วินาทีบนแล็ปท็อปของฉัน


-14
SELECT * 
FROM table 
WHERE some_condition 
ORDER BY RAND()
LIMIT 0, 10

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