即将发布的 Ruby on Rails 3.2 中有一些与 EXPLAIN 相关的新功能,我们想与大家分享。
截至本文撰写时,它们已支持 sqlite3、mysql2 和 postgresql 适配器。
现在,您可以通过这种方式运行关系生成的 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 会 **运行** 查询或查询,然后向数据库请求它们各自的查询计划。这是因为,由于预加载,一个关系可能会触发多个查询来获取记录及其关联。在这种情况下,某些查询需要前一个查询的结果。
如果关系触发了多个查询,该方法仍然会返回一个包含所有查询计划的单个字符串。此输出旨在供人类阅读,因此我们倾向于以一种易于理解的熟悉格式呈现所有内容,而不是结构化的数据。
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,有时某些查询就是慢,而且您知道它们必须慢。例如,后台的某个重量级报表。
宏 silence_auto_explain 允许您避免在代码的这些区域中反复运行 EXPLAIN。
ActiveRecord::Base.silence_auto_explain do
# no automatic EXPLAIN here
end
解释查询计划是另一个话题,以下是一些提示:
调试愉快!