問題的提出
在項目中,有些表的記錄增長非常快,記錄數過大時會使得查詢變得困難,導緻整個資料庫處理性能下降。此時,我們會考慮按一定的規則進行分表存儲。
常用的分表方式是按時間周期,如每月一張,每天一張等。當每月或每天首條記錄到達時,根據表結建構立該周期為字尾的表進行存儲。
相關考慮
這其中主要考慮兩個問題:
(1)如何複制表
采用分表機制,通常會建立一個模闆表。所謂模闆表,是隻定義結構不存儲資料的,也可稱之為類表,而分表,通常會以增加字尾的方式命名,如 log_201901,分表實際存儲資料,可稱之為執行個體表。
表存在關聯、鍵、索引、限制等,這讓表的複制聽起來比較繁瑣,即便通過中繼資料得到這些資訊,還需要自己考慮如索引的命名沖突等問題。而 PostgreSQL 為我們提供了極其便捷的方式。
CREATE TABLE [IF NOT EXISTS] 執行個體表 (LIKE 模闆表 [INCLUDING ALL]);
其中 [ ] 内表示可選項,INCLUDING 除了 ALL 還要其它細分的選項,具體可參考幫助文檔。
(2)資料存儲的邏輯過程
首先得知道表存不存在,不存在則要建立,然後執行資料操作語句。
由于表名是動态的,在應用系統中可以先取得表名形成 SQL 語句再執行,在資料庫存儲過程中則可以使用 EXECUTE 執行動态SQL語句。
分表執行個體
下邊,本文以日志記錄表為例來完整地實踐分表處理過程。
功能描述:日志數量大,目前日志查詢頻繁,曆史日志需要全部儲存。要求每天一個分表,日志主鍵要求全局保持唯一性(即多個分表間不重複),日志到達自動根據目前的時間進行分表存儲。
首先建立日志模闆表,命名為 log_template,并為其建立相關索引,主鍵序列。
-- 建立模闆表,log_id 主鍵,log_at 日志時間, log_content 日志内容
CREATE TABLE log_template (log_id bigint PRIMARY KEY, log_at timestamp, log_content varchar(1000));
-- 對日志時間索引
CREATE INDEX idx_log_at on log_template (log_at);
-- 用于主鍵的序列(各分表使用同一序列)
CREATE SEQUENCE seq_log_id;
我們通過一個過程來完成日志的自動分表存儲。
CREATE OR REPLACE FUNCTION func_log(v_conent varchar) RETURNS bool LANGUAGE 'plpgsql'
AS $$
DECLARE
lv_log_at timestamp := current_timestamp;
lv_suffix_tname varchar; -- 帶字尾的分表名
lv_dsql text; -- 動态SQL
BEGIN
-- 根據時間得到應使用的分表名稱
lv_suffix_tname := 'log_' || to_char(lv_log_at, 'YYYYMMDD');
-- 判斷是否存在,不存在時複制模闆建立分表
lv_dsql := 'CREATE TABLE IF NOT EXISTS ' || lv_suffix_tname || ' (LIKE log_template INCLUDING ALL)';
EXECUTE lv_dsql;
-- 将資料儲存至分表
lv_dsql := 'INSERT INTO ' || lv_suffix_tname || '(log_id, log_at, log_content) VALUES($1, $2, $3)';
EXECUTE lv_dsql USING nextval('seq_log_id'), lv_log_at, v_conent;
RETURN true;
END $$;
執行以下語句來看看預期的結果。
SELECT func_log('hello, the first log!');
SELECT func_log('toady is a nice day!');
SELECT func_log('每天都有新的開始,不再擔心爆表!');
結束語
分表能夠避免單表記錄過于龐大,提高查詢性能。但同時,分表也會給部分查詢或資料處理帶有複雜性,是以是否分表應該根據業務需要來,同時應盡早規劃,後期更改相對繁瑣。
在 MySQL 中也有類似的 CREATE TABLE LIKE 文法,我想都是應運而生,簡單就是美。