¿Cuál es el más rápido? SELECT SQL_CALC_FOUND_ROWS FROM `table`, o SELECT COUNT (*)

176

Cuando limita el número de filas que devuelve una consulta SQL, generalmente utilizada en la paginación, hay dos métodos para determinar el número total de registros:

Método 1

Incluya la SQL_CALC_FOUND_ROWSopción en el original SELECTy luego obtenga el número total de filas ejecutando SELECT FOUND_ROWS():

SELECT SQL_CALC_FOUND_ROWS * FROM table WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();  

Método 2

Ejecute la consulta normalmente y luego obtenga el número total de filas ejecutando SELECT COUNT(*)

SELECT * FROM table WHERE id > 100 LIMIT 10;
SELECT COUNT(*) FROM table WHERE id > 100;  

¿Qué método es el mejor / más rápido?

Jrgns
fuente

Respuestas:

120

Depende. Consulte la publicación de MySQL Performance Blog sobre este tema: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Solo un resumen rápido: Peter dice que depende de sus índices y otros factores. Muchos de los comentarios a la publicación parecen decir que SQL_CALC_FOUND_ROWS es casi siempre más lento, a veces hasta 10 veces más lento, que ejecutar dos consultas.

Nathan
fuente
27
Puedo confirmar esto: acabo de actualizar una consulta con 4 combinaciones en una base de datos de 168,000 filas. Seleccionar solo las primeras 100 filas con un SQL_CALC_FOUND_ROWStomó más de 20 segundos; el uso de una COUNT(*)consulta separada tomó menos de 5 segundos (para ambas consultas de conteo + resultados).
Sam Dufel
9
Hallazgos muy interesantes. Dado que la documentación de MySQL sugiere explícitamente que SQL_CALC_FOUND_ROWSserá más rápido, ¡me pregunto en qué situaciones (si las hay) en realidad es más rápido!
svidgen
12
viejo tema, pero para aquellos que aún son interesantes! Acabo de terminar mi comprobación en INNODB de 10 comprobaciones. Puedo decir que es 26 (2 consulta) contra 9.2 (1 consulta) SELECCIONAR SQL_CALC_FOUND_ROWS tblA. *, TblB.id AS 'b_id', tblB.city AS 'b_city', tblC.id AS 'C_ID', 'c_type' tblC.type AS, 'D_ID' tblD.id AS, 'd_extype' tblD.extype AS, 'y_id' tblY.id AS, tblY.ydt AS y_ydt DE tblA, tblB, tblC, tblD, tblY DONDE tblA.b = tblC.id AND tblA.c = tblB.id AND tblA.d = tblD.id AND tblA.y = tblY.id
Al Po
44
Acabo de ejecutar este experimento y SQLC_CALC_FOUND_ROWS fue mucho más rápido que dos consultas. Ahora mi tabla principal tiene solo 65k y dos combinaciones de unos pocos cientos, pero la consulta principal tarda 0.18 segundos con o sin SQLC_CALC_FOUND_ROWS, pero cuando ejecuté una segunda consulta con COUNT ( id) tomó 0.25 solo.
transilvlad
1
Además de posibles problemas de rendimiento, considere que FOUND_ROWS()ha quedado en desuso en MySQL 8.0.17. Ver también la respuesta de @ madhur-bhaiya.
arueckauer
19

Al elegir el "mejor" enfoque, una consideración más importante que la velocidad podría ser el mantenimiento y la corrección de su código. Si es así, es preferible SQL_CALC_FOUND_ROWS porque solo necesita mantener una sola consulta. El uso de una sola consulta excluye por completo la posibilidad de una sutil diferencia entre las consultas principales y de conteo, lo que puede conducir a un recuento inexacto.

Jeff Clemens
fuente
11
Esto depende de su configuración. Si está utilizando algún tipo de ORM o generador de consultas, es muy fácil usar el mismo criterio para ambas consultas, intercambiar los campos de selección por un recuento y eliminar el límite. Nunca debe escribir los criterios dos veces.
mpen
Señalaría que prefiero mantener el código usando dos consultas SQL simples bastante estándar y fáciles de entender que una que usa una característica propietaria de MySQL, que vale la pena señalar que está en desuso en las versiones más recientes de MySQL.
thomasrutter
15

MySQL ha comenzado a despreciar SQL_CALC_FOUND_ROWS funcionalidad con la versión 8.0.17 en adelante.

Por lo tanto, siempre es preferible considerar ejecutar su consulta con LIMIT, y luego una segunda consulta con COUNT(*)y sinLIMIT determinar si hay filas adicionales.

De documentos :

El modificador de consulta SQL_CALC_FOUND_ROWS y la función FOUND_ROWS () que lo acompaña están en desuso a partir de MySQL 8.0.17 y se eliminarán en una futura versión de MySQL.

COUNT (*) está sujeto a ciertas optimizaciones. SQL_CALC_FOUND_ROWS hace que algunas optimizaciones se deshabiliten.

Utilice estas consultas en su lugar:

SELECT * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT COUNT(*) WHERE id > 100;

Además, SQL_CALC_FOUND_ROWSse ha observado que tiene más problemas en general, como se explica en MySQL WL # 12615 :

SQL_CALC_FOUND_ROWS tiene varios problemas. Primero que nada, es lento. Con frecuencia, sería más barato ejecutar la consulta con LIMIT y luego un SELECT COUNT ( ) para la misma consulta, ya que COUNT ( ) puede hacer uso de optimizaciones que no se pueden hacer al buscar el conjunto de resultados completo (por ejemplo, fileort se puede omitir COUNT (*), mientras que con CALC_FOUND_ROWS, debemos deshabilitar algunas optimizaciones de clasificación de archivos para garantizar el resultado correcto)

Más importante aún, tiene una semántica muy poco clara en una serie de situaciones. En particular, cuando una consulta tiene múltiples bloques de consulta (por ejemplo, con UNION), simplemente no hay forma de calcular el número de filas "posibles" al mismo tiempo que se produce una consulta válida. A medida que el ejecutor iterador avanza hacia este tipo de consultas, es realmente difícil tratar de mantener la misma semántica. Además, si hay múltiples LÍMITES en la consulta (por ejemplo, para tablas derivadas), no está necesariamente claro a cuál de ellos debería referirse SQL_CALC_FOUND_ROWS. Por lo tanto, tales consultas no triviales necesariamente obtendrán una semántica diferente en el ejecutor del iterador en comparación con lo que tenían antes.

Finalmente, la mayoría de los casos de uso en los que SQL_CALC_FOUND_ROWS parecería útil deberían resolverse simplemente mediante otros mecanismos que no sean LIMIT / OFFSET. Por ejemplo, una guía telefónica debe ser paginada por letra (tanto en términos de UX como en términos de uso del índice), no por número de registro. Las discusiones son cada vez más infinitas: ordenadas por fecha (permitiendo nuevamente el uso del índice), no paginadas por el número de publicación. Y así.

Madhur Bhaiya
fuente
¿Cómo realizar estas dos selecciones como operación atómica? ¿Qué sucede si alguien inserta una fila antes de la consulta SELECT COUNT (*)? Gracias.
Dom
@Dom si tiene MySQL8 +, puede ejecutar ambas consultas en una sola consulta utilizando las funciones de Windows; pero esta no será una solución óptima ya que los índices no se usarán correctamente. Otra opción es rodear estas dos consultas con LOCK TABLES <tablename>y UNLOCK TABLES. La tercera opción y (mejor en mi humilde opinión) es repensar la paginación. Lea: mariadb.com/kb/en/library/pagination-optimization
Madhur Bhaiya
8

En mi humilde opinión, la razón por la que 2 consultas

SELECT * FROM count_test WHERE b = 666 ORDER BY c LIMIT 5;
SELECT count(*) FROM count_test WHERE b = 666;

son más rápidos que usar SQL_CALC_FOUND_ROWS

SELECT SQL_CALC_FOUND_ROWS * FROM count_test WHERE b = 555 ORDER BY c LIMIT 5;

tiene que ser visto como un caso particular.

De hecho, depende de la selectividad de la cláusula WHERE en comparación con la selectividad del implícito equivalente al ORDEN + LÍMITE.

Como Arvids dijo en un comentario ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394 ), el hecho de que EXPLAIN usa o no, una tabla de temporay, debería ser una buena base para saber si SCFR será más rápido o no.

Pero, como agregué ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ), el resultado realmente depende del caso. Para un paginador en particular, puede llegar a la conclusión de que “para las 3 primeras páginas, use 2 consultas; para las siguientes páginas, use un SCFR ”!

Pierre-Olivier Vares
fuente
6

Eliminar algunos SQL innecesarios y luego COUNT(*)será más rápido que SQL_CALC_FOUND_ROWS. Ejemplo:

SELECT Person.Id, Person.Name, Job.Description, Card.Number
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
LEFT JOIN Card ON Card.Person_Id = Person.Id
WHERE Job.Name = 'WEB Developer'
ORDER BY Person.Name

Luego cuente sin parte innecesaria:

SELECT COUNT(*)
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
WHERE Job.Name = 'WEB Developer'
Jessé Catrinck
fuente
3

Hay otras opciones para que pueda comparar:

1.) Una función de ventana devolverá el tamaño real directamente (probado en MariaDB):

SELECT 
  `mytable`.*,
  COUNT(*) OVER() AS `total_count`
FROM `mytable`
ORDER BY `mycol`
LIMIT 10, 20

2.) Pensando fuera de la caja, la mayoría de las veces los usuarios no necesitan saber el tamaño EXACTO de la tabla, un aproximado suele ser lo suficientemente bueno.

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
Código4R7
fuente