sql – 用于查找外键不存在的记录的索引

table products
id primary_key

table transactions
product_id foreign_key references products

以下SQL查询非常慢:

SELECT products.* 
FROM   products 
       LEFT JOIN transactions 
              ON ( products.id = transactions.product_id ) 
WHERE  transactions.product_id IS NULL; 

在100亿条产品记录中,可能只有100条产品没有相应交易的记录.

这个查询非常慢,因为我怀疑它正在进行全表扫描以查找那些空的外键产品记录.

我想创建一个像这样的部分索引:

CREATE INDEX products_with_no_transactions_index 
ON (Left JOIN TABLE 
    BETWEEN products AND transactions) 
WHERE transactions.product_id IS NULL;

以上是可能的,我将如何去做?

注意:
该数据集的一些特征:

>交易永远不会被删除,只会被添加.
>产品永远不会被删除,而是以每分钟100次的速度添加(显然这是一个更为复杂的实际用例背后的示例).这些人的一小部分暂时成为孤儿
>我需要经常查询(每分钟最多一次)并且需要始终知道当前的孤立产品集是什么

最佳答案 我能想到的最好的是你在评论中的最后一个想法:a
materialized view.

CREATE MATERIALIZED VIEW orphaned_products AS
SELECT *
FROM   products p
WHERE  NOT EXISTS (SELECT 1 FROM transactions t WHERE t.product_id = p.id)

然后,您可以使用此表(物化视图只是一个表)作为使用孤立产品的查询中的大表产品的直接替代 – 显然对性能有很大影响(几百行而不是一亿行).物化视图需要Postgres 9.3,但这就是您根据评论使用的内容.您可以在早期版本中轻松实现它.

但是,物化视图是快照,不会动态更新. (无论如何,这可能会使任何性能优势无效.)要进行更新,请运行(昂贵的)操作:

REFRESH MATERIALIZED VIEW orphaned_products;

您可以在战略上适当的时间点执行此操作,并根据您的业务模型使多个后续查询从中受益.

当然,你会在orphaned_products.id上有一个索引,但对于几百行的小表来说这不是很重要.

如果您的模型是永远不会删除事务的模型,那么您可以利用它来产生很好的效果.手动创建一个类似的表:

CREATE TABLE orphaned_products2 AS
SELECT *
FROM   products p
WHERE  NOT EXISTS (SELECT 1 FROM transactions t WHERE t.product_id = p.id);

当然,您可以通过截断和重新填充它来刷新“物化视图”,就像第一个一样.但重点是避免昂贵的操作.你真正需要的是:

>将新产品添加到orphaned_products2.
使用trigger AFTER INSERT ON产品实施.
>只要表事务中出现引用行,就会从orphaned_products2中删除产品.
在更新product_id ON转换后使用触发器实现.只有你的模型允许transations.products_id更新 – 这将是一个非常规的事情.
另一个是在插入转换后.

所有相对便宜的操作.

>如果还可以删除事务,则需要另外一个触发器来在删除转换后添加孤立产品 – 这会有点贵.对于每个已删除的事务,您需要检查这是否是最后一个引用相关产品的事件,并在这种情况下添加一个孤儿.可能仍然比刷新整个物化视图便宜很多.

真空

在您获得其他信息之后,我还建议使用custom settings for aggressive vacuuming的orphaned_products2,因为它会产生大量死行.

点赞