이중 쿼리없이 MySQL 페이지 매김?


115

MySQL 쿼리에서 결과 수를 가져 오는 동시에 결과를 제한하는 방법이 있는지 궁금합니다.

페이지 매김이 작동하는 방식 (내가 이해하는대로), 먼저 다음과 같은 작업을 수행합니다.

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

num_rows (query)를 얻은 후 결과 수를 얻었습니다. 하지만 실제로 결과를 제한하려면 다음과 같은 두 번째 쿼리를 수행해야합니다.

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

내 질문 : 어쨌든 주어진 총 결과 수를 검색하고 단일 쿼리에서 반환되는 결과를 제한하는 방법이 있습니까? 또는이를 수행하는 더 효율적인 방법. 감사!


8
query2에 COUNT (*)가 없을지라도
dlofrodloh

답변:


66

아니요, 페이지 매기기를 원하는 응용 프로그램의 수입니다. 쿼리를 두 번 수행하지만 신뢰할 수 있고 방탄입니다. 그러나 몇 초 동안 카운트를 캐시하면 많은 도움이됩니다.

다른 방법은 SQL_CALC_FOUND_ROWS절 을 사용한 다음을 호출하는 것 SELECT FOUND_ROWS()입니다. FOUND_ROWS()나중에 호출 을해야한다는 사실과는 별개 로 이것에 문제가 있습니다 . MySQL 에는 이것이 ORDER BY두 쿼리의 순진한 접근 방식보다 큰 테이블에서 훨씬 느리게 만드는 쿼리 에 영향을 미치는 간지럼 이 있는 버그 가 있습니다 .


2
그러나 트랜잭션 내에서 두 가지 쿼리를 수행하지 않는 한 경쟁 조건 증명이 아닙니다. 하지만 이것은 일반적으로 문제가되지 않습니다.
NickZoic

"신뢰성"이란 SQL 자체가 항상 원하는 결과를 반환한다는 것을 의미하고 "방탄"이란 SQL을 사용할 수있는 것을 방해하는 MySQL 버그가 없음을 의미합니다. 내가 언급 한 버그에 따르면 ORDER BY 및 LIMIT와 함께 SQL_CALC_FOUND_ROWS를 사용하는 것과 달리.
staticsan

5
복잡한 쿼리에서 SQL_CALC_FOUND_ROWS를 사용하여 동일한 쿼리에서 개수를 가져 오는 것은 두 개의 개별 쿼리를 수행하는 것보다 거의 항상 느립니다. 이는 제한에 관계없이 모든 행을 완전히 검색해야하고 LIMIT 절에 지정된 행만 반환되어야하기 때문입니다. 링크가있는 내 응답도 참조하십시오.
thomasrutter 2011 년

이것이 필요한 이유에 따라 전체 결과를 검색하지 않는 것을 생각할 수도 있습니다. 자동 페이징 방법을 구현하는 것이 더 일반적인 관행이되고 있습니다. 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.

검색어는 가장 관련성이 높은 순서로 먼저 반환되어야합니다. 대부분의 사람들은 412 개 중 236 페이지로가는 것에 관심이 없을 것입니다.

Google 검색을했는데 결과가 첫 페이지에 없으면 9 페이지가 아닌 2 페이지로 이동합니다.


42
사실 Google 검색어의 첫 페이지에서 찾을 수없는 경우 보통 9 페이지로 건너 뜁니다.
Phil

3
@Phil 나는 전에 이것을 들었지만 왜 그렇게합니까?
TK123

5
조금 늦었지만 여기에 내 추론이 있습니다. 일부 검색은 검색 엔진 최적화 링크 팜에 의해 지배됩니다. 따라서 처음 몇 페이지는 위치 번호 1을 놓고 싸우는 서로 다른 팜입니다. 유용한 결과는 여전히 쿼리와 관련이있을 가능성이 높지만 맨 위가 아닙니다.
Phil

4
COUNT집계 함수입니다. 하나의 쿼리에서 개수 모든 결과를 어떻게 반환 합니까? 위의 쿼리 LIMIT는 설정에 관계없이 행 1 개만 반환 합니다. 당신이 추가하면 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 2012-08-22

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 2014

아, 그래, 바로 추가 where내부 쿼리에 절을 당신은 (페이지가로 선택되어있는 페이지 된 결과 우측 "총"함께 얻을 limit
Erenor 파스

하위 쿼리 수 (*)는 where 절 그렇지 않으면 결과의 정확한 수를 반환하지 않습니다 같은 요구
AKrush95

1

2020 년에 해답을 찾고있는 사람을 위해. MySQL 문서에 따라 :

"SQL_CALC_FOUND_ROWS 쿼리 수정 자와 함께 제공되는 FOUND_ROWS () 함수 는 MySQL 8.0.17에서 더 이상 사용되지 않으며 향후 MySQL 버전에서 제거 될 예정입니다. 대신 LIMIT로 쿼리를 실행 한 다음 COUNT (*)로 두 번째 쿼리를 실행하는 것이 좋습니다. LIMIT없이 추가 행이 있는지 확인합니다. "

그게 해결 될 것 같아요.

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


0

하위 쿼리에서 대부분의 쿼리를 재사용하고이를 식별자로 설정할 수 있습니다. 예를 들어, 런타임에 따라 's'문자가 포함 된 영화를 찾는 영화 쿼리는 내 사이트에서 다음과 같습니다.

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
이것은 질문에 대한 대답이 아니며 rand의 주문은 정말 나쁜 생각입니다.
Dan Walmsley 2016
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.