New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options to ActiveRecord::Relation#explain #31374

Open
wants to merge 1 commit into
base: master
from

Conversation

Projects
None yet
5 participants
@aderyabin
Copy link
Contributor

aderyabin commented Dec 8, 2017

Support database specific options for ANALYZE requests.

Summary

EXPLAIN displays the execution plan for the supplied statement and supports very useful options according to documentation like ANALYZE. For example, usage EXPLAIN ANALYZEdisplays timing information.

Another option is a FORMAT. It specifies the output format.

Current implementation ActiveRecord::Relation#explain does not support any options.
I've implemented support for PostgreSQL's options (ANALYZE, VERBOSE, BUFFERS, TIMING and FORMAT).

FORMAT supports TEXT, XML, JSON, or YAML.

MySQL supports support EXTENDED, PARTITIONS and FORMAT options.

Examples

PostgreSQL

JSON.load(Author.where(id: 1).explain(format: :json)[0])

# [{"Plan"=>{"Node Type"=>"Index Scan", "Parallel Aware"=>false, "Scan Direction"=>"Forward", "Index Name"=>"authors_pkey", "Relation Name"=>"authors", "Alias"=>"authors", "Startup Cost"=>0.15, "Total Cost"=>8.17, "Plan Rows"=>1, "Plan Width"=>120, "Index Cond"=>"(id = '1'::bigint)"}}]

Author.where(id: 1).explain(analyze: true)
#                                                        QUERY PLAN
# ------------------------------------------------------------------------------------------------------------------------
#  Index Scan using authors_pkey on authors  (cost=0.15..8.17 rows=1 width=120) (actual time=0.005..0.005 rows=1 loops=1)
#    Index Cond: (id = '1'::bigint)
#  Planning time: 0.059 ms
#  Execution time: 0.015 ms
# (4 rows)

puts Author.where(id: 1).includes(posts: [:comments]).explain(format: :json)
[
  {
    "Plan": {
      "Node Type": "Index Scan",
      "Parallel Aware": false,
      "Scan Direction": "Forward",
      "Index Name": "authors_pkey",
      "Relation Name": "authors",
      "Alias": "authors",
      "Startup Cost": 0.15,
      "Total Cost": 8.17,
      "Plan Rows": 1,
      "Plan Width": 120,
      "Index Cond": "(id = '1'::bigint)"
    }
  }
]
[
  {
    "Plan": {
      "Node Type": "Bitmap Heap Scan",
      "Parallel Aware": false,
      "Relation Name": "posts",
      "Alias": "posts",
      "Startup Cost": 4.16,
      "Total Cost": 9.50,
      "Plan Rows": 2,
      "Plan Width": 136,
      "Recheck Cond": "(author_id = '1'::bigint)",
      "Plans": [
        {
          "Node Type": "Bitmap Index Scan",
          "Parent Relationship": "Outer",
          "Parallel Aware": false,
          "Index Name": "index_posts_on_author_id",
          "Startup Cost": 0.00,
          "Total Cost": 4.16,
          "Plan Rows": 2,
          "Plan Width": 0,
          "Index Cond": "(author_id = '1'::bigint)"
        }
      ]
    }
  }
]
[
  {
    "Plan": {
      "Node Type": "Seq Scan",
      "Parallel Aware": false,
      "Relation Name": "comments",
      "Alias": "comments",
      "Startup Cost": 0.00,
      "Total Cost": 15.36,
      "Plan Rows": 8,
      "Plan Width": 216,
      "Filter": "(post_id = ANY ('{1,2,4,5,6}'::integer[]))"
    }
  }
]

MySQL

JSON.load(Author.where(id: 1).explain(format: :json)[0])
# {"query_block"=>{"select_id"=>1, "cost_info"=>{"query_cost"=>"1.00"}, "table"=>{"table_name"=>"authors", "access_type"=>"const", "possible_keys"=>["PRIMARY"], "key"=>"PRIMARY", "used_key_parts"=>["id"], "key_length"=>"8", "ref"=>["const"], "rows_examined_per_scan"=>1, "rows_produced_per_join"=>1, "filtered"=>"100.00", "cost_info"=>{"read_cost"=>"0.00", "eval_cost"=>"0.20", "prefix_cost"=>"0.00", "data_read_per_join"=>"2K"}, "used_columns"=>["id", "name", "author_address_id", "author_address_extra_id", "organization_id", "owned_essay_id"]}}}
@rails-bot

This comment has been minimized.

Copy link

rails-bot commented Dec 8, 2017

r? @schneems

(@rails-bot has picked a reviewer for you, use r? to override)

@aderyabin aderyabin force-pushed the aderyabin:feature/explain_options branch 6 times, most recently to 0df5839 Dec 8, 2017

@fatkodima

This comment has been minimized.

Copy link
Contributor

fatkodima commented Dec 8, 2017

duplicate? #15829

@aderyabin aderyabin force-pushed the aderyabin:feature/explain_options branch from 0df5839 Dec 8, 2017

@aderyabin

This comment has been minimized.

Copy link
Contributor

aderyabin commented Dec 8, 2017

@fatkodima Good question.

There are several differences:

  • FORMAT option returns raw result. And you can export it to Explain analyzers.
JSON.load(Author.where(id: 1).explain(format: :json)[0])
  • Checks FORMAT value
  • Works correctly with eager loading and collects all queries
  • Supports FORMAT option for MySQL
  • Up to date without conflicts

@aderyabin aderyabin force-pushed the aderyabin:feature/explain_options branch to 42588a7 Jan 9, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment