摘要:數據庫分表,就是把一張表分成多張表,物理上雖然分開了,邏輯上彼此仍有聯系。分表有兩種方式:水平分表,即按列分開;垂直分表,即按行分開。分表可以大幅提升查詢速度;提高刪除數據的效率;可以將使用率低的數據通過表空間技術轉移到低成本的存儲介質上。分表的基本過程為:⑴創建父表;⑵定義一個觸發器函數,函數根據約束條件創建子表,子表需繼承自父表;⑶為父表創建觸發器。
一、垂直分表與水平分表
1. 垂直分表
垂直分表指的是:表的記錄並不多,但是字段卻很長,表占用空間很大,檢索表的時候需要執行大量的IO,嚴重降低了性能。這時需要把大的字段拆分到另一個表,並且該表與原表是一對一的關系。
例如學生答題表st:有如下字段:
Id name 分數 題目 回答
其中題目和回答是比較大的字段,id、name和分數比較小。
如果只想查詢id為8的學生的分數:select 分數 from st where id = 8;雖然只是查詢分數,但是題目和回答這兩個大字段也是要被掃描的,很消耗性能。但查詢時只關心分數,並不想查詢題目和回答。這就可以使用垂直分表。把題目單獨放到一張表中,通過id與st表建立一對一的關系,同樣將回答單獨放到一張表中。這樣查詢st中的分數時就不會掃描題目和回答了。
2. 水平分表
水平分表指的是:表的記錄很多,需要通過約束將數據分別存放至不同分表上,且分表與主表邏輯上相對應。
例:QQ的登錄表。假設QQ的用戶有100億,如果只有一張表,每個用戶登錄的時候數據庫都要從這100億中查找,會很慢很慢。如果將這一張表分成100份,每張表有1億條,就小了很多,比如qq0,qq1,qq1...qq99表。
用戶登錄的時候,可以將用戶的id%100,那么會得到0-99的數,查詢表的時候,將表名qq跟取模的數連接起來,就構建了表名。比如123456789用戶,取模的89,那么就到qq89表查詢,查詢的時間將會大大縮短。
二、觸發器
1. 定義:觸發器是數據庫的回調函數,它會在指定的數據庫事件發生時自動執行/調用。
2. 常見的觸發器類型:
l DML( 數據操縱語言 Data Manipulation Language)觸發器:是指觸發器在數據庫中發生DML事件時啟用。DML事件即指在表或視圖中修改數據的insert、update、delete語句。
l DDL(數據定義語言 Data Definition Language)觸發器:是指當服務器或數據庫中發生(DDL事件時啟用。DDL事件即指在表或索引中的create、alter、drop語句也。
3. 觸發器函數
當一個PL/pgSQL 函數作為一個觸發器被調用時,系統自動在最外層的塊創建一些特殊的變量。這些變量分別是:
1) New
數據類型是RECORD。對於行級觸發器,它存有INSERT或UPDATE操作產生的新的數據行。對於語句級觸發器,它的值是NULL。
2) OLD
數據類型是RECORD。對於行級觸發器,它存有被UPDATE或DELETE操作修改或刪除的舊的數據行。對於語句級觸發器,它的值是NULL。
3) TG_NAME
數據類型是name,它保存實際被調用的觸發器的名字。
4) TG_WHEN
數據類型是text,根據觸發器定義信息的不同,它的值是BEFORE 或AFTER。
5) TG_LEVEL
數據類型是text,根據觸發器定義信息的不同,它的值是ROW或STATEMENT。
6) TG_OP
數據類型是text,它的值是INSERT、UPDATE或DELETE,表示觸發觸發器的操作類型。
7) TG_RELID
數據類型是oid,表示觸發器作用的表的oid。
8) TG_RELNAME
數據類型是name,表示觸發器作用的表的名字。它與下面的變量TG_TABLE_NAME的作用是一樣的。
9) TG_TABLE_NAME
數據類型是name,表示觸發器作用的表的名字。
10) TG_TABLE_SCHEMA
數據類型是name,表示觸發器作用的表所在的模式。
11) TG_NARGS
數據類型是integer,表示CREATE TRIGGER命令傳給觸發器過程的參數的個數。
12) TG_ARGV[]
數據類型是text類型的數組。表示CREATE TRIGGER命令傳給觸發器過程的所有參數。下標從0開始。TG_ARGV[0]表示第一個參數,TG_ARGV[1]表示第二個參數,以此類推。 如果下標小於0或大於等於tg_nargs,將會返回一個空值。
三、數據庫按日期分表示例解析
1. 觸發器函數
CREATE OR REPLACE FUNCTION auto_insert_into_tbl_partition() RETURNS trigger AS $BODY$ DECLARE time_column_name text ; -- 父表中用於分區的時間字段的名稱[必須首先初始化!!] curMM varchar(8); -- 'YYYYMM'字串,用做分區子表的后綴 isExist boolean; -- 分區子表,是否已存在 startTime text; endTime text; strSQL text; BEGIN -- 調用前,必須首先初始化(時間字段名):time_column_name [直接從調用參數中獲取!!] time_column_name := TG_ARGV[0]; -- 判斷對應分區表 是否已經存在? EXECUTE 'SELECT $1.'||time_column_name INTO strSQL USING NEW; -- 為strSQL變量賦值,將NEW記錄中recordtime字段的值賦予strSQL變量 curMM := to_char( strSQL::timestamp , 'YYYYMMDD' ); -- 將文本格式的變量轉化為時間戳格式 select count(*) INTO isExist from pg_class where relname = (TG_RELNAME||'_'||curMM); -- pg_class 目錄 pg_class 記錄表和幾乎所有具有列或者像表的東西 -- 若不存在, 則插入前需 先創建子分區 IF ( isExist = false ) THEN -- 創建子分區表 startTime := curMM||' 00:00:00.000'; endTime := to_char( startTime::timestamp + interval '1 month', 'YYYY-MM-DD HH24:MI:SS.MS'); strSQL := 'CREATE TABLE IF NOT EXISTS '||TG_RELNAME||'_'||curMM|| ' ( CHECK('||time_column_name||'>='''|| startTime ||''' AND ' ||time_column_name||'< '''|| endTime ||''' )) INHERITS ('||TG_RELNAME||') ;' ; EXECUTE strSQL; -- 創建索引 strSQL := 'CREATE INDEX '||TG_RELNAME||'_'||curMM||'_INDEX_'||time_column_name||' ON ' ||TG_RELNAME||'_'||curMM||' ('||time_column_name||');' ; EXECUTE strSQL; END IF; -- 插入數據到子分區! strSQL := 'INSERT INTO '||TG_RELNAME||'_'||curMM||' SELECT $1.*' ; EXECUTE strSQL USING NEW; RETURN NULL; END $BODY$ LANGUAGE plpgsql;
2. 觸發器
CREATE TRIGGER insert_tbl_partition_trigger -- 創建觸發器 BEFORE INSERT -- 觸發事件為插入前 ON cdd_ais_table -- 為cdd_ais_table綁定觸發器 FOR EACH ROW -- 行觸發器 EXECUTE PROCEDURE auto_insert_into_tbl_partition('recordtime'); -- 執行觸發器函數,傳入字段為插入數據庫的時間戳類型字段