ActiveRecord: Use consultas eficientes para contagem

Contar é aparentemente simples. Mesmo uma criança de cinco anos pode contar – quão difícil pode ser? A dificuldade está em contar com eficiência . Em um novo aplicativo, muitas vezes você pode se safar com a contagem ineficiente porque o conjunto de dados é pequeno e a carga do banco de dados é mínima. Em um aplicativo mais maduro, há mais dados, o banco de dados está mais ocupado e há outliers. Talvez os usuários em sua plataforma de blog tenham em média cinco postagens, mas alguns de seus usuários mais dedicados podem ter milhares de postagens. Isso torna os problemas de desempenho difíceis de detectar no desenvolvimento ou mesmo na produção. Ironicamente, seus usuários mais dedicados são os mais afetados!

Aqui estão alguns padrões para escrever códigos de contagem mais eficientes. Os exemplos abaixo são para um aplicativo Rails 3 usando ActiveRecord no MySQL, mas os mesmos princípios de SQL devem se aplicar a outras linguagens e estruturas.

Use contagem ou tamanho em vez de comprimento

Se você só precisa saber quantas de algo existem, use countou sizenão length. Por exemplo, se você quiser saber quantas postagens um usuário possui, use user.posts.count. Isso gera uma COUNT(*)consulta relativamente eficiente :

SELECT COUNT(*) FROM `posts` WHERE (`posts`.user_id = 8)

Chamar user.posts.lengthfornece o mesmo resultado, mas sob o capô o ActiveRecord emite uma SELECT *consulta e cria um objeto para cada postagem e, em seguida, conta os objetos. Isso não é apenas mais caro para o banco de dados, mas também mais caro no rubi.

SELECT `posts`.* FROM `posts` WHERE (`posts`.user_id = 8)

Existe uso? em vez de vazio? ou comparando a contagem com 0

Às vezes, você pode nem precisar saber a contagem exata – você só pode se importar se há alguma coisa ou nada. Nessas situações, use em vez de ou . Para continuar o exemplo anterior, se você quiser saber se um usuário tem ou não alguma postagem, use . Isso gera a seguinte consulta:exists?empty?count > 0user.posts.exists?

SELECT 1 FROM `posts` WHERE (`posts`.user_id = 8) LIMIT 1

Chamar faz essencialmente a mesma coisa que , que emite uma consulta. Este tem desempenho semelhante quando a coleção é pequena, ou seja, o usuário possui poucos posts. No entanto, se o usuário tiver muitas postagens, o banco de dados será forçado a contá-las todas. Com a consulta, o banco de dados pode responder à consulta assim que a primeira postagem for encontrada.user.posts.empty?user.posts.count > 0COUNT(*)SELECT 1 ... LIMIT 1

Use offset (x) .existe? em vez de comparar a contagem com x

E se você precisar saber se há alguma coisa? Por exemplo, você deseja saber se um usuário tem 3 ou mais postagens; em caso afirmativo, eles não são mais considerados um novo usuário. Você ainda pode usar e se beneficiar exists?! Use user.posts.offset(3).exists?, que emite a seguinte consulta:

SELECT 1 FROM `posts` WHERE (`posts`.user_id = 8) LIMIT 1 OFFSET 3

Como antes, o banco de dados pode responder à consulta assim que encontrar 3 postagens. A chamada user.posts.count > 3é funcionalmente equivalente, mas se o usuário tiver muitas postagens, o banco de dados contará todas.

Esta dica foi republicada do meu blog, jontai.me