Has*_*aig 4 postgresql performance upgrade postgresql-9.6 query-performance
我有一个名为“链接”的应用程序,其中 1) 用户聚集在群组中并添加其他人,2) 在上述群组中为彼此发布内容。组由links_group
我的 postgresql 9.6.5 DB 中的表定义,而他们在这些中发布的回复由links_reply
表定义。总体而言,DB 的性能非常好。
然而SELECT
,links_reply
表上的一个查询始终显示在slow_log 中。它花费的时间超过 500 毫秒,并且比我在大多数其他 postgresql 操作中遇到的速度慢约 10 倍。
我使用 Django ORM 来生成查询。这里的ORM电话:replies = Reply.objects.select_related('writer__userprofile').filter(which_group=group).order_by('-submitted_on')[:25]
。本质上,这是为给定的组对象选择最新的 25 条回复。它还选择关联user
和userprofile
对象。
这是我的慢日志中相应 SQL 的示例:LOG: duration: 8476.309 ms 语句:
SELECT
"links_reply"."id", "links_reply"."text",
"links_reply"."which_group_id", "links_reply"."writer_id",
"links_reply"."submitted_on", "links_reply"."image",
"links_reply"."device", "links_reply"."category",
"auth_user"."id", "auth_user"."username",
"links_userprofile"."id", "links_userprofile"."user_id",
"links_userprofile"."score", "links_userprofile"."avatar"
FROM
"links_reply"
INNER JOIN "auth_user"
ON ("links_reply"."writer_id" = "auth_user"."id")
LEFT OUTER JOIN "links_userprofile"
ON ("auth_user"."id" = "links_userprofile"."user_id")
WHERE "links_reply"."which_group_id" = 124479
ORDER BY "links_reply"."submitted_on" DESC
LIMIT 25
Run Code Online (Sandbox Code Playgroud)
看看这里的解释分析结果:https : //explain.depesz.com/s/G4X索引扫描(向后)似乎一直在吃。
这是输出\d links_reply
:
Table "public.links_reply"
Column | Type | Modifiers
----------------+--------------------------+----------------------------------------------------------
id | integer | not null default nextval('links_reply_id_seq'::regclass)
text | text | not null
which_group_id | integer | not null
writer_id | integer | not null
submitted_on | timestamp with time zone | not null
image | character varying(100) |
category | character varying(15) | not null
device | character varying(10) | default '1'::character varying
Indexes:
"links_reply_pkey" PRIMARY KEY, btree (id)
"category_index" btree (category)
"links_reply_submitted_on" btree (submitted_on)
"links_reply_which_group_id" btree (which_group_id)
"links_reply_writer_id" btree (writer_id)
"text_index" btree (text)
Foreign-key constraints:
"links_reply_which_group_id_fkey" FOREIGN KEY (which_group_id) REFERENCES links_group(id) DEFERRABLE INITIALLY DEFERRED
"links_reply_writer_id_fkey" FOREIGN KEY (writer_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
Referenced by:
TABLE "links_groupseen" CONSTRAINT "links_groupseen_which_reply_id_fkey" FOREIGN KEY (which_reply_id) REFERENCES links_reply(id) DEFERRABLE INITIALLY DEFERRED
TABLE "links_report" CONSTRAINT "links_report_which_reply_id_fkey" FOREIGN KEY (which_reply_id) REFERENCES links_reply(id) DEFERRABLE INITIALLY DEFERRED
Run Code Online (Sandbox Code Playgroud)
这是一个大表(约 2500 万行)。它运行的硬件有 16 个内核和 60 GB 内存。它与一个 python 应用程序共享这台机器。但我一直在监控服务器的性能,我没有看到那里的瓶颈。
有什么办法可以提高这个查询的性能吗?请就我在此处提供的所有选项(如果有)提出建议。
请注意,这个查询直到上周都表现得非常好。从那以后发生了什么变化?我进行了pg_dump
,然后pg_restore
将DB(在一个单独的VM),并且我升级和PostgreSQL 9.3.10至9.6.5。我还使用了一个pgbouncer
之前调用的连接池,我还没有在我迁移到的新 VM 上配置它。就是这样。
最后,我还注意到(从用户体验中)对于直到上周创建的所有组对象,查询仍然执行得很快。但是现在创建的所有新对象都在生成慢日志。这可能是某种索引问题,特别是索引问题links_reply_submitted_on
吗?
更新:规定的优化确实扭转了局面。看一看:
您需要ANALYZE
在使用pg_upgrade
. 不复制表统计信息。也可以调整 autovacuum 设置。
在多列索引(which_group_id, submitted_on DESC)
应该成为这个查询多好。
没有噪音的格式化查询并带有表别名以提高可读性:
SELECT lr.id, lr.text, lr.which_group_id, lr.writer_id
, lr.submitted_on, lr.image, lr.device, lr.category
, au.id, au.username
, lu.id, lu.user_id, lu.score, lu.avatar
FROM links_reply lr
JOIN auth_user au ON au.id = lr.writer_id
LEFT JOIN links_userprofile lu ON lu.user_id = au.id
WHERE lr.which_group_id = 119287
ORDER BY lr.submitted_on DESC
LIMIT 25;
Run Code Online (Sandbox Code Playgroud)
我认为查询本身没有任何问题。
(我不这么认为。)
这可能是某种索引问题吗?
如果您怀疑腐败,请运行REINDEX
。手册建议:
如果您怀疑用户表上的索引损坏,您可以简单地重建该索引或表上的所有索引,使用
REINDEX INDEX
或REINDEX TABLE
。
在并发访问的情况下:锁定在几个方面不同于从头开始删除和重新创建索引。手册:
REINDEX
类似于索引的删除和重新创建,因为索引内容是从头开始重建的。然而,锁定的考虑是相当不同的。REINDEX
锁定索引的父表的写入但不读取。它还对正在处理的特定索引进行排他锁,这将阻止尝试使用该索引的读取。相比之下,DROP INDEX
暂时在父表上使用排他锁,阻止写入和读取。后续CREATE INDEX
锁定写入但不锁定读取;由于索引不存在,没有读取会尝试使用它,这意味着不会有阻塞,但读取可能会被迫进行昂贵的顺序扫描。
如果并发操作仍然存在问题,请考虑CREATE INDEX CONCURRENTLY
创建新的重复索引,然后在单独的事务中删除旧索引。
但是,看起来表统计信息才是真正的问题。引用您的查询计划:
使用 links_reply_submitted_on on links_reply 向后扫描索引 (成本=0.44..1,664,030.07行=2,001宽度=50) (实际时间=522.811..716.414行=25循环=1) 过滤器:(which_group_id = 119287) 过滤器删除的行:1721320
大胆强调我的。看起来 Postgres 将这个查询计划建立在误导性统计数据的基础上。它期望更多的命中并且可能也低估了谓词的选择性which_group_id = 119287
。最终过滤了 170 万行。这散发着不准确的表格统计信息。还有一个可能的解释:
升级主要版本时pg_upgrade
不会将现有统计信息复制到新版本的数据库中。建议运行VACUUM ANALYZE
或至少ANALYZE
在pg_upgrade
. 该工具甚至会提示提醒您。手册:
由于优化器统计信息不会由 传输
pg_upgrade
,您将被指示运行命令以在升级结束时重新生成该信息。您可能需要设置连接参数以匹配您的新集群。
如果不这样做,表将没有当前的统计信息,直到自动清理被足够的表写入触发(或其他一些实用程序命令,例如CREATE INDEX
或动态ALTER TABLE
更新一些统计信息)。
对于任何转储/恢复周期(在您的情况下使用pg_dump
& )pg_restore
也是如此。转储中不包括表统计信息。
你的桌子很大(~25M rows)
。autovacuum 的默认设置将阈值定义为 row_count 加上固定偏移量的百分比。有时这对大表不起作用,下一次自动分析需要相当长的时间。
ANALYZE
在表或整个数据库上运行手册。
有关的:
...索引问题,特别是
links_reply_submitted_on
索引?
是的,那也是。索引"links_reply_submitted_on" btree (submitted_on)
未针对查询中的模式进行优化:
SELECT ...
FROM links_reply lr
JOIN ...
WHERE lr.which_group_id = 119287
ORDER BY lr.submitted_on DESC
LIMIT 25
Run Code Online (Sandbox Code Playgroud)
就像我们在上面的查询计划中看到的那样,Postgres 使用索引扫描,从底部读取索引并过滤不匹配的内容。如果which_group_id
最近选择的所有(很少!)有 25 行,则此方法可能相当快。但是对于行的不均匀分布或 的许多不同值,它不会很好地工作which_group_id
。
这个多列索引更适合:
links_reply__which_group_id__submitted_on btree (which_group_id, submitted_on DESC)
Run Code Online (Sandbox Code Playgroud)
现在,which_group_id
无论数据分布如何,Postgres 都可以为选定的 选择前 25 行。
有关的:
关于你的观察:
最后,我还注意到(从用户体验中)对于直到上周创建的所有组对象,查询仍然执行得很快。但是现在创建的所有新对象都在生成缓慢的日志。
为什么?新对象可能还没有25 个条目,因此 Postgres 必须不断扫描整个大索引,希望能找到更多。虽然这对于您的旧索引和查询计划来说非常昂贵,但对于新索引(和更新的表统计信息)来说同样非常便宜。
此外,有了准确的表统计信息,Postgres 很可能会使用您的其他索引"links_reply_which_group_id" btree (which_group_id)
来快速获取少数现有行(如果超过 25 行,则进行排序)。但无论如何,我的新索引提供了更可靠的查询计划。
您还可以做其他各种(次要的)事情,例如优化表格布局或调整 autovacuum 设置,但是这个答案已经足够长了。有关的:
你后来评论说:
还删除了无关字段...
仅检索您实际需要的列当然有帮助。这样做,另外。但这不是这里的主要问题。
归档时间: |
|
查看次数: |
10128 次 |
最近记录: |