2011 年 12 月 6 日星期二

Edge Rails 的新增功能:EXPLAIN

作者:fxn

我们想要分享有关即将推出的 Ruby on Rails 3.2 中与 EXPLAIN 相关的部分新功能

  • 手动运行 EXPLAIN
  • 针对速度较慢的查询自动运行 EXPLAIN
  • 禁止自动运行 EXPLAIN

在本指南撰写时,可以使用以下适配器:sqlite3mysql2postgresql

手动运行 EXPLAIN

您现在可以使用以下方式对关系生成的 SQL 运行 EXPLAIN

User.where(:id => 1).joins(:posts).explain

该方法调用的结果是一个字符串,它仔细地模仿了数据库 shell 的输出。例如,在 MySQL 中,您会得到类似于以下内容的结果

EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |             |
|  1 | SIMPLE      | posts | ALL   | NULL          | NULL    | NULL    | NULL  |    1 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
2 rows in set (0.00 sec)

而在 PostgreSQL 中,同样的调用会生成类似于以下内容的结果

EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1
                                  QUERY PLAN
------------------------------------------------------------------------------
 Nested Loop Left Join  (cost=0.00..37.24 rows=8 width=0)
   Join Filter: (posts.user_id = users.id)
   ->  Index Scan using users_pkey on users  (cost=0.00..8.27 rows=1 width=4)
         Index Cond: (id = 1)
   ->  Seq Scan on posts  (cost=0.00..28.88 rows=8 width=4)
         Filter: (posts.user_id = 1)
(6 rows)

请注意,explain运行查询,然后向数据库询问各个查询计划。这是因为迫切加载一个关系可能会触发多个查询以获取记录及其关联项,并且在这种情况下,某些查询需要前一个查询的结果。

如果关系触发多个查询,方法仍然会返回一个包含所有查询计划的字符串。这是供人类理解的输出,因此我们更愿意将所有内容作为字符串以一种马上就能理解的格式呈现出来,而不是作为一种结构。

针对速度较慢的查询自动运行 EXPLAIN

Rails 3.2 有助于检测速度较慢的查询。

新应用会获得

config.active_record.auto_explain_threshold_in_seconds = 0.5

config/environments/development.rb 中。Active Record 会监视查询,如果查询花费的时间超过此阈值,查询计划将使用 warn 记录下来。

这适用于运行 find_by_sql 的任何内容(几乎所有内容都适用,因为大多数 Active Record 最终都会调用此方法)。对于关系的特殊情况,会将阈值与获取记录所需的总时间进行比较,而不是与各个单独查询所花费的时间进行比较。因为从概念上来说,我们关注的是调用的成本

User.where(:id => 1).joins(:posts).explain

而不是由于实现可能触发的不同查询的成本。

在测试和生产环境中,阈值默认为 nil,这意味着该功能已禁用。

如果未设置阈值,此参数的值也为 nil,因此现有的应用若要迁移至 3.2 以便启用自动 EXPLAIN,需要手动添加。

禁止自动运行 EXPLAIN

在启用自动 EXPLAIN 的情况下,某些查询可能仍然很慢,并且您知道必须这样。例如,后台中的一个重量级报表。

silence_auto_explain 允许您在代码的那些区域中避免重复运行 EXPLAIN

ActiveRecord::Base.silence_auto_explain do
  # no automatic EXPLAIN here
end

解释查询计划

查询计划的解释是另一门主题,以下是几个指针

祝调试快乐!