sib*_*baz 1 postgresql performance optimization query-performance
我有一个奇怪的问题,我真的不明白。简单地说,我有一个包含 4 个表连接的连接,我相信它们都有适当的索引,但是查询需要大量的时间,除非我删除它的一部分。
更大的图片是,有 3 种类型的对象 A、B 和 C,每个对象都有自己的表,并且相关联,A 是孩子,B 是父母,C 是祖父母。除此之外,还有一个关系表 R,允许多个 B 与多个 C 相关,并且由于关系 R 属于特定类型,因此还有一个附加表 T。
现在在有问题的查询中,我试图获取类型 A 的记录列表,谁的父母与祖父母有特定类型的关系,祖父母的名字 ILIKE 另一个字符串。
表A有~700k条记录,表B有~60k条记录,表C有~8k条记录,表R有~90k条记录,表T有~100条记录。
由于 A 包含链接到字段 B.id 的字段 parent_id,因此 B 不需要直接包含在查询中。
所以查询是这样的:
SELECT DISTINCT A.id, A.name
FROM A
JOIN R ON A.parent_id=R.lhs
JOIN T ON R.type=T.id AND T.alias='type-name'
JOIN C ON R.rhs=C.id
WHERE A.flag=1 AND A.strvalue='value' AND C.name ILIKE '%substr%'
ORDER BY A.name ASC
LIMIT 25;
Run Code Online (Sandbox Code Playgroud)
像这样运行查询需要超过 10 秒(我从来没有让它运行完成,因为它需要太长时间)。
在我的实际设置中,我在关键 ID 字段中有类型,所以查询实际上挂在类型中的一个字段上,但索引也会这样做。
奇怪的是我已经尝试从查询中取出位,因此尝试确定花费太长时间的位,并删除 T 部分或 ILIKE 部分似乎使其在正常时间范围内执行。
这是在我的原始查询上运行 EXAMINE 的结果(我已经替换了名称字段以适合上面的示例,但保留在类型引用中,所以希望它有意义,对于任何错别字深表歉意)
限制(成本=40296.05..40296.10 行=5 宽度=358)
-> 唯一(成本=40296.05..40296.10 行=5 宽度=358)
-> 排序(成本=40296.05..40296.06 行=5 宽度=358)
排序键:a.name、t.name、a.id
-> 嵌套循环(成本=277.06..40295.99 行=5 宽度=358)
加入过滤器:((r.lhs).id = (a.parent).id)
-> 嵌套循环(成本=277.06..3279.49 行=1 宽度=365)
加入过滤器:((r.rhs).id = c.id)
-> c 上的 Seq 扫描(成本 = 0.00..113.53 行 = 1 宽度 = 4)
过滤器:(名称 ~~* '%substr%'::text)
-> 嵌套循环(成本=277.06..3150.35 行=1249 宽度=456)
-> 在 t 上使用 t_alias 进行索引扫描(成本 = 0.00..8.27 行 = 1 宽度 = 278)
索引条件:((别名)::text = 'type-name'::text)
-> r 上的位图堆扫描(成本 = 277.06..2985.96 行 = 12490 宽度 = 186)
重新检查条件:(类型 = t.id)
-> 位图索引扫描 to_related_by (cost=0.00..273.94 rows=12490 width=0)
索引条件:(类型 = t.id)
-> Seq Scan on a (cost=0.00..36973.25 rows=3460 width=194)
过滤器:(((flag IS NULL) OR (NOT flag)) AND (((strvalue).id)::text = 'value'::text))
(19 行)
如果我创建一堆简化的示例表并将大量真实数据放入其中,这可能会更容易,但我希望有人可以指出我的愚蠢错误,而无需我这样做。
这是解释分析的结果(我让它运行,所以你可以看到它实际需要多长时间):-
限制(成本=40344.50..40344.51行=1宽度=358)(实际时间=2478498.373..2478498.414行=7循环=1)
-> Unique (cost=40344.50..40344.51 rows=1 width=358) (实际时间=2478498.369..2478498.394 rows=7 loops=1)
-> Sort (cost=40344.50..40344.51 rows=1 width=358) (实际时间=2478498.366..2478498.374 rows=7 loops=1)
排序键:a.name、t.name、a.id
排序方式:快速排序内存:25kB
-> Nested Loop (cost=274.74..40344.49 rows=1 width=358) (实际时间=1492181.642..2478498.230 rows=7 loops=1)
加入过滤器:((r.rhs).id = (a.parent).id)
-> Nested Loop (cost=274.74..3327.99 rows=1 width=365) (实际时间=96.682..1445.683 rows=4409 loops=1)
加入过滤器:((r.rhs).id = c.id)
-> c 上的 Seq Scan (cost=0.00..113.53 rows=1 width=4) (实际时间=0.143..10.569 rows=6 loops=1)
过滤器:(名称 ~~* '%substr%'::text)
-> Nested Loop (cost=274.74..3210.48 rows=319 width=456) (实际时间=3.961..185.316 rows=32112 loops=6)
-> 在 t 上使用 t_alias 进行索引扫描(cost=0.00..8.27 rows=1 width=278)(实际时间=0.020..0.025 rows=1 loops=6)
索引条件:((别名)::text = 'type-name'::text)
-> r 上的位图堆扫描(cost=274.74..3046.09 rows=12490 width=186)(实际时间=3.927..93.154 rows=32112 loops=6)
重新检查条件:(类型 = id)
过滤器:((lhs_app = 'collectible'::application) AND (rhs_app = 'realthing'::application))
-> Bitmap Index Scan on type (cost=0.00..273.94 rows=12490 width=0) (实际时间=3.527..3.527 rows=32112 loops=6)
索引条件:(类型 = r.id)
-> Seq Scan on a (cost=0.00..36973.25 rows=3460 width=194) (实际时间=220.577..561.750 rows=21 loops=4409)
过滤器:(((flag IS NULL) OR (NOT flag)) AND (((strvalue).id)::text = 'value'::text))
总运行时间:2478498.516 毫秒
(22 行)
我认为它可能会有所帮助,这是 A 的 \d 的修改版本:-
collector=> \d a
Table "public.a"
Column | Type | Modifiers
------------------------+-----------------------------+-----------------------------------------------------------------
idi | integer | not null default nextval('idi_seq'::regclass)
id | character(24) | not null
parent | mg_item |
strvalue | mg_item |
name | text |
flag | boolean |
Indexes:
"a_pkey" PRIMARY KEY, btree (idi)
"a_id_key" UNIQUE CONSTRAINT, btree (id)
"a_parent" btree (parent)
"a_flag" btree (flag)
"a_name" btree (name)
"a_strvalue" btree (strvalue)
Run Code Online (Sandbox Code Playgroud)
我看到了几个问题。
最大的一个是PG 在过滤 A 时对 A 使用序列扫描。我认为你需要一个关于 A.flag 和 A.strvalue 的复合索引。如果已经有可用的索引,PostgreSQL 出于某种原因选择不使用它。这似乎消耗了您的成本估算的 92%,并且可能是它运行这么长时间的原因。
至于 ILIKE,只要您的通配符是第一个字符,PostgreSQL 本身就不能(但请参阅下面的模块)使用索引。这只是对 ILIKE 运算符的限制。出于这个原因,您将获得序列扫描,这意味着正在加载每一行,并且正在扫描 C.name 列的字符。但奇怪的一件事是 ILIKE 序列扫描似乎并没有消耗掉这个查询计划中的大部分成本估计。无论如何,如果是 ILIKE 运算符导致速度变慢,我会考虑重写您的查询,使其看起来像这样: ILIKE 'value%' 或者考虑使用PostgreSQL's full text search。
更新
ILIKE 运算符可以使用三元组索引。高超!
| 归档时间: |
|
| 查看次数: |
5015 次 |
| 最近记录: |