2011-02-03 70 views
4

我想利用PostgreSQL中的多列btree索引在两个表之间执行烦人的连接。PostgreSQL多列索引加入比较(“<" and ">”)运算符

   Table "revision_main" 
    Column  |   Type   | Modifiers 
----------------+------------------------+----------- 
revision_id | integer    | 
page_id  | integer    | 

Indexes: 
    "revision_main_pkey" UNIQUE, btree (revision_id) 
    "revision_main_cluster_idx" btree (page_id, "timestamp") CLUSTER 

此表包含修订版本(约300万行)到维基页面。我的表格中有更多的列,但我已经放弃了这个例子,因为它们应该没有关系。

   Table "revert" 
     Column  | Type | Modifiers 
--------------------+---------+----------- 
page_id   | integer | 
revision_id  | integer | 
reverted_to  | integer | 
Indexes: 
    "revert_page_between_idx" btree (page_id, reverted_to, revision_id) CLUSTER 

此表包含还原版本(约2200万行)。如果修订已被恢复,那么revision_id将在revision_main表中有一行,其revision_id将位于reverted_to和revision_id之间,并且共享相同的page_id。 (如果您好奇,请参阅http://en.wikipedia.org/wiki/Wikipedia:Revert)。

加入这两个表以恢复修订看起来很简单。这里是我想出:

explain SELECT 
    r.revision_id, 
    rvt.revision_id 
FROM revision_main r 
INNER JOIN revert rvt 
    ON r.page_id = rvt.page_id 
    AND r.revision_id > rvt.reverted_to 
    AND r.revision_id < rvt.revision_id; 
             QUERY PLAN            
---------------------------------------------------------------------------------------------------- 
Merge Join (cost=4202878.87..15927491478.57 rows=88418194298 width=8) 
    Merge Cond: (r.page_id = rvt.page_id) 
    Join Filter: ((r.revision_id > rvt.reverted_to) AND (r.revision_id < rvt.revision_id)) 
    -> Index Scan using revision_main_page_id_idx on revision_main r (cost=0.00..9740790.61 rows=223163392 width=8) 
    -> Materialize (cost=4201592.06..4536465.21 rows=26789852 width=12) 
     -> Sort (cost=4201592.06..4268566.69 rows=26789852 width=12) 
       Sort Key: rvt.page_id 
       -> Seq Scan on revert rvt (cost=0.00..438534.52 rows=26789852 width=12) 

即使在撤销的聚集索引应该是一个B树索引(从而支持运营商比较喜欢“<”和“>”),查询优化器不使用该指数进行加入,“解释”预测总成本超过150亿(可能会在明年完成)。

比较运算符是否不可用于多列(btree)索引?我只是做错了吗?

回答

5

看起来优化器比你更了解自己的工作。

如果您选择的表格不止一小部分(硬件相关的部分,比方说5%),那么选择和排序整个表格比使用索引更快。如果你只是选择了几行,那么它应该使用索引。所以它会为您提供正确的数据查询计划。

至于总成本,这些数字都是BS,并且仅在单个查询内相互比较时才有用。 (由两个非常相似的查询产生的总成本可能大不相同)执行时间和查询成本几乎不相关。

+0

我可以看到刚刚排序整个表可能会快于使用索引,但根据我的经验,成本估计往往是执行时间的一致反映。另一方面,我从来没有真正知道这些数字是什么意思,所以我会承认你的理解。你是否建议我应该运行查询并忽略数字? – halfak 2011-02-03 22:09:12

+0

@halfak:让我仔细看看。数据库喜欢用小表开始连接。如果您将(page_id,revision_id)上的索引添加到revision_main中,您可能会得到更有效的查询,这可能是可能的。它也可能更糟。但是,如果失败了,那么让它变得更加高效的唯一方法就是找到一种方法来减少数据量。 “ – btilly 2011-02-03 22:46:32

0

您的查询(基于SQL)看起来需要读取整个还原表,并为还原表中的每一行找到适当的修订行。

由于需要读取整个回复表,因此需要对其进行顺序扫描。它似乎期望大致正确的行数。

然后,每个恢复行将匹配多个修订版,它认为这将通过索引扫描和合并连接最好地完成。据估计,平均而言,每个恢复行大致匹配3300个修订版,从而产生880亿行。

我不知道有什么方法可以快速选择88亿行。

为了获得更准确的估计值,您需要一种说服PostgreSQL的方法,即每个回复所涵盖的修订量少于3300个。

你说你是在恢复修订后,表明每个修订只应出现一次,即使包含在多个恢复中也是如此。

所以尽量使用​​,而不是一个INNER JOIN

这不会给你虽然复归修订:

EXPLAIN 
SELECT 
    r.revision_id 
FROM revision_main r 
WHERE EXISTS (SELECT 1 FROM revert rvt 
    WHERE r.page_id = rvt.page_id 
    AND r.revision_id > rvt.reverted_to 
    AND r.revision_id < rvt.revision_id);