未验证 提交 d6529af2 编写于 作者: J Jordan Lewis 提交者: Jeremy Daer

Simplify and speed up Postgres query for primary_keys

primary_keys(table) needs to query various metadata tables in Postgres to
determine the primary key for the table. Previously, it did so using a
complex common table expression against pg_constraint and pg_attribute.

This patch simplifies the query by joining pg_index against pg_attribute
instead of going through pg_constraint. This avoids an expensive unnest,
window function query, and common table expression.

EXPLAINing these queries in Postgres against a database with a single
table with a composite primary key shows a 66% reduction in the plan and
execute latencies. This is significant during application startup time,
especially against very large schemas, where these queries would be even
slower and more numerous.

Closes #27949
上级 2b4d145f
...@@ -426,16 +426,14 @@ def pk_and_sequence_for(table) #:nodoc: ...@@ -426,16 +426,14 @@ def pk_and_sequence_for(table) #:nodoc:
def primary_keys(table_name) # :nodoc: def primary_keys(table_name) # :nodoc:
select_values(<<-SQL.strip_heredoc, "SCHEMA") select_values(<<-SQL.strip_heredoc, "SCHEMA")
WITH pk_constraint AS ( SELECT a.attname
SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint FROM pg_index i
WHERE contype = 'p' CROSS JOIN unnest(i.indkey) as k
AND conrelid = #{quote(quote_table_name(table_name))}::regclass JOIN pg_attribute a
), cons AS ( ON a.attrelid = i.indrelid
SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint AND a.attnum = k
) WHERE i.indrelid = #{quote(quote_table_name(table_name))}::regclass
SELECT attr.attname FROM pg_attribute attr AND i.indisprimary
INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
ORDER BY cons.rownum
SQL SQL
end end
......
...@@ -298,6 +298,10 @@ def setup ...@@ -298,6 +298,10 @@ def setup
t.string :region t.string :region
t.integer :code t.integer :code
end end
@connection.create_table(:barcodes_reverse, primary_key: ["code", "region"], force: true) do |t|
t.string :region
t.integer :code
end
end end
def teardown def teardown
...@@ -308,6 +312,11 @@ def test_composite_primary_key ...@@ -308,6 +312,11 @@ def test_composite_primary_key
assert_equal ["region", "code"], @connection.primary_keys("barcodes") assert_equal ["region", "code"], @connection.primary_keys("barcodes")
end end
def test_composite_primary_key_out_of_order
skip if current_adapter?(:SQLite3Adapter)
assert_equal ["region", "code"], @connection.primary_keys("barcodes")
end
def test_primary_key_issues_warning def test_primary_key_issues_warning
model = Class.new(ActiveRecord::Base) do model = Class.new(ActiveRecord::Base) do
def self.table_name def self.table_name
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册