Как отметил Дэмиен, если вы действительно хотите один запрос каждый раз, вы должны использовать join.
Но вы, возможно, не захотите ни одного вызова SQL. Вот почему (с здесь ):
Оптимизированная готовая загрузка
Давайте посмотрим на это:
Post.find(:all, :include => [:comments])
До Rails 2.0 мы видели что-то вроде следующего SQL-запроса в журнале:
SELECT `posts`.`id` AS t0_r0, `posts`.`title` AS t0_r1, `posts`.`body` AS t0_r2, `comments`.`id` AS t1_r0, `comments`.`body` AS t1_r1 FROM `posts` LEFT OUTER JOIN `comments` ON comments.post_id = posts.id
Но теперь в Rails 2.1 одна и та же команда доставит разные SQL-запросы. На самом деле как минимум 2, а не 1. «И как это может быть улучшением?» Давайте посмотрим на сгенерированные запросы SQL:
SELECT `posts`.`id`, `posts`.`title`, `posts`.`body` FROM `posts`
SELECT `comments`.`id`, `comments`.`body` FROM `comments` WHERE (`comments`.post_id IN (130049073,226779025,269986261,921194568,972244995))
Ключевое слово :include
для Eager Loading было реализовано для решения страшной проблемы 1 + N. Эта проблема возникает, когда у вас есть ассоциации, затем вы загружаете родительский объект и начинаете загружать одну ассоциацию за раз, таким образом, проблема 1 + N. Если ваш родительский объект имеет 100 дочерних объектов, вы выполняете 101 запрос, что не очень хорошо. Один из способов оптимизировать это - объединить все, используя предложение OUTER JOIN
в SQL, чтобы родительский и дочерний объекты загружались одновременно в одном запросе.
Казалось, хорошая идея и на самом деле все еще. Но для некоторых ситуаций внешнее соединение монстров становится медленнее, чем многие меньшие запросы. Шло много дискуссий, и вы можете посмотреть детали на билеты 9640, 9497, 9560, L109.
Суть: : как правило, лучше разделить соединение монстров на более мелкие, как вы видели в приведенном выше примере. Это позволяет избежать проблемы перегрузки декартовой системы. Для непосвященных давайте запустим версию запроса с внешним соединением:
mysql> SELECT `posts`.`id` AS t0_r0, `posts`.`title` AS t0_r1, `posts`.`body` AS t0_r2, `comments`.`id` AS t1_r0, `comments`.`body` AS t1_r1 FROM `posts` LEFT OUTER JOIN `comments` ON comments.post_id = posts.id ;
+-----------+-----------------+--------+-----------+---------+
| t0_r0 | t0_r1 | t0_r2 | t1_r0 | t1_r1 |
+-----------+-----------------+--------+-----------+---------+
| 130049073 | Hello RailsConf | MyText | NULL | NULL |
| 226779025 | Hello Brazil | MyText | 816076421 | MyText5 |
| 269986261 | Hello World | MyText | 61594165 | MyText3 |
| 269986261 | Hello World | MyText | 734198955 | MyText1 |
| 269986261 | Hello World | MyText | 765025994 | MyText4 |
| 269986261 | Hello World | MyText | 777406191 | MyText2 |
| 921194568 | Rails 2.1 | NULL | NULL | NULL |
| 972244995 | AkitaOnRails | NULL | NULL | NULL |
+-----------+-----------------+--------+-----------+---------+
8 rows in set (0.00 sec)
Обратите внимание на это: вы видите много дупликаций в первых 3 столбцах (от t0_r0 до t0_r2)? Это столбцы модели Post, остальные - столбцы комментариев каждого сообщения. Обратите внимание, что сообщение «Hello World» было повторено 4 раза. Вот что делает соединение: родительские строки повторяются для каждого потомка. Этот конкретный пост имеет 4 комментария, поэтому он был повторен 4 раза.
Проблема в том, что это сильно бьет по Rails, потому что ему придется иметь дело с несколькими маленькими и недолговечными объектами. Боль ощущается на стороне Rails, не так сильно на стороне MySQL. Теперь сравните это с меньшими запросами:
mysql> SELECT `posts`.`id`, `posts`.`title`, `posts`.`body` FROM `posts` ;
+-----------+-----------------+--------+
| id | title | body |
+-----------+-----------------+--------+
| 130049073 | Hello RailsConf | MyText |
| 226779025 | Hello Brazil | MyText |
| 269986261 | Hello World | MyText |
| 921194568 | Rails 2.1 | NULL |
| 972244995 | AkitaOnRails | NULL |
+-----------+-----------------+--------+
5 rows in set (0.00 sec)
mysql> SELECT `comments`.`id`, `comments`.`body` FROM `comments` WHERE (`comments`.post_id IN (130049073,226779025,269986261,921194568,972244995));
+-----------+---------+
| id | body |
+-----------+---------+
| 61594165 | MyText3 |
| 734198955 | MyText1 |
| 765025994 | MyText4 |
| 777406191 | MyText2 |
| 816076421 | MyText5 |
+-----------+---------+
5 rows in set (0.00 sec)
На самом деле я немного обманываю, я вручную удалил поля create_at и updated_at из всех вышеупомянутых запросов, чтобы вы поняли это немного яснее. Итак, вот оно: набор результатов сообщений, разделенных и не дублированных, и набор результатов комментариев того же размера, что и раньше. Чем длиннее и сложнее результирующий набор, тем больше это имеет значение, поскольку с большим количеством объектов придется столкнуться Rails. Выделение и освобождение нескольких сотен или тысяч маленьких дублированных объектов никогда не бывает выгодным.
Но эта новая функция умна. Допустим, вы хотите что-то вроде этого:
>> Post.find(:all, :include => [:comments], :conditions => ["comments.created_at > ?", 1.week.ago.to_s(:db)])
В Rails 2.1 он поймет, что для таблицы 'comments' существует условие фильтрации, поэтому он не будет разбивать его на небольшие запросы, но вместо этого он сгенерирует старую версию внешнего соединения, например:
SELECT `posts`.`id` AS t0_r0, `posts`.`title` AS t0_r1, `posts`.`body` AS t0_r2, `posts`.`created_at` AS t0_r3, `posts`.`updated_at` AS t0_r4, `comments`.`id` AS t1_r0, `comments`.`post_id` AS t1_r1, `comments`.`body` AS t1_r2, `comments`.`created_at` AS t1_r3, `comments`.`updated_at` AS t1_r4 FROM `posts` LEFT OUTER JOIN `comments` ON comments.post_id = posts.id WHERE (comments.created_at > '2008-05-18 18:06:34')
Итак, вложенные объединения, условия и т. Д. В таблицах соединений все равно должны работать нормально. В целом это должно ускорить ваши запросы. Некоторые сообщили, что из-за большего количества отдельных запросов MySQL, похоже, получает более сильный удар по процессору. Вы работаете дома и проводите стресс-тесты и тесты, чтобы увидеть, что происходит.