2009-05-04 2 views
92

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

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

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

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

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

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

+2

Хотя у вас не было бы COUNT (*) в запросе2 – dlofrodloh

ответ

54

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

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

+1

Отлично, спасибо за помощь! – ash

+2

Это не совсем доказательство условий гонки, однако, если вы не выполняете два запроса в транзакции. Однако это вообще не проблема. – NickZoic

+0

Под «надежным» я подразумевал, что сам SQL всегда будет возвращать нужный результат, и «пуленепробиваемым» я имел в виду, что ошибок MySQL не мешает тому, что SQL вы можете использовать. В отличие от использования SQL_CALC_FOUND_ROWS с ORDER BY и LIMIT, в соответствии с указанной мной ошибкой. – staticsan

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

Этот запрос просто возвращает общее количество записей в таблице; а не количество записей, соответствующих условию. –

+1

Общее количество записей - это то, что необходимо для разбивки на страницы (@Lawrence). – imme

14

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

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

Для получения дополнительной информации об оптимизации пагинации, проверьте this post и this post.

64

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

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

+0

Это правда, я буду помнить об этом. – ash

+28

На самом деле, если я не нашел его на первой странице запроса Google, как правило, я перехожу на страницу девять. – Phil

+3

@Phil Я слышал это раньше, но зачем это? – TK123

22

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

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

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

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

+1

Отличная точка! –

+1

@thomasrutter У меня был такой же подход, однако он обнаружил сегодня недостаток. На последней странице результатов не будут отображаться данные разбивки на страницы. т.е. на каждой странице должно быть 25 результатов, на последней странице, вероятно, не будет такого количества, скажем, у нее есть 7 ... это означает, что счетчик (*) никогда не будет запущен, и поэтому никакая разбивка на страницы не будет отображаться пользователь. – duellsy

+1

Нет - если вы скажете, 200 результатов, вы запрашиваете следующие 25, и вы получаете только 7 назад, что говорит вам, что общее количество результатов - 207, и поэтому вам не нужно делать другой запрос с помощью COUNT (*), потому что вы уже знаете, что он собирается сказать. У вас есть вся необходимая информация, чтобы показать разбивку на страницы. Если у вас возникла проблема с разбиением на страницы, которое не отображается пользователю, тогда у вас есть ошибка где-то еще. – thomasrutter

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

+1

благодарит за эту ссылку! – Vikram

+0

Да, эта ссылка точно содержит ценную информацию. –

+0

Ссылка мертва, я думаю, это правильно: http://www.percona.com/files/presentations/ppc2009/PPC2009_mysql_pagination.pdf. Не будет редактировать, потому что не уверен, что это так. – hectorg87

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

Это не отвечает на вопрос, а заказ rand - очень плохая идея. –

0

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

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 секунд на моем ноутбуке.

 Смежные вопросы

  • Нет связанных вопросов^_^