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.
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.
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.
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.
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.