我有一个非常简单的Postgres SELECT
查询,可以对两个表A
和B
进行操作,其中A
包含B
&#的外键39; b_id
字段中的主键:
SELECT
A.*, B.*
FROM
A
INNER JOIN
B
ON
B.id = A.b_id
WHERE
-- various conditions on A
A.x == 3
-- ...
AND B.source = 'example' --source is indexed
ORDER BY
A.created_at DESC NULLS LAST
LIMIT
20
字段x
(整数),created_at
(日期时间)和b_id
( UUID A
上的source
以及B
上的字符变化具有B树索引,b_id
具有外键约束B
的主键id
。 A
和B
的主键是随机UUID
s:
Table "A"
Column | Type | Modifiers
-------------+--------------------------+---------------
id | uuid | not null
x | integer |
b_id | uuid | not null
created_at | timestamp with time zone | default now()
Indexes:
"a_pkey" PRIMARY KEY, btree (id)
"ix_a_created_at" btree (created_at)
"ix_a_x" btree (x)
"ix_a_created_at_desc" btree (created_at DESC NULLS LAST)
Foreign-key constraints:
"a_b_id_fkey" FOREIGN KEY (b_id) REFERENCES b(id)
Table "B"
Column | Type | Modifiers
----------+--------------------------+------------------------
id | uuid | not null
source | character varying | not null
Indexes:
"b_pkey" PRIMARY KEY, btree (id)
"ix_b_source" btree (source)
Postgres生成并执行以下查询计划:
Limit (cost=0.72..4290.05 rows=20 width=122) (actual time=25801.196..25801.261 rows=20 loops=1)
-> Nested Loop (cost=0.72..14553904.10 rows=67861 width=122) (actual time=25801.195..25801.252 rows=20 loops=1)
-> Index Scan using a_created_at_desc on a (cost=0.43..6230611.59 rows=2200076 width=122) (actual time=0.038..21791.638 rows=2060427 loops=1)
Filter: ((x == 3)))
Rows Removed by Filter: 3799305
-> Index Scan using b_pkey on b (cost=0.29..3.77 rows=1 width=16) (actual time=0.002..0.002 rows=0 loops=2060427)
Index Cond: (id = a.b_id)
Filter: ((source)::text = 'example'::text)
Rows Removed by Filter: 1
Total runtime: 25801.311 ms
对我而言,这似乎很好,因为Postgres使用可用的索引进行过滤和排序。
然而,问题是我的数据非常严重":表A
由批处理填充,该批处理首先在B
中创建一行然后数百个A
中的数千个关联行。由于source
值在各批次之间不同,这意味着按创建日期排序A
会产生具有相同b_id
值的大块行。
所以问题 - 就我所知 - 当Postgres执行嵌套循环以从A
获取相关行时,它需要迭代数百万行才能击中那些行。实际上与查询相关,这使得执行非常慢(在具有数百万行的表上大于1秒)。如果我删除ORDER BY
子句,查询将在几毫秒内完成,这似乎证实了这一假设。
我有什么办法可以加快速度吗?