[RUBY] Benötigen Sie diese Vorspannung wirklich? ~ Nutzung der verzögerten Last ~

Schauen Sie sich zuerst den folgenden Code an.

review = Review.preload(:user, :book).find_by(id: review_id)

Was machen Sie, wenn Sie Code wie diesen sehen? Ich muss kein "Preload" hinzufügen. Ich denke, ich werde darauf hinweisen.

In diesem Artikel möchte ich erklären, warum dieses "Preload" nicht notwendig ist.

Was ist Vorspannung?

Wenn Sie Preload hinzufügen, können Sie gleichzeitig die angegebenen zugehörigen Daten abrufen. In diesem Beispiel erhalten Sie beim Erhalt der Bewertung auch den zugehörigen Benutzer und das Buch gleichzeitig.

Unten sehen Sie das Ergebnis der Ausführung mit irb. Wenn Sie die Überprüfung erhalten, werden Benutzer und Buch ebenfalls AUSGEWÄHLT, und Sie können sehen, dass SQL nicht dort ausgegeben wird, wo Sie es tatsächlich verwenden.

irb(main):011:0> review_id = 15
=> 15
irb(main):012:0> review = Review.preload(:user, :book).find_by(id: review_id)
  Review Load (0.8ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` = 15 LIMIT 1
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
  Book Load (0.8ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1
=> #<Review id: 15, content: "hogehoge", user_id: 1, book_id: 1, status: "draft", created_at: "2020-06-15 14:21:23", updated_at: "2020-06-15 14:21:23">
irb(main):013:0> review.user
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):014:0> review.book
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">

Wann sollte die Vorspannung verwendet werden?

Es wird hauptsächlich als Gegenmaßnahme für N + 1 verwendet. N + 1 wird hier nicht im Detail beschrieben, aber es ist ein Ereignis, bei dem die Erfassungs-SQL verwandter Daten einzeln in einer Schleife usw. ausgegeben wird, wie unten gezeigt.

irb(main):022:0> Review.all.each do |review|
irb(main):023:1*   review.book
irb(main):024:1> end
  Review Load (0.6ms)  SELECT `reviews`.* FROM `reviews`
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 2 LIMIT 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 3 LIMIT 1
  Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 4 LIMIT 1
  Book Load (2.7ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 5 LIMIT 1

Da das Buch nicht im Voraus erworben wurde, wird SQL nacheinander bei review.book ausgegeben. Wenn Sie prealod anhängen und das Buch im Voraus erhalten, sieht es wie folgt aus.

irb(main):025:0> Review.all.preload(:book).each do |review|
irb(main):026:1*   review.book
irb(main):027:1> end
  Review Load (0.8ms)  SELECT `reviews`.* FROM `reviews`
  Book Load (0.7ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` IN (1, 2, 3, 4, 5)

Sie können sehen, dass das Buch, das sich auf die Überprüfung bezieht, die in Review.all vor dem Eintritt in die Schleife erhalten wurde, in einem SQL abgerufen wird. SQL wird während der Schleife nicht ausgegeben, da es gemeinsam vor der Schleife erfasst werden kann. Das Ausgeben von SQL ist im Allgemeinen ein kostspieliger Prozess, sodass eine einzelne SQL die Leistung verbessert. Selbst im obigen Beispiel können Sie anhand der Gesamtausführungszeit von SQL feststellen, dass es einen Leistungsunterschied gibt.

Warum fügst du es diesmal nicht hinzu?

Aber was ist mit dem ersten Beispiel? Da wir nur eine Bewertung erhalten haben, kann es nicht wie zuvor N + 1 in der Schleife sein.

Ich denke, dass das Vorladen bedeutet, dass Sie es zumindest später verwenden können. Schauen wir uns das folgende Beispiel an.

#Benutzer abrufen
#Ich werde Benutzer verwenden und später überprüfen, also laden Sie es vor
review = Review.preload(:user, :book).find_by(id: review_id)

#Benutzer verwenden
review.user

#benutze das Buch
review.book

Da das Vorladen beigefügt ist, werden Benutzer und Buch auch beim Erwerb der Überprüfung erfasst. Das Ausführungsergebnis ist wie folgt.

irb(main):007:0> review = Review.preload(:user, :book).find_by(id: review_id)
  Review Load (0.8ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` = 36 LIMIT 1
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
  Book Load (0.4ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1
=> #<Review id: 36, content: "", user_id: 1, book_id: 1, status: "draft", created_at: "2020-06-30 15:20:01", updated_at: "2020-06-30 15:20:01">
irb(main):008:0> review.user
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):009:0> review.book
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">

Was wäre, wenn Sie keine Vorspannung hätten?

irb(main):010:0> review = Review.find_by(id: review_id)
  Review Load (0.7ms)  SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` = 36 LIMIT 1
=> #<Review id: 36, content: "", user_id: 1, book_id: 1, status: "draft", created_at: "2020-06-30 15:20:01", updated_at: "2020-06-30 15:20:01">
irb(main):011:0> review.user
  User Load (0.5ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, name: "1234567890", created_at: "2019-12-12 05:43:52", updated_at: "2019-12-12 05:43:52">
irb(main):012:0> review.book
  Book Load (0.6ms)  SELECT `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
=> #<Book id: 1, title: "book1", created_at: "2020-06-15 14:21:15", updated_at: "2020-06-15 14:21:15">

Wenn Sie die Überprüfung erhalten, erhalten Sie nicht den Benutzer und das Buch, aber die SQL wird dort ausgegeben, wo Sie sie verwenden. Da es jedoch nur eine Überprüfung gibt, ist die Anzahl der ausgegebenen SQLs gleich. In diesem Beispiel ist der Wirkungsgrad mit oder ohne Vorspannung gleich.

Aber was ist mit dem folgenden Beispiel?

#Benutzer abrufen
#Ich werde Benutzer verwenden und später überprüfen, also laden Sie es vor
review = Review.preload(:user, :book).find_by(id: review_id)

#Verwenden Sie den Benutzer unter bestimmten Bedingungen
if hoge
  review.user
end

#Verwenden Sie das Buch unter bestimmten Bedingungen
if fuga
  review.book
end

Da das Vorladen beigefügt ist, werden Benutzer und Buch auch beim Erwerb der Überprüfung erfasst. Wenn hoge oder fuga wahr ist, werden sowohl Benutzer als auch Überprüfung verwendet, sodass die Anzahl der SQLs gleich ist, unabhängig davon, ob das Vorladen durchgeführt wird oder nicht.

Aber was ist mit falsch? Wenn beispielsweise hoge false ist, wird der Benutzer nicht verwendet, sodass der durch das Vorladen erhaltene Benutzer nicht verwendet wird. Wenn fuga falsch ist, wird das Buch auch nicht verwendet.

Was ist, wenn ich diesmal keine Vorlast hinzugefügt habe?

#Benutzer abrufen
review = Review.find_by(id: review_id)

#Verwenden Sie den Benutzer unter bestimmten Bedingungen
if hoge
  review.user
end

#Verwenden Sie das Buch unter bestimmten Bedingungen
if fuga
  review.book
end

Wenn Sie eine Bewertung erhalten, erhalten Sie keinen Benutzer oder kein Buch. Wenn hoge oder fuga wahr ist, werden Rezension und Buch am Verwendungsort erworben. Wenn false, wird es nicht abgerufen.

Bei dieser Implementierung kann sie nur bei Verwendung abgerufen werden. Eine Implementierung, die Daten bei Bedarf auf diese Weise abruft, wird übrigens als verzögertes Laden bezeichnet.

Wussten Sie, welches effizienter ist? Beim Vorladen eines Modells ist die Anzahl der SQL-Dateien dieselbe wie beim Vorladen, wenn nicht alle durch das Vorladen erfassten Modelle verwendet werden. Wenn es auch nur ein Muster gibt, das abhängig von den Bedingungen nicht verwendet wird, erhöht sich die Anzahl der SQL-Anweisungen.

Wenn Sie nur ein Element wie im ersten Beispiel erhalten möchten, ist es sinnlos, es vorzuladen, aber es ist ineffizient. Seien Sie also vorsichtig.

Schließlich

Wenn Sie neu bei Rails sind und Preload und Eifriges Laden irgendwie kennen, sollten Sie es einfach hinzufügen, oder? Ich denke, viele Leute denken das.

Viele Rezensenten weisen auf N + 1 hin, aber ich habe das Gefühl, dass nur wenige Leute auf nutzlose Vorspannungen wie diese hinweisen (individueller Eindruck).

Es ist leicht zu glauben, dass das Hinzufügen von Preload und Eiferspannung, die N + 1 besiegen, nichts Falsches ist, aber es gibt Muster, die wie diesmal ineffizient werden. Daher sollten diejenigen, die sich dessen nicht bewusst waren, sich dessen bewusst sein. Lass uns gehen.

Recommended Posts

Benötigen Sie diese Vorspannung wirklich? ~ Nutzung der verzögerten Last ~
Benötigen Sie eine speicherbewusste Implementierung von Java?
Bequeme Verwendung von Map, die Sie nicht unerwartet kennen