[RUBY] [Rails] Supprimez le SQL inutile en utilisant le contrôle de cache des associations

Active Record a une fonction puissante appelée fonction d'association. Dans cet article, nous avons résumé comment utiliser le contrôle de cache des associations pour supprimer le SQL inutile.

Qu'est-ce que le contrôle du cache d'association?

Il est décrit comme suit dans le guide Rails. [Active Record Association-3.1 Cache Control](https://railsguides.jp/association_basics.html#%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3% 83% A5% E5% 88% B6% E5% BE% A1)

Le résultat de la dernière requête exécutée est conservé dans le cache et peut être utilisé dans les opérations suivantes.

En fait, déplacez-le et vérifiez s'il est mis en cache. Utilisez le modèle ci-dessous.

class User < ApplicationRecord
  has_many :reviews
end

class Review < ApplicationRecord
  belongs_to :user
end

Lorsque ʻuser.reviews` est exécuté comme indiqué ci-dessous, SQL n'est pas émis lors de la deuxième exécution. En effet, ActiveRecord met en cache le résultat de la première exécution et le renvoie.

irb(main):004:0> user = User.first
  User Load (5.8ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User:0x000056307ae3aca0

irb(main):005:0> user.reviews
  Review Load (2.7ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1
=> [#<Review:0x000056307b1a77d0

irb(main):007:0> user.reviews
=> [#<Review:0x000056307b1a77d0

Dans cet exemple, vous pouvez voir qu'il faut 2,7 ms pour exécuter le SQL pour obtenir les critiques. Puisque le cache est utilisé la deuxième fois, cela signifie que le même processus économise 2,7 ms.

Il n'y a pas beaucoup d'avantages si un seul problème SQL est raccourci comme cette fois, Si cela est accumulé 100 fois et 1000 fois, ce sera une différence de quelques secondes, et vous pourrez sentir la différence.

Au moment de l'acquisition

Utilisez le même modèle que dans l'exemple précédent. Si vous disposez déjà de l'objet utilisateur, comment obtenir les avis de l'utilisateur?

#Objet utilisateur acquis
user

#Obtenez les avis de l'utilisateur
# 1
reviews = Review.where(user_id: user.id)

# 2
reviews = Review.where(user: user)

# 3
reviews = user.reviews

Le SQL émis lors de la récupération des avis est le même pour 1 à 3, mais il y a une différence. La différence est de savoir si les données liées ʻutilisateur de la révision sont mises en cache. Obtenez des critiques par les méthodes 1 à 3 ci-dessus avec irb, et vérifiez si les données associées ʻutilisateur des critiques sont mises en cache.

#1


irb(main):009:0> reviews = Review.where(user_id: user.id)
  Review Load (1.2ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1
=> [#<Review:0x000055fc149bca10

irb(main):010:0> reviews.first.user
  User Load (0.7ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User:0x000055fc14b75d20

#2


irb(main):011:0> reviews = Review.where(user: user)
  Review Load (0.8ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1
=> [#<Review:0x000055fc14b83ab0

irb(main):012:0> reviews.first.user
  User Load (0.7ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User:0x000055fc14fca880

#3


irb(main):015:0> reviews = user.reviews
  Review Load (0.6ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1
=> [#<Review:0x000055fc1504c0b0

irb(main):016:0> reviews.first.user
=> #<User:0x000055fc14873f78

Seulement dans le cas de 3, le SQL pour obtenir l'utilisateur n'est pas émis lors de l'exécution de reviews.first.user. Il semble que les données d'origine associées soient mises en cache lorsqu'elles sont acquises par association.

Avant de vérifier réellement l'opération, je m'attendais à ce que les données associées soient mises en cache car la méthode de transmission de l'objet à où comme dans 2 passe également l'objet, mais elle a été implémentée comme ça. Cela ne semble pas être le cas.

Lors de l'acquisition de données associées, il est préférable de les acquérir à l'aide d'une association car le cache peut être utilisé, alors utilisons-le activement.

Au moment de la création

Utilisez le même modèle que dans l'exemple précédent. Si vous avez déjà récupéré l'objet utilisateur et souhaitez ajouter un avis à cet utilisateur, comment le créez-vous?

#Objet utilisateur acquis
user

#Créer un nouvel avis pour l'utilisateur
# 1
review = Review.create!(content: 'hogehoge', user_id: user.id)

# 2
review = Review.create!(content: 'hogehoge', user: user)

# 3
review = user.reviews.create!(content: 'hogehoge')

L'insertion SQL est la même pour 1 à 3, mais il existe une différence entre le SQL avant la création et l'état du cache après la création. Vérifions avec irb.

#1


#Nombre d'avis avant création
irb(main):051:0> user.reviews.size
=> 19

#L'utilisateur a été sélectionné avant la création!
irb(main):054:0> review = Review.create!(content: 'hogehoge', user_id: user.id)
   (0.4ms)  BEGIN
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
  Review Create (0.7ms)  INSERT INTO `reviews` (`content`, `user_id`, `created_at`, `updated_at`) VALUES ('hogehoge', 1, '2020-06-15 14:46:13.461715', '2020-06-15 14:46:13.461715')
   (2.0ms)  COMMIT
=> #<Review:0x000055fc16072188
 id: 20,

#L'utilisateur est mis en cache dans l'avis renvoyé
#→ L'utilisateur est-il acquis au moment de la création pour faire ce cache?
irb(main):055:0> review.user
=> #<User:0x000055fc16076c60

#Utilisateur d'origine.Le nombre d'avis n'a pas augmenté car il est identique au cache avant la création
irb(main):056:0> user.reviews.size
=> 19
#Rechargement requis pour mettre à jour
irb(main):057:0> user.reviews.reload.size
  Review Load (0.9ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1
=> 20

#2


#Nombre d'avis avant création
irb(main):058:0> user.reviews.size
=> 20

#Pas de sélection avant création
irb(main):059:0> review = Review.create!(content: 'hogehoge', user: user)
   (0.4ms)  BEGIN
  Review Create (0.6ms)  INSERT INTO `reviews` (`content`, `user_id`, `created_at`, `updated_at`) VALUES ('hogehoge', 1, '2020-06-15 14:53:06.510290', '2020-06-15 14:53:06.510290')
   (3.4ms)  COMMIT
=> #<Review:0x000055fc16fa6690
 id: 21,

#L'utilisateur est mis en cache dans l'avis renvoyé
#→ Il semble que l'objet utilisateur passé à créer soit mis en cache
irb(main):060:0> review.user
=> #<User:0x000055fc16b28b40
 id: 1,

#Utilisateur d'origine.Le nombre d'avis n'a pas augmenté car il est identique au cache avant la création
irb(main):061:0> user.reviews.size
=> 20
#Rechargement requis pour mettre à jour
irb(main):062:0> user.reviews.reload.size
  Review Load (0.8ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`user_id` = 1
=> 21

#3


#Nombre d'avis avant création
irb(main):063:0> user.reviews.size
=> 21

#Pas de sélection avant création
irb(main):064:0> review = user.reviews.create!(content: 'hogehoge')
   (0.6ms)  BEGIN
  Review Create (0.6ms)  INSERT INTO `reviews` (`content`, `user_id`, `created_at`, `updated_at`) VALUES ('hogehoge', 1, '2020-06-15 14:55:45.393655', '2020-06-15 14:55:45.393655')
   (1.8ms)  COMMIT
=> #<Review:0x000055fc15fd6120
 id: 22,

#L'utilisateur est mis en cache dans l'avis renvoyé
irb(main):065:0> review.user
=> #<User:0x000055fc16b28b40
 id: 1,

# user.Également ajouté aux avis
irb(main):066:0> user.reviews.size
=> 22

Dans tous les modèles, l'objet de révision renvoyé par create a mis en cache l'objet utilisateur. Cependant, dans le cas de 1, l'instruction select a été émise avant la création. Si vous avez un objet d'association, il semble plus efficace de passer l'objet. De plus, seulement dans le cas de 3, la révision créée est ajoutée à ʻuser.reviews`.

Lors de la mise à jour des données associées, il est préférable d'utiliser l'association car elle est ajoutée aux données d'origine et peut être traitée plus efficacement. Même si vous n'utilisez pas l'association, il semble plus efficace de passer l'association en tant qu'objet comme en 2, car l'instruction select n'est pas émise inutilement.

finalement

Je pensais connaître le cache dans une certaine mesure, mais je n'avais pas remarqué le comportement que select a été émis avant la 1ère et la 2ème création écrite en [Au moment de la création] ... Il semble qu'il y ait encore beaucoup de choses que vous n'avez pas remarquées à moins d'essayer consciemment.

Recommended Posts

[Rails] Supprimez le SQL inutile en utilisant le contrôle de cache des associations
Une revue du code utilisé par les rails débutants
[Rails] Enregistrez-vous par attribut du même modèle en utilisant Devise
[Rails] Comment afficher une liste de messages par catégorie
Cliquez sur l'image dans le champ file_field de Rails pour télécharger et remplacer l'aperçu
[Rails] Introduction de Rubocop par les débutants
[Rails] Vérifiez le contenu de l'objet
Explication de l'ordre des itinéraires ferroviaires
Vérifier l'état de migration des rails
Fonction Strict_loading pour supprimer l'occurrence de problème N + 1 ajoutée à partir des rails 6.1
Comment faire de https le schéma de l'URL générée par l'assistant d'URL de Rails