2011年12月6日,星期二

Edge Rails 新功能:EXPLAIN

由 fxn 发布

即将发布的 Ruby on Rails 3.2 中有一些与 EXPLAIN 相关的新功能,我们想与大家分享。

  • 手动运行 EXPLAIN
  • 为慢查询自动运行 EXPLAIN
  • 禁用自动 EXPLAIN

截至本文撰写时,它们已支持 sqlite3mysql2postgresql 适配器。

手动运行 EXPLAIN

现在,您可以通过这种方式运行关系生成的 EXPLAIN SQL:

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/environments/development.rb 中获得

config.active_record.auto_explain_threshold_in_seconds = 0.5

Active Record 会监控查询,如果查询花费的时间超过该阈值,则会使用 warn 记录其查询计划。

这适用于任何运行 find_by_sql 的情况(这几乎涵盖了所有内容,因为 Active Record 的大部分最终都会调用该方法)。在关系(relation)的特定情况下,阈值是针对获取记录所需的总时间进行比较,而不是针对每个单独查询花费的时间。因为概念上我们关心的是调用成本

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

解释查询计划

解释查询计划是另一个话题,以下是一些提示:

调试愉快!