postgresql - 强制 Postgresql 使用 Merge Append
问题描述
假设我有以下表格和索引:
create table inbound_messages(id int, user_id int, received_at timestamp);
create table outbound_messages(id int, user_id int, sent_at timestamp);
create index on inbound_messages(user_id, received_at);
create index on outbound_messages(user_id, sent_at);
现在我想为用户提取最后 20 条消息,无论是在特定时间范围内的入站消息还是出站消息。我可以执行以下操作,从解释来看,PG 似乎以“并行”方式返回两个索引,因此它最大限度地减少了需要扫描的行数。
explain select * from (select id, user_id, received_at as time from inbound_messages union all select id, user_id, sent_at as time from outbound_messages) x where user_id = 5 and time between '2018-01-01' and '2020-01-01' order by user_id,time desc limit 20;
Limit (cost=0.32..16.37 rows=2 width=16)
-> Merge Append (cost=0.32..16.37 rows=2 width=16)
Sort Key: inbound_messages.received_at DESC
-> Index Scan Backward using inbound_messages_user_id_received_at_idx on inbound_messages (cost=0.15..8.17 rows=1 width=16)
Index Cond: ((user_id = 5) AND (received_at >= '2018-01-01 00:00:00'::timestamp without time zone) AND (received_at <= '2020-01-01 00:00:00'::timestamp without time zone))
-> Index Scan Backward using outbound_messages_user_id_sent_at_idx on outbound_messages (cost=0.15..8.17 rows=1 width=16)
Index Cond: ((user_id = 5) AND (sent_at >= '2018-01-01 00:00:00'::timestamp without time zone) AND (sent_at <= '2020-01-01 00:00:00'::timestamp without time zone))
例如,它可以做一些疯狂的事情,比如在内存中找到所有匹配的行,然后对这些行进行排序。假设有数百万个匹配行,那么这可能需要很长时间。但是因为它以相同的顺序遍历索引,所以我们希望结果是一个快速的操作。看起来“合并追加”操作是懒惰地完成的,它实际上并没有实现所有匹配的行。
现在我们可以看到 postgres 支持对两个不同的表进行此操作,但是是否可以强制 Postgres 对单个表使用此优化。
可以说我想要最后 20 个inbound messages
用于user_id = 5
or user_id = 6
。
explain select * from inbound_messages where user_id in (6,7) order by received_at desc limit 20;
然后我们得到一个执行位图堆扫描的查询计划,然后执行内存排序。因此,如果有数百万条消息匹配,那么它将查看数百万行,即使理论上它可以使用相同的合并技巧只查看几行。
Limit (cost=15.04..15.09 rows=18 width=16)
-> Sort (cost=15.04..15.09 rows=18 width=16)
Sort Key: received_at DESC
-> Bitmap Heap Scan on inbound_messages (cost=4.44..14.67 rows=18 width=16)
Recheck Cond: (user_id = ANY ('{6,7}'::integer[]))
-> Bitmap Index Scan on inbound_messages_user_id_received_at_idx (cost=0.00..4.44 rows=18 width=0)
Index Cond: (user_id = ANY ('{6,7}'::integer[]))
我们可以考虑(received_at)
在表上添加一个索引,然后它会执行相同的反向扫描。但是,如果我们有大量用户,那么我们就会错过潜在的大幅加速,因为我们正在扫描大量与查询不匹配的索引条目。
解决方案
推荐阅读
- c# - ChangeLog OriginalValue 与 EF Core 更新实体上的 CurrentValue 相同
- mongodb - 使用 MongoDB 聚合获取组中的百分比
- c# - 当属性可以为空时,@Html.DisplayNameForModel() 不起作用
- php - PHP:如何将 in_array 与正则表达式字符串数组一起使用?
- typescript - 将泛型值推送到 Typescript 中泛型类中的泛型列表
- javascript - 在 html 中的 onclick 方法上将 Rails 对象值设置为 true
- aws-cdk - AWS CDK 代码管道如何部署到 Elastic Beanstalk?
- javascript - 如何使点击事件仅在名称上有效?
- c# - 使用 LINQ 循环遍历数据并在表格中显示
- mysql - 如何强制mariaDB使用索引?