2017-10-09 25 views
0

我有一個現有的表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。這會導致另一個問題:如果併發事務在insert/select正在運行時通過觸發器插入/更新item_documents,則該行由於唯一約束而被鎖定(這也會導致死鎖)。同樣,因爲insert/select鎖定了item_documents表中的行,所以它將阻止任何併發事務運行其觸發器。這是特別痛苦的,因爲insert/selectitem_documents需要至少一分鐘運行(並且有很多併發事務)。

我的下一個解決方案是首先添加觸發器,但以較小的批次執行insert/select並隨着時間的推移將其分散開。我可以承擔額外的時間,因爲使用item_documents表的功能在回填完成之前未被使用。我的想法是,鎖定item_documents只保留到批次完成。

這是確保表與減少鎖定同步的正確解決方案嗎?

回答

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;