44 votes

Compter le nombre de requêtes effectuées

Je voudrais tester qu'un certain morceau de code exécute le moins de requêtes SQL possible.

ActiveRecord::TestCase semble avoir sa propre méthode assert_queries , qui fera exactement cela. Mais comme je ne corrige pas ActiveRecord, cela ne me sert à rien.

RSpec ou ActiveRecord fournit-il des moyens officiels et publics de compter le nombre de requêtes SQL effectuées dans un bloc de code?

50voto

Ryan Bigg Points 64561

Je pense que vous avez répondu à votre propre question en mentionnant assert_queries, mais va ici:

Je vous recommande de prendre un coup d'oeil au code behind assert_queries et en l'utilisant pour construire votre propre méthode que vous pouvez utiliser pour compter les requêtes. Le principal de la magie à l'œuvre ici est cette ligne:

ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)

J'ai eu un peu d'un bricoleur, ce matin, et arraché les pièces de ActiveRecord qui ne la requête de comptage et est venu avec ceci:

module ActiveRecord
  class QueryCounter
    cattr_accessor :query_count do
      0
    end

    IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/]

    def call(name, start, finish, message_id, values)
      # FIXME: this seems bad. we should probably have a better way to indicate
      # the query was cached
      unless 'CACHE' == values[:name]
        self.class.query_count += 1 unless IGNORED_SQL.any? { |r| values[:sql] =~ r }
      end
    end
  end
end

ActiveSupport::Notifications.subscribe('sql.active_record', ActiveRecord::QueryCounter.new)

module ActiveRecord
  class Base
    def self.count_queries(&block)
      ActiveRecord::QueryCounter.query_count = 0
      yield
      ActiveRecord::QueryCounter.query_count
    end
  end
end

Vous serez en mesure de référence de l' ActiveRecord::Base.count_queries méthode de n'importe où. Passer un bloc dans lequel vos requêtes sont exécutées et il sera de retour le nombre de requêtes qui ont été exécutées:

ActiveRecord::Base.count_queries do
  Ticket.first
end

Renvoie "1" pour moi. Pour faire ce travail: mettre dans un fichier à l' lib/active_record/query_counter.rb et de demander à votre config/application.rb le fichier comme ceci:

require 'active_record/query_counter'

Hey presto!


Un peu d'explication est probablement nécessaire. Lorsque nous appelons cette ligne:

    ActiveSupport::Notifications.subscribe('sql.active_record', ActiveRecord::QueryCounter.new)

Nous crochet dans Rails 3 est peu de notifications cadre. C'est un brillant peu plus de la dernière version majeure de Rails que personne ne sait vraiment. Il nous permet de vous abonner à des notifications d'événements au sein de Rails à l'aide de la subscribe méthode. Nous passons à l'événement, nous voulons vous abonner en tant que premier argument, alors tout objet qui répond à l' call la deuxième.

Dans ce cas, lorsqu'une requête est exécutée notre petite requête de compteur consciencieusement incrémenter le ActiveRecord::QueryCounter.query_count variable, mais seulement pour les vrais questions.

De toute façon, c'était amusant. J'espère qu'il s'agit utile pour vous.

23voto

Yuriy Kharchenko Points 421

Ma vision du script de Ryan (nettoyé un peu et enveloppé dans un matcher), j'espère qu'il est toujours d'actualité pour quelqu'un:

Je mets ceci dans spec / support / query_counter.rb

 module ActiveRecord
  class QueryCounter

    attr_reader :query_count

    def initialize
      @query_count = 0
    end

    def to_proc
      lambda(&method(:callback))
    end

    def callback(name, start, finish, message_id, values)
      @query_count += 1 unless %w(CACHE SCHEMA).include?(values[:name])
    end

  end
end
 

et ceci à spec / support / matchers / dépasser_query_limit.rb

 RSpec::Matchers.define :exceed_query_limit do |expected|

  match do |block|
    query_count(&block) > expected
  end

  failure_message_for_should_not do |actual|
    "Expected to run maximum #{expected} queries, got #{@counter.query_count}"
  end

  def query_count(&block)
    @counter = ActiveRecord::QueryCounter.new
    ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
    @counter.query_count
  end

end
 

Usage:

 expect { MyModel.do_the_queries }.to_not exceed_query_limit(2)
 

12voto

Jaime Cham Points 510

Voici une autre formulation de la solution de Ryan et Yuri qui n'est qu'une fonction que vous ajoutez à votre test_helper.rb :

 def count_queries &block
  count = 0

  counter_f = ->(name, started, finished, unique_id, payload) {
    unless payload[:name].in? %w[ CACHE SCHEMA ]
      count += 1
    end
  }

  ActiveSupport::Notifications.subscribed(counter_f, "sql.active_record", &block)

  count
end
 

L'utilisation est juste:

 c = count_queries do
  SomeModel.first
end
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X