两个 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+SENDERRECEIVER+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);。上述SELECTEXPLAIN 计划如下:

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))

在这种情况下,我可以有效地找到 receiversender 的匹配项,但我需要在之后对结果进行排序,这可能会很慢。

那么这两个选项中哪个更好,为什么?第一个的估计成本要低得多,但第二个似乎更“确定”。

【问题讨论】:

哪个更好取决于您的数据分布。如果您的查询几乎没有匹配项,那么 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 索引对于基于相似性查询的文本列最有效

c++中for循环和switch语句哪个更高效

SQL里if语句和case语句有啥区别吗?哪个使用更高效?就是查询更优化?

PostgreSQL 14新特性--减少索引膨胀

PostgreSQL 多列索引,包括数组

机器学习时代的哈希算法,将如何更高效地索引数据