Cái nào nhanh nhất? CHỌN SQL_CALC_FOUND_lawS TỪ `bảng` hoặc CHỌN COUNT (*)


176

Khi bạn giới hạn số lượng hàng được trả về bởi một truy vấn SQL, thường được sử dụng trong phân trang, có hai phương pháp để xác định tổng số bản ghi:

Phương pháp 1

Bao gồm SQL_CALC_FOUND_ROWStùy chọn trong bản gốc SELECTvà sau đó nhận tổng số hàng bằng cách chạy SELECT FOUND_ROWS():

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

Cách 2

Chạy truy vấn bình thường và sau đó nhận được tổng số hàng bằng cách chạy SELECT COUNT(*)

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

Phương pháp nào là tốt nhất / nhanh nhất?

Câu trả lời:


120

Nó phụ thuộc. Xem bài đăng trên Blog về Hiệu suất của MySQL về chủ đề này: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Chỉ là một bản tóm tắt nhanh: Peter nói rằng nó phụ thuộc vào chỉ số của bạn và các yếu tố khác. Nhiều ý kiến ​​cho bài viết dường như nói rằng SQL_CALC_FOUND_lawS hầu như luôn chậm hơn - đôi khi chậm hơn đến 10 lần - so với việc chạy hai truy vấn.


27
Tôi có thể xác nhận điều này - Tôi vừa cập nhật một truy vấn với 4 tham gia trên cơ sở dữ liệu hàng 168.000. Chỉ chọn 100 hàng đầu tiên SQL_CALC_FOUND_ROWStrong hơn 20 giây; sử dụng một COUNT(*)truy vấn riêng biệt mất dưới 5 giây (đối với cả truy vấn đếm + kết quả).
Sam Dufel

9
Những phát hiện rất thú vị. Kể từ tài liệu MySQL của một cách rõ ràng cho thấy rằng SQL_CALC_FOUND_ROWSsẽ nhanh hơn, tôi tự hỏi trong những tình huống (nếu có) nó thực sự nhanh hơn!
Svidgen

12
chủ đề cũ, nhưng cho những người vẫn còn thú vị! Vừa hoàn thành kiểm tra của tôi trên INNODB từ 10 kiểm tra, tôi có thể biết rằng đó là 26 (2query) so với 9.2 (1 truy vấn) CHỌN SQL_CALC_FOUND_lawS 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 TỪ tblA, tblB, tblC, tblD, tblY Ở ĐÂU tblA.b = tblC.id VÀ tblA.c = tblB.id VÀ tblA.d = tblD.id VÀ tblA.y = tblY.id
Al Po

4
Tôi mới chạy thử nghiệm này và SQLC_CALC_FOUND_lawS nhanh hơn nhiều so với hai truy vấn. Bây giờ bảng chính của tôi chỉ có 65 nghìn và hai lần tham gia vài trăm, nhưng truy vấn chính mất 0,18 giây có hoặc không có SQLC_CALC_FOUND_lawS nhưng khi tôi chạy truy vấn thứ hai với COUNT ( id) thì chỉ mất 0,25.
transilvlad

1
Ngoài các vấn đề về hiệu năng có thể xảy ra, hãy xem xét điều đó FOUND_ROWS()đã bị phản đối trong MySQL 8.0.17. Xem thêm câu trả lời của @ madhur-bhaiya.
arueckauer

19

Khi chọn cách tiếp cận "tốt nhất", một cân nhắc quan trọng hơn tốc độ có thể là tính duy trì và tính chính xác của mã của bạn. Nếu vậy, SQL_CALC_FOUND_lawS thích hợp hơn vì bạn chỉ cần duy trì một truy vấn duy nhất. Sử dụng một truy vấn duy nhất loại trừ hoàn toàn khả năng có sự khác biệt tinh tế giữa truy vấn chính và truy vấn đếm, điều này có thể dẫn đến một COUNT không chính xác.


11
Điều này phụ thuộc vào thiết lập của bạn. Nếu bạn đang sử dụng một số loại ORM hoặc trình tạo truy vấn, thì rất dễ sử dụng cùng một tiêu chí cho cả hai truy vấn, trao đổi các trường được chọn để đếm và giảm giới hạn. Bạn không bao giờ nên viết ra các tiêu chí hai lần.
mpen

Tôi sẽ chỉ ra rằng tôi muốn duy trì mã bằng hai truy vấn SQL đơn giản, dễ hiểu, đơn giản hơn một truy vấn sử dụng tính năng MySQL độc quyền - điều đáng chú ý là không được dùng trong các phiên bản MySQL mới hơn.
thomasrutter

15

MySQL đã bắt đầu phản đối SQL_CALC_FOUND_ROWSchức năng với phiên bản 8.0.17 trở đi.

Vì vậy, luôn luôn ưu tiên xem xét thực hiện truy vấn của bạn LIMITvà sau đó truy vấn thứ hai có COUNT(*)và không có LIMITđể xác định xem có hàng bổ sung hay không.

Từ tài liệu :

Công cụ sửa đổi truy vấn SQL_CALC_FOUND_lawS và hàm FOUND_lawS () đi kèm không được chấp nhận kể từ MySQL 8.0.17 và sẽ bị xóa trong phiên bản MySQL trong tương lai.

COUNT (*) có thể tối ưu hóa nhất định. SQL_CALC_FOUND_lawS khiến một số tối ưu hóa bị vô hiệu hóa.

Sử dụng các truy vấn này thay thế:

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

Ngoài ra, SQL_CALC_FOUND_ROWSđã được quan sát thấy có nhiều vấn đề nói chung, như được giải thích trong MySQL WL # 12615 :

SQL_CALC_FOUND_lawS có một số vấn đề. Trước hết, nó chậm. Thông thường, sẽ rẻ hơn khi chạy truy vấn với LIMIT và sau đó một CHỌN COUNT ( ) riêng cho cùng một truy vấn, vì COUNT ( ) có thể sử dụng các tối ưu hóa không thể thực hiện khi tìm kiếm toàn bộ tập kết quả (ví dụ: fileort có thể được bỏ qua cho COUNT (*), trong khi với CALC_FOUND_lawS, chúng tôi phải vô hiệu hóa một số tối ưu hóa tập tin để đảm bảo kết quả đúng)

Quan trọng hơn, nó có ngữ nghĩa rất không rõ ràng trong một số tình huống. Cụ thể, khi một truy vấn có nhiều khối truy vấn (ví dụ như với UNION), đơn giản là không có cách nào để tính số lượng các hàng có thể có được cùng lúc với việc tạo ra một truy vấn hợp lệ. Khi người thực thi lặp đang tiến tới các loại truy vấn này, thực sự rất khó để cố gắng giữ lại cùng một ngữ nghĩa. Hơn nữa, nếu có nhiều GIỚI HẠN trong truy vấn (ví dụ: đối với các bảng dẫn xuất), thì không nhất thiết phải rõ ràng trong số đó SQL_CALC_FOUND_lawS nên tham khảo. Do đó, các truy vấn không cần thiết như vậy sẽ nhất thiết phải có các ngữ nghĩa khác nhau trong trình thực thi lặp so với những gì chúng có trước đây.

Cuối cùng, hầu hết các trường hợp sử dụng trong đó SQL_CALC_FOUND_lawS có vẻ hữu ích nên được giải quyết đơn giản bằng các cơ chế khác ngoài LIMIT / OFFSET. Ví dụ, một danh bạ điện thoại nên được phân trang bằng chữ cái (cả về UX và về mặt sử dụng chỉ mục), chứ không phải theo số hồ sơ. Các cuộc thảo luận ngày càng vô hạn - cuộn theo thứ tự ngày (một lần nữa cho phép sử dụng chỉ mục), không phải bằng cách phân trang theo số bài. Và như thế.


Làm thế nào để thực hiện hai lựa chọn này là hoạt động nguyên tử? Điều gì xảy ra nếu ai đó chèn một hàng trước truy vấn SELECT COUNT (*)? Cảm ơn.
Dom

@Dom nếu bạn có MySQL8 +, bạn có thể chạy cả truy vấn trong một truy vấn bằng các hàm Window; nhưng đây sẽ không phải là một giải pháp tối ưu vì các chỉ mục sẽ không được sử dụng đúng cách. Một tùy chọn khác là bao quanh hai truy vấn này với LOCK TABLES <tablename>UNLOCK TABLES. Tùy chọn thứ ba và (IMHO tốt nhất) là suy nghĩ lại về phân trang. Vui lòng đọc: mariadb.com/kb/en/l Library / pancination
Madhur Bhaiya

14

Theo bài viết sau: https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Nếu bạn có INDEX trên mệnh đề where của bạn (nếu id được lập chỉ mục trong trường hợp của bạn), thì tốt hơn là không sử dụng SQL_CALC_FOUND_lawS và sử dụng 2 truy vấn thay thế, nhưng nếu bạn không có chỉ mục về mệnh đề where của bạn (id trong trường hợp của bạn) sau đó sử dụng SQL_CALC_FOUND_lawS hiệu quả hơn.


8

IMHO, lý do tại sao 2 truy vấn

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

nhanh hơn so với sử dụng SQL_CALC_FOUND_lawS

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

phải được xem như một trường hợp cụ thể

Nó trong thực tế phụ thuộc vào độ chọn lọc của mệnh đề WHERE so với độ chọn lọc của mệnh đề ngầm tương đương với ĐẶT HÀNG + GIỚI HẠN.

Như Arvids đã nói trong nhận xét ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394 ), thực tế là EXPLAIN có sử dụng hay không, một bảng thời gian, nên là một cơ sở tốt để biết liệu SCFR sẽ nhanh hơn hay không.

Nhưng, như tôi đã thêm ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ), kết quả thực sự, thực sự phụ thuộc vào trường hợp. Đối với một người phân trang cụ thể, bạn có thể đi đến kết luận rằng, trong 3 trang đầu tiên, hãy sử dụng 2 truy vấn; đối với các trang tiếp theo, hãy sử dụng SCFR hấp!


6

Loại bỏ một số SQL không cần thiết và sau đó COUNT(*)sẽ nhanh hơn SQL_CALC_FOUND_ROWS. Thí dụ:

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

Sau đó đếm mà không có phần không cần thiết:

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

3

Có các tùy chọn khác để bạn điểm chuẩn:

1.) Một chức năng cửa sổ sẽ trả về kích thước thực trực tiếp (được kiểm tra trong MariaDB):

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

2.) Suy nghĩ ra khỏi hộp, hầu hết thời gian người dùng không cần biết kích thước CHÍNH XÁC của bảng, một xấp xỉ thường là đủ tốt.

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.