PostgreSQL數據庫分表原理


摘要數據庫分表,就是把一張表分成多張表,物理上雖然分開了,邏輯上彼此仍有聯系。分表有兩種方式:水平分表,即按列分開;垂直分表,即按行分開。分表可以大幅提升查詢速度;提高刪除數據的效率;可以將使用率低的數據通過表空間技術轉移到低成本的存儲介質上。分表的基本過程為:⑴創建父表;⑵定義一個觸發器函數,函數根據約束條件創建子表,子表需繼承自父表;⑶為父表創建觸發器。

 

一、垂直分表與水平分表

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. 常見的觸發器類型:

DML( 數據操縱語言 Data Manipulation Language)觸發器:是指觸發器在數據庫中發生DML事件時啟用。DML事件即指在表或視圖中修改數據的insert、update、delete語句。

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');
-- 執行觸發器函數,傳入字段為插入數據庫的時間戳類型字段

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM