Aar*_*ron 8 postgresql postgresql-9.4
我有一张表progresses(目前包含数十万条记录):
Column | Type | Modifiers
---------------+-----------------------------+---------------------------------------------------------
id | integer | not null default nextval('progresses_id_seq'::regclass)
lesson_id | integer |
user_id | integer |
created_at | timestamp without time zone |
deleted_at | timestamp without time zone |
Indexes:
"progresses_pkey" PRIMARY KEY, btree (id)
"index_progresses_on_deleted_at" btree (deleted_at)
"index_progresses_on_lesson_id" btree (lesson_id)
"index_progresses_on_user_id" btree (user_id)
Run Code Online (Sandbox Code Playgroud)
和视图v_latest_progresses,其将查询最近progress的user_id和lesson_id:
SELECT DISTINCT ON (progresses.user_id, progresses.lesson_id)
progresses.id AS progress_id,
progresses.lesson_id,
progresses.user_id,
progresses.created_at,
progresses.deleted_at
FROM progresses
WHERE progresses.deleted_at IS NULL
ORDER BY progresses.user_id, progresses.lesson_id, progresses.created_at DESC;
Run Code Online (Sandbox Code Playgroud)
对于任何给定的课程,用户可以有许多进度,但我们经常想查询一组给定的用户或课程(或两者的组合)最近创建的一组进度。
v_latest_progresses当我指定一组user_ids时,视图可以很好地做到这一点,甚至是高性能的:
# EXPLAIN SELECT "v_latest_progresses".* FROM "v_latest_progresses" WHERE "v_latest_progresses"."user_id" IN ([the same list of ids given by the subquery in the second example below]);
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Unique (cost=526.68..528.66 rows=36 width=57)
-> Sort (cost=526.68..527.34 rows=265 width=57)
Sort Key: progresses.user_id, progresses.lesson_id, progresses.created_at
-> Index Scan using index_progresses_on_user_id on progresses (cost=0.47..516.01 rows=265 width=57)
Index Cond: (user_id = ANY ('{ [the above list of user ids] }'::integer[]))
Filter: (deleted_at IS NULL)
(6 rows)
Run Code Online (Sandbox Code Playgroud)
但是,如果我尝试执行相同的查询,用user_id子查询替换s集,它会变得非常低效:
# EXPLAIN SELECT "v_latest_progresses".* FROM "v_latest_progresses" WHERE "v_latest_progresses"."user_id" IN (SELECT "users"."id" FROM "users" WHERE "users"."company_id"=44);
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Merge Semi Join (cost=69879.08..72636.12 rows=19984 width=57)
Merge Cond: (progresses.user_id = users.id)
-> Unique (cost=69843.45..72100.80 rows=39969 width=57)
-> Sort (cost=69843.45..70595.90 rows=300980 width=57)
Sort Key: progresses.user_id, progresses.lesson_id, progresses.created_at
-> Seq Scan on progresses (cost=0.00..31136.31 rows=300980 width=57)
Filter: (deleted_at IS NULL)
-> Sort (cost=35.63..35.66 rows=10 width=4)
Sort Key: users.id
-> Index Scan using index_users_on_company_id on users (cost=0.42..35.46 rows=10 width=4)
Index Cond: (company_id = 44)
(11 rows)
Run Code Online (Sandbox Code Playgroud)
我想弄清楚的是为什么 PostgreSQL 想要在第二个示例中的子查询过滤之前DISTINCT对整个progresses表执行查询。
有人会对如何改进此查询有任何建议吗?
Chr*_*ris 11
亚伦,
在我最近的工作中,我一直在研究有关 PostgreSQL 的一些类似问题。PostgreSQL 几乎总是非常擅长生成正确的查询计划,但它并不总是完美的。
一些简单的建议是确保ANALYZE在您的progresses表上运行以确保您更新了统计信息,但这并不能保证解决您的问题!
由于这篇文章可能过于冗长的原因,我在统计收集ANALYZE和查询规划器中发现了一些可能需要长期解决的奇怪行为。在短期内,诀窍是重写您的查询以尝试修改您想要的查询计划。
在无法访问您的数据进行测试的情况下,我将提出以下两个可能的建议。
ARRAY()PostgreSQL 在其查询规划器中以不同的方式处理数组和记录集。有时你会得到一个相同的查询计划。在这种情况下,就像在我的许多情况下一样,您没有。
在您的原始查询中,您有:
EXPLAIN SELECT "v_latest_progresses".* FROM "v_latest_progresses"
WHERE "v_latest_progresses"."user_id"
IN (SELECT "users"."id" FROM "users" WHERE "users"."company_id"=44);
Run Code Online (Sandbox Code Playgroud)
作为尝试修复它的第一遍,请尝试
EXPLAIN SELECT "v_latest_progresses".* FROM "v_latest_progresses"
WHERE "v_latest_progresses"."user_id" =
ANY(ARRAY(SELECT "users"."id" FROM "users" WHERE "users"."company_id"=44));
Run Code Online (Sandbox Code Playgroud)
注意子查询从IN到的变化=ANY(ARRAY())。
另一个技巧是强制单独优化,如果我的第一个建议不起作用。我知道很多人使用这个技巧,因为CTE中的查询是与主查询分开优化和物化的。
EXPLAIN
WITH user_selection AS(
SELECT "users"."id" FROM "users" WHERE "users"."company_id"=44
)
SELECT "v_latest_progresses".* FROM "v_latest_progresses"
WHERE "v_latest_progresses"."user_id" =
ANY(ARRAY(SELECT "id" FROM user_selection));
Run Code Online (Sandbox Code Playgroud)
本质上,通过user_selection使用WITH子句创建 CTE ,您要求 PostgreSQL 对子查询执行单独的优化
SELECT "users"."id" FROM "users" WHERE "users"."company_id"=44
Run Code Online (Sandbox Code Playgroud)
然后实现这些结果。然后,我再次使用该=ANY(ARRAY())表达式尝试手动操作该计划。
在这些情况下,您可能不能只相信 的结果EXPLAIN,因为它已经认为它找到了成本最低的解决方案。确保运行一个EXPLAIN (ANALYZE,BUFFERS)...以找出它在时间和页面读取方面的真正成本。
| 归档时间: |
|
| 查看次数: |
1821 次 |
| 最近记录: |