本文來源:www.codacy.com/blog/how-to…sql
在Postgres中更新大型表並不像看起來那樣簡單。若是您的表包含數億行,您將發現很難及時進行簡單的操做,例如添加列或更改列類型。數據庫
在不停機的狀況下進行這類操做是一個更大的挑戰。在這篇博客文章中,我將嘗試概述一些策略,以在管理大型數據集的同時最大程度地減小表不可用性。安全
當您更新列中的值時,Postgres將在磁盤中寫入一個新行,棄用舊行,而後繼續更新全部索引。此過程等同於INSERT
加上每一行後再DELETE
,這會佔用大量資源。bash
除此以外,須要更新大表時還應瞭解的事項列表:併發
考慮到這一點,讓咱們看一些能夠用來有效更新表中大量數據行的策略: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,而後在新表上將其刪除。可使用相似的方法來處理其餘類型的請求。
一旦達到必定大小,曾經瞬時的操做可能須要幾個小時來準備和執行。我的實驗結論: