摘要:数据库分表,就是把一张表分成多张表,物理上虽然分开了,逻辑上彼此仍有联系。分表有两种方式:水平分表,即按列分开;垂直分表,即按行分开。分表可以大幅提升查询速度;提高删除数据的效率;可以将使用率低的数据通过表空间技术转移到低成本的存储介质上。分表的基本过程为:⑴创建父表;⑵定义一个触发器函数,函数根据约束条件创建子表,子表需继承自父表;⑶为父表创建触发器。
一、垂直分表与水平分表
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'); -- 执行触发器函数,传入字段为插入数据库的时间戳类型字段