Qual é o mais rápido? SELECT SQL_CALC_FOUND_ROWS FROM `table` ou SELECT COUNT (*)

176

Quando você limita o número de linhas a serem retornadas por uma consulta SQL, geralmente usada em paginação, existem dois métodos para determinar o número total de registros:

Método 1

Inclua a SQL_CALC_FOUND_ROWSopção no original SELECTe obtenha o número total de linhas executando SELECT FOUND_ROWS():

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

Método 2

Execute a consulta normalmente e obtenha o número total de linhas executando SELECT COUNT(*)

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

Qual método é o melhor / mais rápido?

Jrgns
fonte

Respostas:

120

Depende. Veja a publicação do MySQL Performance Blog sobre este assunto: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Apenas um resumo rápido: Peter diz que depende de seus índices e de outros fatores. Muitos dos comentários à postagem parecem dizer que SQL_CALC_FOUND_ROWS é quase sempre mais lento - às vezes até 10 vezes mais lento - do que executar duas consultas.

nathan
fonte
27
Posso confirmar isso: acabei de atualizar uma consulta com 4 associações em um banco de dados de 168.000 linhas. Selecionar apenas as primeiras 100 linhas com uma SQL_CALC_FOUND_ROWSdemora de 20 segundos; o uso de uma COUNT(*)consulta separada levou menos de 5 segundos (para consultas de contagem + resultados).
Sam Dufel
9
Achados muito interessantes. Como a documentação do MySQL sugere explicitamente que SQL_CALC_FOUND_ROWSserá mais rápido, pergunto-me em que situações (se houver) ela é realmente mais rápida!
svidgen
12
tópico antigo, mas para quem ainda é interessante! Acabei de terminar minha verificação no INNODB a partir de 10 verificações. Posso dizer que são 26 (2query) contra 9,2 (1 consulta) SELECT 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, AS tblY.ydt y_ydt DE tblA, tblB, tblC, tblD, tblY ONDE tblA.b = tblC.id E tblA.c = tblB.id E tblA.d = tblD.id E tblA.y = tblY.id
Al Po
4
Acabei de executar este experimento e o SQLC_CALC_FOUND_ROWS foi muito mais rápido do que duas consultas. Agora, minha tabela principal tem apenas 65k e duas junções de algumas centenas, mas a consulta principal leva 0,18 segundos com ou sem SQLC_CALC_FOUND_ROWS, mas quando executei uma segunda consulta com COUNT ( id), ela precisou de 0,25 sozinha.
transilvlad
1
Além de possíveis problemas de desempenho, considere que FOUND_ROWS()isso foi preterido no MySQL 8.0.17. Veja também a resposta de @ madhur-bhaiya.
arueckauer
19

Ao escolher a "melhor" abordagem, uma consideração mais importante que a velocidade pode ser a manutenção e a correção do seu código. Nesse caso, é preferível SQL_CALC_FOUND_ROWS porque você precisa manter apenas uma única consulta. O uso de uma única consulta impede completamente a possibilidade de uma diferença sutil entre as consultas principal e a contagem, o que pode levar a uma COUNT imprecisa.

Jeff Clemens
fonte
11
Isso depende da sua configuração. Se você estiver usando algum tipo de ORM ou construtor de consultas, é muito fácil usar o mesmo critério where para as duas consultas, trocar os campos selecionados por uma contagem e diminuir o limite. Você nunca deve escrever os critérios duas vezes.
MPEN
Gostaria de salientar que prefiro manter o código usando duas consultas SQL simples e razoavelmente simples e fáceis de entender do que uma que usa um recurso proprietário do MySQL - que vale a pena notar que está obsoleto nas versões mais recentes do MySQL.
thomasrutter
15

O MySQL começou a descontinuar a SQL_CALC_FOUND_ROWSfuncionalidade com a versão 8.0.17 em diante.

Portanto, é sempre preferível considerar a execução de sua consulta com LIMITe, em seguida, uma segunda consulta com COUNT(*)e sem LIMITpara determinar se há linhas adicionais.

Dos documentos :

O modificador de consulta SQL_CALC_FOUND_ROWS e a função FOUND_ROWS () acompanhante estão obsoletos no MySQL 8.0.17 e serão removidos em uma versão futura do MySQL.

COUNT (*) está sujeito a determinadas otimizações. SQL_CALC_FOUND_ROWS faz com que algumas otimizações sejam desativadas.

Use estas consultas:

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

Além disso, SQL_CALC_FOUND_ROWSfoi observado que há mais problemas em geral, conforme explicado no MySQL WL # 12615 :

SQL_CALC_FOUND_ROWS tem vários problemas. Primeiro de tudo, é lento. Freqüentemente, seria mais barato executar a consulta com LIMIT e, em seguida, um SELECT COUNT ( ) separado para a mesma consulta, pois COUNT ( ) pode fazer uso de otimizações que não podem ser feitas na pesquisa de todo o conjunto de resultados (por exemplo, filesort pode ser ignorado por COUNT (*), enquanto que com CALC_FOUND_ROWS, é necessário desativar algumas otimizações de classificadores de arquivos para garantir o resultado certo)

Mais importante, ele tem semântica muito clara em várias situações. Em particular, quando uma consulta possui vários blocos de consulta (por exemplo, com UNION), simplesmente não há como calcular o número de linhas "possíveis" ao mesmo tempo em que produz uma consulta válida. Como o executor do iterador está progredindo em direção a esse tipo de consulta, é realmente difícil tentar manter a mesma semântica. Além disso, se houver vários LIMITs na consulta (por exemplo, para tabelas derivadas), não está necessariamente claro a qual deles SQL_CALC_FOUND_ROWS deve se referir. Portanto, essas consultas não triviais necessariamente terão semânticas diferentes no executor do iterador em comparação com as que tinham antes.

Finalmente, a maioria dos casos de uso em que SQL_CALC_FOUND_ROWS parece útil deve ser resolvida por outros mecanismos que não sejam LIMIT / OFFSET. Por exemplo, uma lista telefônica deve ser paginada por letra (tanto em termos de UX quanto em termos de uso do índice), não por número de registro. As discussões são cada vez mais infinitas, com rolagem ordenada por data (permitindo novamente o uso do índice), não por paginação pelo número da postagem. E assim por diante.

Madhur Bhaiya
fonte
Como realizar essas duas seleções como operação atômica? E se alguém inserir uma linha antes da consulta SELECT COUNT (*)? Obrigado.
Dom
@ Dom, se você possui o MySQL8 +, pode executar a consulta em uma única consulta usando as funções do Windows; mas essa não será uma solução ideal, pois os índices não serão usados ​​corretamente. Outra opção é cercar essas duas consultas com LOCK TABLES <tablename>e UNLOCK TABLES. Terceira opção e (melhor IMHO) é repensar a paginação. Leia: mariadb.com/kb/en/library/pagination-optimization
Madhur Bhaiya
14

De acordo com o seguinte artigo: https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Se você tiver um INDEX na sua cláusula where (se o id estiver indexado no seu caso), é melhor não usar SQL_CALC_FOUND_ROWS e, em vez disso, use 2 consultas, mas se você não tiver um índice sobre o que você coloca na cláusula where (id no seu caso), usar SQL_CALC_FOUND_ROWS é mais eficiente.

patapouf_ai
fonte
8

IMHO, a razão pela qual 2 consultas

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

são mais rápidos do que usar SQL_CALC_FOUND_ROWS

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

tem que ser visto como um caso particular.

De fato, depende da seletividade da cláusula WHERE comparada à seletividade da implícita equivalente à ORDER + LIMIT.

Como Arvids disse no comentário ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394 ), o fato de o EXPLAIN usar ou não, tabela temporária, deve ser uma boa base para saber se o SCFR será mais rápido ou não.

Mas, como eu adicionei ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ), o resultado realmente depende do caso. Para um paginador específico, você pode concluir que “nas 3 primeiras páginas, use 2 consultas; para as páginas seguintes, use um SCFR ”!

Pierre-Olivier Vares
fonte
6

Remover alguns SQL desnecessários e depois COUNT(*)será mais rápido que SQL_CALC_FOUND_ROWS. Exemplo:

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

Depois conte sem parte desnecessária:

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

Existem outras opções para você comparar:

1.) Uma função de janela retornará o tamanho real diretamente (testado no MariaDB):

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

2.) Pensando fora da caixa, na maioria das vezes os usuários não precisam saber o tamanho EXATO da tabela, uma aproximação geralmente é boa o suficiente.

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
Code4R7
fonte