Usando will_paginate sin: total_entries para mejorar una consulta larga

Tengo una implementación actual de will_paginate que usa el método paginate_by_sql para construir la colección a ser paginada. Tenemos una consulta personalizada para total_entries que es muy complicada y pone una gran carga en nuestra base de datos. Por lo tanto, nos gustaría recortar el total de entradas de la paginación.

En otras palabras, en lugar de la visualización de paginación típica de ‘anterior 1 [2] 3 4 5 siguiente’, simplemente nos gustaría un botón ‘siguiente – anterior’ solamente. Pero necesitamos saber algunas cosas.

  1. ¿Mostramos el enlace anterior? Esto solo ocurrirá, por supuesto, si los registros existentes antes de los que se muestran en la selección actual
  2. ¿Mostramos el siguiente enlace? Esto no se mostrará si se muestra el último registro de la colección

De los docs

Se generará automáticamente una consulta para contar las filas si no proporciona: total_entries. Si experimenta problemas con este SQL generado, es posible que desee realizar el conteo manualmente en su aplicación.

Entonces, en última instancia, la situación ideal es la siguiente.

  • Elimine el recuento total_entries porque está causando demasiada carga en la base de datos
  • Muestre 50 registros a la vez con la semi-paginación usando solo los botones de siguiente / anterior para navegar y no necesita mostrar todos los números de página disponibles
  • Solo muestra el botón siguiente y el botón anterior en consecuencia

¿Alguien ha trabajado con un problema similar o ha pensado en una resolución?

Hay muchas ocasiones en las que will_paginate realiza un trabajo realmente horrible al calcular el número de entradas, especialmente si hay uniones involucradas que confunden el generador de SQL de conteo.

Si todo lo que necesita es un método simple anterior / siguiente, entonces todo lo que debe hacer es intentar recuperar N + 1 entradas de la base de datos, y si solo obtiene N o menos de lo que está en la última página.

Por ejemplo:

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 

Puede encapsular esto fácilmente en algún módulo que se puede incluir en los diversos modelos que lo requieren, o hacer una extensión de controlador.

Descubrí que esto funciona significativamente mejor que el método predeterminado will_paginate.

El único problema de rendimiento es una limitación de MySQL que puede ser un problema dependiendo del tamaño de sus tablas.

Por la razón que sea, la cantidad de tiempo que lleva realizar una consulta con un LÍMITE pequeño en MySQL es proporcional a la COMPENSACIÓN. En efecto, el motor de la base de datos lee todas las filas que conducen al valor de compensación particular, luego devuelve las siguientes filas de números LÍMITES, sin saltar hacia adelante como cabría esperar.

Para grandes conjuntos de datos, donde tiene valores de COMPENSACIÓN en el rango de más de 100.000, puede encontrar una degradación significativa del rendimiento. Cómo se manifestará esto es que cargar la página 1 es muy rápido, la página 1000 es algo lenta, pero la página 2000 es extremadamente lenta.