Использование will_paginate без :total_entries для улучшения длинного запроса

У меня есть текущая реализация will_paginate, которая использует метод paginate_by_sql для создания коллекции, подлежащей разбивке на страницы. У нас есть собственный запрос для total_entries, который очень сложен и создает большую нагрузку на нашу БД. Поэтому мы хотели бы вообще исключить total_entries из нумерации страниц.

Другими словами, вместо типичного отображения разбиения на страницы «предыдущий 1 [2] 3 4 5 следующий» мы хотели бы просто использовать только кнопку «следующий - предыдущий». Но нам нужно знать несколько вещей.

  1. Отображаем ли мы предыдущую ссылку? Конечно, это произойдет только в том случае, если записи, существующие до тех, которые отображаются в текущем выборе
  2. Мы отображаем следующую ссылку? Это не будет отображаться, если отображается последняя запись в коллекции.

Из документов.

Запрос на подсчет строк будет сгенерирован автоматически, если вы не укажете :total_entries. Если у вас возникли проблемы с этим сгенерированным SQL, вы можете выполнить подсчет вручную в своем приложении.

Итак, в конечном счете, идеальная ситуация выглядит следующим образом.

  • Удалите счетчик total_entries, потому что он вызывает слишком большую нагрузку на базу данных.
  • Отображение 50 записей за раз с полуразбивкой на страницы с использованием только кнопок «следующая/предыдущая» для навигации без необходимости отображения всех доступных номеров страниц.
  • Отображать только следующую кнопку и предыдущую кнопку соответственно

Кто-нибудь работал с подобной проблемой или есть мысли по решению?


person mwilliams    schedule 14.09.2009    source источник
comment
Вот недавнее решение stackoverflow.com/questions/26696731/   -  person chris finne    schedule 06.11.2019


Ответы (1)


Есть много случаев, когда will_paginate делает действительно ужасную работу по подсчету количества записей, особенно если задействованы соединения, которые сбивают с толку генератор SQL count.

Если все, что вам нужно, это простой метод prev/next, то все, что вам нужно сделать, это попытаться получить N+1 записей из базы данных, и если вы получите только N или меньше, чем вы находитесь на последней странице.

Например:

per_page = 10
page = 2

@entries = Thing.with_some_scope.find(:all, :limit => per_page + 1, :offset => (page - 1) * per_page)

@next_page = @entries.slice!(per_page, 1)
@prev_page = page > 1

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

Я обнаружил, что это работает значительно лучше, чем метод will_paginate по умолчанию.

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

По какой-то причине количество времени, необходимое для выполнения запроса с небольшим LIMIT в MySQL, пропорционально OFFSET. По сути, механизм базы данных считывает все строки, ведущие к определенному значению смещения, а затем возвращает следующие LIMIT числовые строки, не пропуская вперед, как можно было бы ожидать.

Для больших наборов данных, где у вас есть значения OFFSET в диапазоне от 100 000 и более, вы можете обнаружить, что производительность значительно снижается. Как это будет проявляться, так это то, что страница 1 загружается очень быстро, страница 1000 несколько медленнее, но страница 2000 очень медленная.

person tadman    schedule 14.09.2009
comment
Спасибо за идею. Я собираюсь взломать и посмотреть, что я могу придумать. У меня есть несколько длинных пользовательских SQL-запросов, но я думаю, что смогу превратить их в решение и получить представление о том, какой будет производительность. Спасибо! - person mwilliams; 14.09.2009
comment
Спасибо за ответ. Красиво и упрощенно, и я смотрел далеко за пределы такого простого решения. Большая часть моей реализации уже на месте и кажется намного лучше. Хотя у меня проблемы с кнопками «следующий/предыдущий», но я скоро все исправлю. Еще раз спасибо! - person mwilliams; 14.09.2009