PostgreSQL 嵌套循环连接性能
PostgreSQL Nested Loop Join Performance
我有两个表 exchange_rate
(10 万行)和 paid_date_t
(900 万行),结构如下。
"exchange_rate"
Column | Type | Collation | Nullable | Default
-----------------------------+--------------------------+-----------+----------+---------
valid_from | timestamp with time zone | | |
valid_until | timestamp with time zone | | |
currency | text | | |
Indexes:
"exchange_rate_unique_valid_from_currency_key" UNIQUE, btree (valid_from, currency)
"exchange_rate_valid_from_gist_idx" gist (valid_from)
"exchange_rate_valid_from_until_currency_gist_idx" gist (valid_from, valid_until, currency)
"exchange_rate_valid_from_until_gist_idx" gist (valid_from, valid_until)
"exchange_rate_valid_until_gist_idx" gist (valid_until)
"paid_date_t"
Column | Type | Collation | Nullable | Default
-------------------+-----------------------------+-----------+----------+---------
currency | character varying(3) | | |
paid_date | timestamp without time zone | | |
Indexes:
"paid_date_t_paid_date_idx" btree (paid_date)
我运行下面select查询并根据多个连接键连接这些表:
SELECT
paid_date
FROM exchange_rate erd
JOIN paid_date_t sspd
ON sspd.paid_date >= erd.valid_from AND sspd.paid_date < erd.valid_until
AND erd.currency = sspd.currency
WHERE sspd.currency != 'USD'
但是,查询的性能很低,需要数小时才能执行。下面的查询计划显示它使用嵌套循环连接。
Nested Loop (cost=0.28..44498192.71 rows=701389198 width=40)
-> Seq Scan on paid_date_t sspd (cost=0.00..183612.84 rows=2557615 width=24)
Filter: ((currency)::text <> 'USD'::text)
-> Index Scan using exchange_rate_valid_from_until_currency_gist_idx on exchange_rate erd (cost=0.28..16.53 rows=80 width=36)
Index Cond: (currency = (sspd.currency)::text)
Filter: ((sspd.paid_date >= valid_from) AND (sspd.paid_date < valid_until))
我使用过不同的索引方法,但得到了相同的结果。我知道 <=
和 >=
运算符不支持合并或散列连接。
欢迎任何想法。
您应该创建一个较小的 table,其中仅包含 paid_date_t 中的行样本。如果每次尝试测试都花费很长时间,则很难优化查询。
您的 btree 索引将进行相等性测试的列作为第 2 列,这肯定效率较低。此查询的更好的 btree 索引(如当前所写)类似于 (currency, valid_from, valid_until)
.
对于要点索引,您确实希望它在时间范围内,而不是在范围的单独端点上。您可以将 table 转换为一个范围类型,或者构建一个函数索引来动态转换它们(然后重写查询以使用相同的表达式)。由于对时区的不同处理,您的 table 具有不同的类型,这使情况变得复杂。索引看起来像:
create index on exchange_rate using gist (tstzrange(valid_from,valid_until), currency);
然后 ON 条件看起来像:
ON sspd.paid_date::timestamptz <@ tstzrange(erd.valid_from, erd.valid_until)
AND erd.currency = sspd.currency
将要点索引中的列顺序与我显示的顺序相反可能会更快,您应该在自己的数据上尝试这两种方式并查看。
我有两个表 exchange_rate
(10 万行)和 paid_date_t
(900 万行),结构如下。
"exchange_rate"
Column | Type | Collation | Nullable | Default
-----------------------------+--------------------------+-----------+----------+---------
valid_from | timestamp with time zone | | |
valid_until | timestamp with time zone | | |
currency | text | | |
Indexes:
"exchange_rate_unique_valid_from_currency_key" UNIQUE, btree (valid_from, currency)
"exchange_rate_valid_from_gist_idx" gist (valid_from)
"exchange_rate_valid_from_until_currency_gist_idx" gist (valid_from, valid_until, currency)
"exchange_rate_valid_from_until_gist_idx" gist (valid_from, valid_until)
"exchange_rate_valid_until_gist_idx" gist (valid_until)
"paid_date_t"
Column | Type | Collation | Nullable | Default
-------------------+-----------------------------+-----------+----------+---------
currency | character varying(3) | | |
paid_date | timestamp without time zone | | |
Indexes:
"paid_date_t_paid_date_idx" btree (paid_date)
我运行下面select查询并根据多个连接键连接这些表:
SELECT
paid_date
FROM exchange_rate erd
JOIN paid_date_t sspd
ON sspd.paid_date >= erd.valid_from AND sspd.paid_date < erd.valid_until
AND erd.currency = sspd.currency
WHERE sspd.currency != 'USD'
但是,查询的性能很低,需要数小时才能执行。下面的查询计划显示它使用嵌套循环连接。
Nested Loop (cost=0.28..44498192.71 rows=701389198 width=40)
-> Seq Scan on paid_date_t sspd (cost=0.00..183612.84 rows=2557615 width=24)
Filter: ((currency)::text <> 'USD'::text)
-> Index Scan using exchange_rate_valid_from_until_currency_gist_idx on exchange_rate erd (cost=0.28..16.53 rows=80 width=36)
Index Cond: (currency = (sspd.currency)::text)
Filter: ((sspd.paid_date >= valid_from) AND (sspd.paid_date < valid_until))
我使用过不同的索引方法,但得到了相同的结果。我知道 <=
和 >=
运算符不支持合并或散列连接。
欢迎任何想法。
您应该创建一个较小的 table,其中仅包含 paid_date_t 中的行样本。如果每次尝试测试都花费很长时间,则很难优化查询。
您的 btree 索引将进行相等性测试的列作为第 2 列,这肯定效率较低。此查询的更好的 btree 索引(如当前所写)类似于 (currency, valid_from, valid_until)
.
对于要点索引,您确实希望它在时间范围内,而不是在范围的单独端点上。您可以将 table 转换为一个范围类型,或者构建一个函数索引来动态转换它们(然后重写查询以使用相同的表达式)。由于对时区的不同处理,您的 table 具有不同的类型,这使情况变得复杂。索引看起来像:
create index on exchange_rate using gist (tstzrange(valid_from,valid_until), currency);
然后 ON 条件看起来像:
ON sspd.paid_date::timestamptz <@ tstzrange(erd.valid_from, erd.valid_until)
AND erd.currency = sspd.currency
将要点索引中的列顺序与我显示的顺序相反可能会更快,您应该在自己的数据上尝试这两种方式并查看。