Разбивка на страницы MySQL без двойного запроса?


мне было интересно, есть ли способ получить количество результатов из запроса MySQL, и в то же время ограничить результаты.

как работает разбиение на страницы (как я понимаю), сначала я делаю что-то вроде

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

после того, как я получаю num_rows(запрос), у меня есть количество результатов. Но тогда, чтобы фактически ограничить мои результаты, я должен сделать второй запрос, например:

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

мой вопрос: есть ли в любом случае, чтобы получить общее количество результатов, которые будет ли дано и ограничить результаты, возвращаемые в одном запросе? Или более эффективный способ сделать это. Спасибо!

8 96

8 ответов:

нет, вот сколько приложений, которые хотят разбить на страницы, должны это сделать. Это надежный и пуленепробиваемый, хотя он делает запрос дважды. Но вы можете кэшировать счет в течение нескольких секунд, и это очень поможет.

другой способ-использовать SQL_CALC_FOUND_ROWS предложение, а затем вызов SELECT FOUND_ROWS(). помимо того, что вы должны поставить FOUND_ROWS() позвоните после этого, есть проблема с этим: есть ошибка в MySQL вот это получится что влияет ORDER BY запросы делают его много медленнее на больших таблицах, чем наивный подход двух запросов.

я почти никогда не делаю два запроса.

просто верните на одну строку больше, чем нужно, отобразите только 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, и ваши результаты не находятся на первой странице, вы, скорее всего, перейдете на вторую страницу, а не девять.

другой способ избежать двойного запроса заключается в том, чтобы сначала извлечь все строки для текущей страницы с помощью предложения LIMIT, а затем выполнить второй запрос COUNT (*), если было получено максимальное количество строк.

во многих приложениях наиболее вероятным результатом будет то, что все результаты поместятся на одной странице, и необходимость делать разбиение на страницы является исключением, а не нормой. В этих случаях первый запрос не будет получать максимальное количество результатов.

для например, ответы на вопрос stackoverflow редко попадают на вторую страницу. Комментарии к ответу редко выходят за пределы 5 или около того, чтобы показать их все.

таким образом, в этих приложениях вы можете просто сначала сделать запрос с ограничением, а затем, пока этот предел не достигнут, Вы точно знаете, сколько строк есть без необходимости делать второй запрос COUNT (*), который должен охватывать большинство ситуаций.

в большинстве ситуаций это гораздо быстрее и менее ресурсоемким, чтобы сделать это в двух отдельных запросов, чем сделать это в одном, хотя это кажется нелогичным.

Если вы используете 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/

для получения дополнительной информации об оптимизации разбиения на страницы, проверьте этот пост и этот пост.

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

мой ответ может быть поздно, но вы можете пропустить второй запрос (с ограничением) и просто отфильтровать информацию через свой внутренний скрипт. В 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

вы можете повторно использовать большую часть запроса в подзапросе и установить его в идентификатор. Например, запрос фильма, который находит фильмы, содержащие букву "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 секунды на моем ноутбуке.

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