我怎样才能回填这张桌子?

时间:2017-10-09 18:53:44

标签: postgresql performance

我有一个现有的表items

create table items (
    id serial primary key,
    name text
    -- ... and other columns that aren't of relevance.
);

我想创建另一个名为item_documents的表:

create table item_documents (
    id serial primary key,
    item_id integer unique foreign key items(id) on delete cascade,
    document tsvector
    -- ... and other computed columns.
);

item_documents表是从items表计算的。每次插入,更新或删除items表时,都应重新计算相应的item_documents记录。为了实现这一点,我将最终在items表上创建一个触发器来重新计算插入/更新上的item_documents(这是我完成此迁移后的目标)。

我面临的主要问题是我想回填item_documents表。 items表非常大。我想过只做insert/select

insert into item_documents (item_id, document, ...)
select id, compute_document(id, name, ...), ... from items

这有一个明显的问题:如果并发事务插入/更新items表,则item_documents中不会有相应的行。

我的下一个解决方案是在执行insert/select之前添加触发器。这导致了另一个问题:如果并发事务在item_documents运行时通过触发器插入/更新insert/select,则该行由于唯一约束而被锁定(这也可能导致死锁)。同样,因为insert/select锁定item_documents表中的行,它将阻止任何并发事务运行其触发器。这是特别痛苦的,因为insert/selectitem_documents至少需要一分钟才能运行(并且有很多并发事务)。

我的下一个解决方案是首先添加触发器,但是在较小的批次中执行insert/select并随着时间的推移将它们展开。我可以负担额外的时间,因为在回填完成之前,不使用使用item_documents表的功能。我的想法是item_documents上的锁只保留到批处理完成之前。

这是确保表与减少锁定同步的正确解决方案吗?

1 个答案:

答案 0 :(得分:1)

是的,为避免长时间的交易,您需要进行某种批处理。

我会使用此查询作为更新的基础:

SELECT id
FROM items
LEFT JOIN item_documents d ON d.item_id = items.id
WHERE d.item_id IS NULL
LIMIT 10

然后,对于此队列中的每个项目,运行compute_document函数并填充item_documents

实际上,这在单个PostgreSQL语句中是可行的:

-- repeat this until done:
INSERT INTO item_documents (item_id, document)
SELECT items.id, compute_document(...)
FROM items
LEFT JOIN item_documents AS d ON d.item_id = items.id
WHERE d.item_id IS NULL -- Process all items without documents,
LIMIT 10                -- but only 10 at a time, to avoid locking;

请记住在两个表中的相关列(item_idid)上创建基本索引。

作为替代方案,您可以使用布尔标志来指示处理数据。

ALTER TABLE items ADD is_processed boolean; --nulls!
CREATE INDEX items_todo ON items (id) WHERE is_processed IS DISTINCT FROM true;

-- repeat this until done:
WITH workitem AS (
  INSERT INTO item_documents (item_id, document)
  SELECT items.id, compute_document(...)
  FROM items 
  WHERE is_processed IS DISTINCT FROM true
  LIMIT 10
  RETURNING item_id 
)
UPDATE items
  SET is_processed = true
FROM workitems
WHERE workitems.item_id = items.id;