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

将要点索引中的列顺序与我显示的顺序相反可能会更快,您应该在自己的数据上尝试这两种方式并查看。