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