2009-05-04 12 views
92

Tôi đã tự hỏi nếu có một cách để có được số lượng kết quả từ một truy vấn MySQL, và đồng thời giới hạn kết quả.Phân trang MySQL mà không cần truy vấn kép?

Các công trình pagination cách (như tôi hiểu nó), đầu tiên tôi làm điều gì đó như

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

Sau khi tôi nhận được NUM_ROWS (query), tôi có số lượng kết quả. Nhưng sau đó để thực sự hạn chế kết quả của tôi, tôi phải làm một truy vấn thứ hai như:

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

Câu hỏi của tôi: Liệu có cách nào để cả hai lấy tổng số kết quả đó sẽ được đưa ra, và giới hạn kết quả trả về trong truy vấn đơn lẻ? Hoặc bất kỳ cách hiệu quả hơn để làm điều này. Cảm ơn!

+2

Mặc dù bạn sẽ không có COUNT (*) trong truy vấn2 – dlofrodloh

Trả lời

54

Không, đó là số lượng ứng dụng muốn phân trang phải thực hiện. Đó là đáng tin cậy và chống đạn, mặc dù nó làm cho các truy vấn hai lần. Nhưng bạn có thể lưu trữ bộ nhớ trong vài giây và điều đó sẽ giúp ích rất nhiều.

Cách khác là sử dụng mệnh đề SQL_CALC_FOUND_ROWS và sau đó gọi SELECT FOUND_ROWS(). ngoài thực tế, bạn phải thực hiện cuộc gọi FOUND_ROWS() sau đó, có vấn đề với điều này: Có a bug in MySQL rằng dấu tick này ảnh hưởng đến các truy vấn đơn giản hơn nhiều so với truy vấn ngây thơ của hai truy vấn.

+1

Tuyệt vời, cảm ơn sự giúp đỡ của bạn! – ash

+2

Nó không phải là hoàn toàn bằng chứng điều kiện chủng tộc, tuy nhiên, trừ khi bạn thực hiện hai truy vấn trong một giao dịch. Tuy nhiên, đây không phải là vấn đề. – NickZoic

+0

Bởi "đáng tin cậy" tôi có nghĩa là bản thân SQL luôn luôn sẽ trả về kết quả bạn muốn, và bằng "bullet-proof" tôi có nghĩa là không có lỗi MySQL cản trở những gì bạn có thể sử dụng SQL. Không giống như sử dụng SQL_CALC_FOUND_ROWS với ORDER BY và LIMIT, theo lỗi tôi đã đề cập. – staticsan

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

Truy vấn này chỉ trả về tổng số bản ghi trong bảng; không phải số lượng hồ sơ phù hợp với điều kiện. –

+1

Tổng số bản ghi là những gì cần thiết cho phân trang (@Lawrence). – imme

14

Trong hầu hết các trường hợp, nhanh hơn và ít tài nguyên hơn để thực hiện điều đó trong hai truy vấn riêng biệt hơn là thực hiện trong một truy vấn, mặc dù điều đó có vẻ phản trực giác.

Nếu bạn sử dụng SQL_CALC_FOUND_ROWS, thì đối với các bảng lớn, truy vấn của bạn chậm hơn nhiều, chậm hơn đáng kể so với thực hiện hai truy vấn. Lý do cho điều này là SQL_CALC_FOUND_ROWS làm cho mệnh đề LIMIT được áp dụng sau khi tìm nạp các hàng thay vì trước đó, vì vậy nó lấy toàn bộ hàng cho tất cả các kết quả có thể trước khi áp dụng các giới hạn. Điều này không thể được một chỉ số thỏa mãn vì nó thực sự tìm nạp dữ liệu.

Nếu bạn sử dụng hai truy vấn, đầu tiên chỉ tìm nạp COUNT (*) và không thực sự tìm nạp và dữ liệu thực, điều này có thể được đáp ứng nhanh hơn nhiều vì nó thường có thể sử dụng chỉ mục và không phải tìm nạp dữ liệu hàng thực tế cho mỗi hàng mà nó nhìn. Sau đó, truy vấn thứ hai chỉ cần nhìn vào hàng $ offset đầu tiên + $ và sau đó trả về.

bài này từ blog hiệu suất MySQL giải thích điều này nữa:

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

Để biết thêm thông tin về tối ưu hóa pagination, kiểm tra this postthis post.

64

Tôi hầu như không bao giờ thực hiện hai truy vấn.

Chỉ cần trả lại một hàng nhiều hơn mức cần thiết, chỉ hiển thị 10 trên trang và nếu có nhiều hơn được hiển thị, hãy hiển thị nút "Tiếp theo".

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. 

truy vấn của bạn nên quay trở lại trong một thứ tự đầu tiên phù hợp nhất.Rất có thể, hầu hết mọi người sẽ không quan tâm đến việc chuyển đến trang 236 trên 412.

Khi bạn thực hiện tìm kiếm trên google và kết quả của bạn không ở trên trang đầu tiên, bạn có thể truy cập trang hai, không chín.

+0

Điều này đúng, tôi sẽ ghi nhớ điều đó. – ash

+28

Thực ra, nếu tôi không tìm thấy nó trên trang đầu tiên của truy vấn Google, thường thì tôi chuyển sang trang chín. – Phil

+3

@Phil Tôi đã nghe điều này trước đây nhưng tại sao lại như vậy? – TK123

22

Cách tiếp cận khác để tránh truy vấn kép là tìm nạp tất cả các hàng cho trang hiện tại sử dụng mệnh đề LIMIT trước, sau đó chỉ thực hiện truy vấn COUNT (*) thứ hai nếu số lượng hàng tối đa được truy lục.

Trong nhiều ứng dụng, kết quả có khả năng nhất là tất cả các kết quả phù hợp trên một trang và phải phân trang là ngoại lệ thay vì chỉ tiêu. Trong những trường hợp này, truy vấn đầu tiên sẽ không truy xuất số lượng kết quả tối đa.

Ví dụ: câu trả lời trên câu hỏi ngăn xếp lưu trữ hiếm khi tràn vào trang thứ hai. Nhận xét về câu trả lời hiếm khi vượt quá giới hạn 5 hoặc hơn để yêu cầu hiển thị tất cả. Vì vậy, trong các ứng dụng này, bạn có thể chỉ cần thực hiện truy vấn với LIMIT trước, và sau đó miễn là không đạt đến giới hạn đó, bạn biết chính xác có bao nhiêu hàng mà không cần thực hiện COUNT thứ hai (*). truy vấn - bao gồm phần lớn các tình huống.

+1

Điểm tuyệt vời! –

+1

@thomasrutter Tôi đã có cách tiếp cận tương tự, tuy nhiên phát hiện ra một lỗ hổng với nó ngày hôm nay. Trang cuối cùng của kết quả sẽ không có dữ liệu phân trang. tức là, giả sử mỗi trang phải có 25 kết quả, trang cuối cùng sẽ không có nhiều kết quả, giả sử nó có 7 ... có nghĩa là số (*) sẽ không bao giờ được chạy và do đó không có số trang nào được hiển thị cho người dùng. – duellsy

+1

Không - nếu bạn nói, 200 kết quả, bạn truy vấn 25 tiếp theo và bạn chỉ nhận được 7 trở lại, cho bạn biết rằng tổng số kết quả là 207 và do đó bạn không cần phải thực hiện truy vấn khác với COUNT (*) bởi vì bạn đã biết những gì nó sẽ nói. Bạn có tất cả thông tin bạn cần để hiển thị phân trang. Nếu bạn đang gặp sự cố về phân trang không hiển thị cho người dùng thì bạn có lỗi ở một nơi khác. – thomasrutter

2

Câu trả lời của tôi có thể bị trễ, nhưng bạn có thể bỏ qua truy vấn thứ hai (với giới hạn) và chỉ lọc thông tin qua tập lệnh cuối của bạn. Trong PHP ví dụ, bạn có thể làm một cái gì đó như:

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

Nhưng tất nhiên, khi bạn có hàng ngàn hồ sơ để xem xét, nó trở nên kém hiệu quả rất nhanh. Số lượng được tính toán trước có thể là một ý tưởng hay để xem xét.

Dưới đây là một đọc tốt về đề tài này: http://www.percona.com/ppc2009/PPC2009_mysql_pagination.pdf

+1

cảm ơn bạn đã chia sẻ liên kết này! – Vikram

+0

Có, liên kết này chính xác có thông tin giá trị. –

+0

Liên kết đã chết, tôi đoán đây là địa chỉ chính xác: http://www.percona.com/files/presentations/ppc2009/PPC2009_mysql_pagination.pdf. Sẽ không chỉnh sửa bởi vì không chắc chắn nếu nó là. – hectorg87

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

Điều này không trả lời câu hỏi và lệnh rand là một ý tưởng tồi. –

0

Bạn có thể tái sử dụng hầu hết các truy vấn trong một subquery và đặt nó là một định danh. Ví dụ một truy vấn phim tìm thấy các phim chứa thứ tự của chữ cái theo thời gian chạy sẽ giống như thế này trên trang của tôi.

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; 

Do lưu ý rằng tôi không phải là một chuyên gia về cơ sở dữ liệu, và tôi hy vọng ai đó sẽ có thể tối ưu hóa mà tốt hơn một chút. Khi nó đứng chạy nó trực tiếp từ giao diện dòng lệnh SQL cả hai đều mất ~ 0,02 giây trên máy tính xách tay của tôi.