两个 PostgreSQL 索引中哪个更高效?
Posted
技术标签:
【中文标题】两个 PostgreSQL 索引中哪个更高效?【英文标题】:Which of the two PostgreSQL indexes is more efficient? 【发布时间】:2015-11-01 17:57:10 【问题描述】:我有以下 PostgreSQL 架构:
CREATE TABLE User (
ID INTEGER PRIMARY KEY
);
CREATE TABLE BOX (
ID INTEGER PRIMARY KEY
);
CREATE SEQUENCE seq_item;
CREATE TABLE Item (
ID INTEGER PRIMARY KEY DEFAULT nextval('seq_item'),
SENDER INTEGER REFERENCES User(id),
RECEIVER INTEGER REFERENCES User(id),
INFO TEXT,
BOX_ID INTEGER REFERENCES Box(id) NOT NULL,
ARRIVAL TIMESTAMP
);
它的主要用例是典型的生产者/消费者场景。不同的用户可以在特定用户的特定框中插入数据库中的项目,并且每个用户可以检索发给她/他的框中的最顶部(这意味着最旧的)项目。它或多或少模仿了数据库级别的队列功能。
更准确地说,最常见的操作如下:
INSERT INTO ITEM(SENDER, RECEIVER, INFO, BOX_ID, ARRIVAL)
VALUES (nsid, nrid, ncontent, nqid, ntime);
并根据RECEIVER+SENDER
或RECEIVER+BOX_ID
的组合检索命令:
SELECT * INTO it FROM Item i WHERE (i.RECEIVER=? OR i.RECEIVER is NULL) AND
(i.BOX_ID=?) ORDER BY ARRIVAL LIMIT 1;
DELETE FROM Item i WHERE i.id=it.id;
和
SELECT * INTO it FROM Item i WHERE (i.RECEIVER=? OR i.RECEIVER is NULL) AND
(i.SENDER=?) ORDER BY ARRIVAL LIMIT 1;
DELETE FROM Item i WHERE i.id=it.id;
最后两个 sn-ps 打包在一个存储过程中。
我考虑过使用两个不同的索引。
1. CREATE INDEX ind ON item(arrival);
。上述SELECT
的EXPLAIN
计划如下:
Limit (cost=0.29..2.07 rows=1 width=35)
-> Index Scan using ind on item i (cost=0.29..3010.81 rows=1693 width=35)
Filter: (((receiver = 2) OR (receiver IS NULL)) AND (sender = 2))
据我了解,这种方法的优点是可以避免对数据进行排序。但是,据我了解,我仍然需要扫描整个表,但是访问将是随机的,这会减慢执行速度。我不确定是否会因为LIMIT 1
而在找到匹配项后立即停止执行,或者它会始终扫描整个表。
2.CREATE INDEX ind ON item(receiver, sender);
EXPLAIN
:
Limit (cost=512.23..512.23 rows=1 width=35)
-> Sort (cost=512.23..516.46 rows=1693 width=35)
Sort Key: arrival
-> Bitmap Heap Scan on message m (cost=42.37..503.76 rows=1693 width=35)
Recheck Cond: (((receiver = 2) AND (sender = 2)) OR ((receiver IS NULL) AND (sender = 2)))
-> BitmapOr (cost=42.37..42.37 rows=1693 width=0)
-> Bitmap Index Scan on ind (cost=0.00..37.22 rows=1693 width=0)
Index Cond: ((receiver = 2) AND (sender = 2))
-> Bitmap Index Scan on ind (cost=0.00..4.30 rows=1 width=0)
Index Cond: ((receiver IS NULL) AND (sender = 2))
在这种情况下,我可以有效地找到 receiver
和 sender
的匹配项,但我需要在之后对结果进行排序,这可能会很慢。
那么这两个选项中哪个更好,为什么?第一个的估计成本要低得多,但第二个似乎更“确定”。
【问题讨论】:
哪个更好取决于您的数据分布。如果您的查询几乎没有匹配项,那么order by
对性能的影响不大。如果他们有很多匹配项,那么就会有很大的不同。
在考虑索引之前,先考虑键。在我看来,sender,box 和 receiver,box 是候选键。 box 也可以是一个(你不能两次发送一个盒子?)发送者和接收者的可空性对我来说看起来很可疑;发件人可以在没有已知收件人的情况下发送一个盒子吗? (和/或反之亦然)
@wildplasser,很抱歉造成混淆,但该示例被混淆了。盒子旨在作为存储不同物品的容器。它们是静态的,很少更改,并且沿行不是唯一的。当接收者未知时,每个人都可以挑选相应的物品。
那么,box 是稳定且唯一的,可以不为空吗?为什么盒子不是唯一的?一个箱子能不能按时寄出?
一个盒子里可以放很多东西,因此它们不是唯一的。
【参考方案1】:
对于这个查询:
SELECT * INTO it
FROM Item i
WHERE (i.RECEIVER = ? OR i.RECEIVER is NULL) AND
(i.SENDER = ?)
ORDER BY ARRIVAL
LIMIT 1;
最好的索引可能是item(sender, arrival, receiver)
,按此顺序。这将按发送方过滤,然后使用索引进行排序,并再次按接收方过滤。
最快的方法可能是:
select *
from ((select i.*
from item i
where receiver = ? and sender = ?
order by arrival
limit 1
) union all
(select i.*
from item i
where receiver is null and sender = ?
order by arrival
limit 1
)
) i
order by arrival
limit 1;
此版本的最佳索引是item(sender, receiver, arrival)
。它将使用索引在每个子查询中获取(最多)一行。最后的排序(两行)可以忽略不计。
当然,同样的推理也适用于其他查询。
【讨论】:
非常感谢您的详尽解释!因此,您建议在item(sender, receiver, arrival)
和 item(box_id, receiver, arrival)
上编写两个单独的索引?另外,为什么两者的并集比单纯的(i.RECEIVER = ? OR i.RECEIVER is NULL) AND (i.SENDER = ?)
更快?此外,我们最终通过包含arrival
来实现什么?这会改善内部order by
吗?
SQL 优化器通常很难使用OR
(我不知道 Postgres 是否有更好的优化器)。末尾的arrival
代表order by
。以上是关于两个 PostgreSQL 索引中哪个更高效?的主要内容,如果未能解决你的问题,请参考以下文章
哪个 Postgresql 索引对于基于相似性查询的文本列最有效