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

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

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

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

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

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

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

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(*) и второй с ограничением. Причина этого заключается в том, что 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