如何在PostgreSQL中更新大表

本文來源:www.codacy.com/blog/how-to…sql

在Postgres中更新大型表並不像看起來那樣簡單。若是您的表包含數億行,您將發現很難及時進行簡單的操做,例如添加列或更改列類型。數據庫

在不停機的狀況下進行這類操做是一個更大的挑戰。在這篇博客文章中,我將嘗試概述一些策略,以在管理大型數據集的同時最大程度地減小表不可用性。安全

通常準則

當您更新列中的值時,Postgres將在磁盤中寫入一個新行,棄用舊行,而後繼續更新全部索引。此過程等同於INSERT加上每一行後再DELETE,這會佔用大量資源。bash

除此以外,須要更新大表時還應瞭解的事項列表:併發

  • 從頭開始建立新表比更新每一行要快。順序寫比稀疏更新快,而且最後不會出現死行。
  • 表約束和索引嚴重延遲了每次寫入。若是可能,應在更新運行時刪除全部索引,觸發器和外鍵,並在最後從新建立它們。
  • 添加沒有默認值的可空列是一種廉價的操做。寫入列的實際數據是昂貴的部分。
  • 更新行時,不會重寫存儲在TOAST中的數據
  • 從Postgres 9.2開始,在某些數據類型之間進行轉換不須要重寫整個表。例如:從VARCHAR(32)轉換爲VARCHAR(64)。

考慮到這一點,讓咱們看一些能夠用來有效更新表中大量數據行的策略:ide

增量更新

若是您可使用例如順序ID對數據進行細分,則能夠批量更新行。因爲您只須要保持較短期的鎖定,所以能夠最大化表的可用性。若是添加新列,則能夠將其臨時設置爲可爲空,而後開始逐漸用新值填充它。post

這種方法的主要問題是性能,這是一個很是緩慢的過程,由於就地更新成本很高。在遷移期間,它可能還須要更復雜的應用程序邏輯。性能

建立一個新表

更新大表的最快方法是建立一個新表。優化

若是能夠安全地刪除現有表,而且有足夠的磁盤空間,則執行更新的最簡單方法是將數據插入到新表中,而後對其進行重命名。如下是此操做的基本執行腳本:spa

create table user_info_copy (LIKE user_info INCLUDING INDEXES INCLUDING COMMENTS);

INSERT INTO user_info_copy
SELECT user_no, idcard_no, real_name, bankcard_no, bind_mobile
     , false, bind_status, user_identity, create_time, creator
     , edit_time, editor, is_del, VERSION, customer_id
     , id_card_type, source_id, platform_no, one_passport_no, bank_code
FROM user_info;

drop TABLE user_info;

alter table user_info_copy rename to user_info;
複製代碼

從新建立現有表

若是因爲不想從新建立視圖或因爲其餘限制而不能刪除原始表,則可使用臨時表保存新值,截斷舊錶並在那裏重寫數據。當您有未決的寫請求時,此方法也有一些優勢,如咱們將在下一部分中看到的。

若是您的表能夠容納在內存中,則應在此事務期間增長temp_buffers屬性。使用RAM代替磁盤來存儲臨時表將明顯提升性能:

SET temp_buffers = 3000MB; ----相應地更改此值

# 建立臨時表
CREATE TABLE temp_user_info(  
   user_no BIGINT,  
   PRIMARY KEY( user_no )  
);
# 若是須要提速能夠從表中刪除索引
# 複製數據到臨時表中
insert into temp_user_info select user_no from user_info;

# 改變表結構,好比須要添加新列
TRUNCATE user_no;
# 執行插入列字段語句
# 再把數據反寫到user_info表
複製代碼

處理併發寫入

即便進行了上述優化,從新建立表仍然是緩慢的操做。若是您正在實時數據庫中運行查詢,則可能須要處理併發寫入請求。

最簡單的方法是在事務期間在表上強制使用SHARE LOCK, 語句以下

LOCK TABLE user_info IN SHARE MODE;
複製代碼

若是花費太長時間,全部寫請求將一直等到鎖釋放或超時爲止。若是未刪除原始表,則一旦事務結束,將執行未超時的請求。請注意,即便使用相同的名稱建立新表,請求仍將失敗,由於它們使用表OID

根據寫請求的性質,您還能夠建立自定義規則來存儲對錶所作的更改。例如,您能夠設置一個規則,以在開始數據遷移以前記錄已刪除的行:

CREATE RULE deleted_rule AS ON DELETE
TO tbl
DO INSERT INTO tbl_deletes VALUES
(
  OLD.id
);
複製代碼

遷移結束時,您只需從tbl_deletes中讀取ID,而後在新表上將其刪除。可使用相似的方法來處理其餘類型的請求。

結論

一旦達到必定大小,曾經瞬時的操做可能須要幾個小時來準備和執行。我的實驗結論:

  • 用存儲過程批量更新 560w , 1455秒結束
  • 用複製表更名方法操做 560w數據, 120秒左右就結束了;

點我拼團

點我抽獎

相關文章
相關標籤/搜索