อันไหนเร็วที่สุด เลือก SQL_CALC_FOUND_ROWS จาก 'table' หรือเลือก COUNT (*)


176

เมื่อคุณ จำกัด จำนวนแถวที่จะส่งคืนโดยแบบสอบถาม SQL ซึ่งมักใช้ในการเพจมีสองวิธีในการกำหนดจำนวนระเบียนทั้งหมด:

วิธีที่ 1

รวมSQL_CALC_FOUND_ROWSตัวเลือกในต้นฉบับSELECTแล้วรับจำนวนแถวทั้งหมดโดยเรียกใช้SELECT FOUND_ROWS():

SELECT SQL_CALC_FOUND_ROWS * FROM table WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();  

วิธีที่ 2

เรียกใช้แบบสอบถามโดยปกติแล้วรับจำนวนแถวทั้งหมดด้วยการเรียกใช้ SELECT COUNT(*)

SELECT * FROM table WHERE id > 100 LIMIT 10;
SELECT COUNT(*) FROM table WHERE id > 100;  

วิธีใดดีที่สุด / เร็วที่สุด

คำตอบ:


120

มันขึ้นอยู่กับ. ดูโพสต์ MySQL Performance Blog ในหัวข้อนี้: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

สรุปโดยย่อ: Peter บอกว่าขึ้นอยู่กับดัชนีของคุณและปัจจัยอื่น ๆ ความคิดเห็นจำนวนมากในการโพสต์ดูเหมือนจะบอกว่า SQL_CALC_FOUND_ROWS เกือบจะช้ากว่าเสมอ - บางครั้งมากถึง 10 เท่าช้ากว่า - ใช้คำสั่งสองคำ


27
ฉันสามารถยืนยันสิ่งนี้ได้ - ฉันเพิ่งอัพเดตคิวรีที่มี 4 joins ในฐานข้อมูลแถว 168,000 แถว การเลือก 100 แถวแรกเท่านั้นโดยSQL_CALC_FOUND_ROWSใช้เวลามากกว่า 20 วินาที การใช้COUNT(*)ข้อความค้นหาแยกกันใช้เวลาไม่เกิน 5 วินาที (สำหรับทั้งข้อความค้นหานับ + ผลลัพธ์)
Sam Dufel

9
ผลการวิจัยที่น่าสนใจมาก เนื่องจากเอกสารของ MySQLอย่างชัดเจนแสดงให้เห็นว่าSQL_CALC_FOUND_ROWSจะเร็วขึ้นผมสงสัยในสิ่งที่สถานการณ์ (ถ้ามี) จริง ๆ แล้วมันเป็นได้เร็วขึ้น!
svidgen

12
หัวข้อเก่า แต่สำหรับผู้ที่ยังน่าสนใจ! เพิ่งตรวจสอบ INNODB เสร็จจากการตรวจสอบ 10 ครั้งฉันสามารถบอกได้ว่ามันเป็น 26 (2 แบบสอบถาม) เทียบกับ 9.2 (1 แบบสอบถาม) เลือก SQL_CALC_FOUND_ROWS tblA. *, tblB.id AS 'b_id', tblB.city AS 'b_city', tblC.id AS 'c_id' tblC.type AS 'c_type' tblD.id AS 'd_id' tblD.extype AS 'd_extype' tblY.id AS 'y_id' tblY.ydt AS y_ydt จาก tblA, tblB, tblC, tblD, tblY WHERE tblA.b = tblC.id AND tblA.c = tblB.id AND tblA.d = tblD.id AND tblA.y = tblY.id
Al Po

4
ฉันเพิ่งทำการทดสอบนี้และ SQLC_CALC_FOUND_ROWS นั้นเร็วกว่าการสืบค้นสองรายการ ตอนนี้ตารางหลักของฉันมีเพียง 65k และสองตัวรวมกันไม่กี่ร้อย แต่การสืบค้นหลักใช้เวลา 0.18 วินาทีโดยมีหรือไม่มี SQLC_CALC_FOUND_ROWS แต่เมื่อฉันเรียกใช้การค้นหาครั้งที่สองด้วย COUNT ( id) มันใช้เวลา 0.25 เพียงอย่างเดียว
transilvlad

1
นอกจากปัญหาด้านประสิทธิภาพที่เป็นไปได้แล้วให้พิจารณาว่าFOUND_ROWS()เลิกใช้แล้วใน MySQL 8.0.17 ดูคำตอบของ @ madhur-bhaiya ด้วย
arueckauer

19

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


11
ขึ้นอยู่กับการตั้งค่าของคุณ หากคุณกำลังใช้ ORM หรือตัวสร้างคิวรีบางประเภทคุณสามารถใช้งานได้ง่ายโดยที่เกณฑ์สำหรับทั้งคิวรีสลับเขตข้อมูลที่เลือกสำหรับการนับและปล่อยขีด จำกัด คุณไม่ควรเขียนเกณฑ์สองครั้ง
mpen

ฉันจะชี้ให้เห็นว่าฉันควรรักษารหัสโดยใช้สองมาตรฐานที่ค่อนข้างง่ายและง่ายต่อการเข้าใจแบบสอบถาม SQL มากกว่าหนึ่งที่ใช้คุณสมบัติ MySQL เป็นกรรมสิทธิ์ - ซึ่งเป็นมูลค่า noting จะเลิกใช้ในรุ่น MySQL รุ่นใหม่
thomasrutter

15

MySQL ได้เริ่มลดSQL_CALC_FOUND_ROWSฟังก์ชันการทำงานกับเวอร์ชัน 8.0.17 เป็นต้นไป

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

จากเอกสาร :

ตัวแก้ไขคิวรี่ SQL_CALC_FOUND_ROWS และฟังก์ชัน FOUND_ROWS () ที่มาพร้อมกับเลิกใช้งานแล้วในฐานะของ MySQL 8.0.17 และจะถูกลบออกในรุ่น MySQL ในอนาคต

COUNT (*) ขึ้นอยู่กับการปรับให้เหมาะสมบางอย่าง SQL_CALC_FOUND_ROWS ทำให้การเพิ่มประสิทธิภาพบางอย่างถูกปิดใช้งาน

ใช้คำค้นหาเหล่านี้แทน:

SELECT * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT COUNT(*) WHERE id > 100;

นอกจากนี้SQL_CALC_FOUND_ROWSยังพบว่ามีปัญหามากกว่าปกติดังที่อธิบายไว้ในMySQL WL # 12615 :

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

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

ในที่สุดกรณีการใช้งานส่วนใหญ่ที่ดูเหมือนว่ามีประโยชน์ SQL_CALC_FOUND_ROWS ควรแก้ไขโดยกลไกอื่น ๆ กว่า LIMIT / OFFSET เช่นสมุดโทรศัพท์ควรมีเลขหน้าตัวอักษร (ทั้งในแง่ของ UX และในแง่ของการใช้ดัชนี) ไม่ได้ตามหมายเลขบันทึก การสนทนาจะเพิ่มขึ้นอย่างไม่มีที่สิ้นสุดเลื่อนเรียงตามวันที่ (อนุญาตให้ใช้ดัชนีอีกครั้ง) ไม่ใช่โดยเลขหน้าโพสต์ และอื่น ๆ


วิธีการดำเนินการทั้งสองเลือกเป็นการดำเนินการของอะตอม? จะเกิดอะไรขึ้นถ้ามีคนแทรกแถวหนึ่งหน้าคิวรี SELECT COUNT (*) ขอบคุณ
Dom

@Dom ถ้าคุณมี MySQL8 + คุณสามารถเรียกใช้ทั้งแบบสอบถามในแบบสอบถามเดียวโดยใช้ฟังก์ชั่นหน้าต่าง แต่สิ่งนี้จะไม่เป็นทางออกที่ดีที่สุดเนื่องจากดัชนีจะไม่ถูกใช้อย่างถูกต้อง อีกตัวเลือกหนึ่งคือการล้อมรอบทั้งสองคำสั่งด้วยและLOCK TABLES <tablename> UNLOCK TABLESตัวเลือกที่สามและ (IMHO ที่ดีที่สุด) คือการคิดเลขหน้าใหม่ โปรดอ่าน: mariadb.com/kb/en/library/pagination-optimization
Madhur Bhaiya

14

อ้างอิงจากบทความต่อไปนี้: https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

หากคุณมีINDEXในส่วนคำสั่ง where (ถ้า id ถูกทำดัชนีในกรณีของคุณ) จะเป็นการดีกว่าที่จะไม่ใช้SQL_CALC_FOUND_ROWSและใช้ 2 แบบสอบถามแทน แต่ถ้าคุณไม่มีดัชนีในสิ่งที่คุณใส่ไว้ในส่วนคำสั่งของคุณ (id ในกรณีของคุณ) จากนั้นใช้SQL_CALC_FOUND_ROWSมีประสิทธิภาพมากขึ้น


8

IMHO เหตุผลที่ 2 ข้อสงสัย

SELECT * FROM count_test WHERE b = 666 ORDER BY c LIMIT 5;
SELECT count(*) FROM count_test WHERE b = 666;

เร็วกว่าการใช้ SQL_CALC_FOUND_ROWS

SELECT SQL_CALC_FOUND_ROWS * FROM count_test WHERE b = 555 ORDER BY c LIMIT 5;

จะต้องถูกมองว่าเป็นกรณีเฉพาะ

ในความเป็นจริงแล้วมันขึ้นอยู่กับการเลือกของประโยค WHERE เมื่อเทียบกับการเลือกของนัยหนึ่งเทียบเท่ากับ ORDER + LIMIT

ดังที่ Arvids บอกไว้ในความคิดเห็น ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-toqs_cl_calc_found_rows/#comment-1174394 ) ความจริงที่ว่าการใช้งานจริงหรือไม่ ตาราง temporay ควรเป็นฐานที่ดีสำหรับการรู้ว่า SCFR จะเร็วขึ้นหรือไม่

แต่เมื่อฉันเพิ่ม ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ) ผลลัพธ์จริงๆขึ้นอยู่กับกรณี สำหรับผู้ให้คะแนนเฉพาะคุณสามารถสรุปได้ว่า“ สำหรับ 3 หน้าแรกให้ใช้ 2 แบบสอบถาม สำหรับหน้าต่อไปนี้ให้ใช้ SCFR”!


6

ถอดบาง SQL ที่ไม่จำเป็นแล้วจะเร็วกว่าCOUNT(*) SQL_CALC_FOUND_ROWSตัวอย่าง:

SELECT Person.Id, Person.Name, Job.Description, Card.Number
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
LEFT JOIN Card ON Card.Person_Id = Person.Id
WHERE Job.Name = 'WEB Developer'
ORDER BY Person.Name

จากนั้นนับโดยไม่มีส่วนที่ไม่จำเป็น:

SELECT COUNT(*)
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
WHERE Job.Name = 'WEB Developer'

3

มีตัวเลือกอื่นให้คุณเปรียบเทียบ:

1. )ฟังก์ชั่นหน้าต่างจะคืนขนาดจริงโดยตรง (ทดสอบใน MariaDB):

SELECT 
  `mytable`.*,
  COUNT(*) OVER() AS `total_count`
FROM `mytable`
ORDER BY `mycol`
LIMIT 10, 20

2. ) เมื่อคิดนอกกรอบผู้ใช้ส่วนใหญ่ไม่จำเป็นต้องรู้ขนาดที่แน่นอนของตารางโดยประมาณมักจะดีพอ

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.