Klustron触发器介绍和案例测试
Klustron触发器介绍和案例测试
注意:
如无特别说明,文中的版本号可以使用任何已发布版本的版本号代替。所有已发布版本详见:Release notes
本文目标:
主要内容是首先介绍触发器的概述,介绍基于行级别(FOR EACH ROW)和语句级别(FOR EACH STATEMENT)上触发器,介绍创建触发器的基本语句和注意事项,然后测试基于FOR EACH ROW的触发器的测试案例和测试基于FOR EACH STATEMENT的触发器的测试案例。
01 触发器介绍
首先得提一下触发器函数,触发器函数与用户自定义函数类似,也是用户自定义的函数,不同的是触发器函数不需要在SQL中显示调用执行,而是由数据库系统在特定事件发生时自动去执行的(例如表/视图上的增删改等)。因此,用户的在创建触发器函数这一特殊函数时,除了定义函数体之外,还需要指定该函数的执行时机,即指定哪些事件(增/删/该)发生在哪个对象(表/视图)之前/后时执行触发器函数,这样以来,一个完整的触发器就创建出来了。
所以触发器共包含两部分内容:1、触发器函数;2、触发器的定义(即定义触发器函数的执行时机)。
在Klustron中,触发器(Triggers)是用于在数据库中特定的操作(如插入、更新、删除)发生时自动执行的特殊函数。触发器可以在行级别(FOR EACH ROW)或语句级别(FOR EACH STATEMENT)上触发,这取决于触发器的定义。
以下是FOR EACH ROW和FOR EACH STATEMENT触发器的区别:
1、FOR EACH ROW 触发器:
- FOR EACH ROW 触发器是在每行受影响时触发的。也就是说,当执行的操作影响到表中的每一行时,触发器会被执行。
- FOR EACH ROW 触发器可以访问并操作正在被修改的行的数据,因为它是在每行之间触发的。
- FOR EACH ROW 触发器通常用于需要对每个受影响的行执行特定操作的情况,例如在更新某一行时更新其他相关行。
2、FOR EACH STATEMENT触发器:
- FOR EACH STATEMENT触发器是在执行语句完成后触发的,而不是在每行之间触发。
- FOR EACH STATEMENT触发器不能直接访问或操作正在被修改的行的数据,因为它是在语句级别上触发的。
- FOR EACH STATEMENT触发器通常用于需要对整个语句的结果执行操作的情况,例如在插入大批量数据时进行一些汇总计算或日志记录。
在编写触发器时,需要根据具体的需求和操作确定是使用 FOR EACH ROW 还是FOR EACH STATEMENT触发器。如果需要对每行进行个别操作或需要访问被修改的行数据,应选择FOR EACH ROW触发器。如果只需要对整个语句的结果进行操作,而不需要访问具体的行数据,应选择FOR EACH STATEMENT触发器。
02 触发器创建语法
更具前面的介绍,我们可以已经触发器函数的与普通函数的区别:借助预定义的变量名来引用上下文中数据变化信息;特殊的返回值类型。除此之外,和其他的普通函数没什么太大区别。这里主要介绍的是触发器的创建,下面是创建触发器的语法规则:
CREATE [ CONSTRAINT ] TRIGGER name -- 触发器名称
{ BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] } -- 触发的时机(BEFORE/AFTER)和触发事件(例如增删改等)
ON table_name -- 触发器针对的表
[ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ] -- 是否延迟执行
[ REFERENCING { { OLD | NEW } TABLE [ AS ] name } [ ... ] ] -- 见上文的'触发器参数'
[ FOR [ EACH ] { ROW | STATEMENT } ] -- 触发器的级别
[ WHEN ( condition ) ] -- 触发器时需要额外满足的条件
EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments ) -- 触发器函数
where event can be one of:
INSERT
UPDATE [ OF column_name [, ... ] ]
DELETE
语法规则中的添加备注信息已经非常详细了,读者可以参考讲解触发器参数时给的例子来体会。这里再补充以下要点:
- 目前 Klustron 不支持延迟执行触发器(即提交事务时再执行触发器函数)
- 虽然创建触发器时可以为触发器函数指定的'参数',但触发器函数仍然要求是一个无参数的函数;而这些这些传入的“参数”其实韩式存放在上面提到的上下文中,并且通过系统约定好的名称TG_ARGV来引用。
- 在更新操作并发量很高或者修改的数据量很大时,行触发器对性能的影响是非常明显的。它不仅阻止了 DML 语句的下推,也使得触发器函数被大量执行。如果我们只关心部分数据的修改,可以在创建触发器时指定WHEN子句,这样一来只当满足子句的条件时才会执行触发器函数,降低对性能的影响。
- 目前 Klustron 要求创建触发器的表必须存在主键(后续版本可能去除这个限制)。
- 如果在分区表上创建语句级触发器,只有当用户显示更新分区表时会触发,单独更新分区表的子表不会触发;如果在分区表上创建行级触发器,当用户更新分区表或者单独更新分区表的子表时都会触发。
03 FOR EACH ROW 触发器案例
这个是基于FOR EACH ROW的触发器的测试案例,一张是业务表product表,一张是审计表product_record表,当对product表做的每一行操作,都会触发触发器对product表的每个操作行为记录到审计表product_record表中。
3.1 连接到数据库
[root@kunlun1 ~]# su - kunlun
[kunlun@kunlun1 ~]$ psql -h 192.168.56.112 -p 47001 postgres
3.2 创建业务表product和审计表product_record
CREATE TABLE "public"."product" (
"pro_id" int8 primary key,
"pro_name" varchar(100),
"pro_type" varchar(100),
"price" int8
);
CREATE TABLE "public"."product_record" (
"pro_id" int8 ,
"pro_name" varchar(100) ,
"pro_type" varchar(100),
"price" int8,
"data_type" varchar(50),
"update_time" varchar(50)
);
3.3 创建触发器函数
create or replace function product_record_fun()
returns trigger as $$
BEGIN
IF TG_OP = 'INSERT' then
INSERT INTO "public"."product_record"("pro_id", "pro_name", "pro_type", "price", "data_type", "update_time")
VALUES (new.pro_id, new.pro_name, new.pro_type, new.price, tg_op, to_char(now(),'YYYYMMDD'));
ELSIF TG_OP = 'UPDATE' then
INSERT INTO "public"."product_record"("pro_id", "pro_name", "pro_type", "price", "data_type", "update_time")
VALUES (new.pro_id, new.pro_name, new.pro_type, new.price, tg_op, to_char(now(),'YYYYMMDD'));
ELSIF TG_OP = 'DELETE' then
INSERT INTO "public"."product_record"("pro_id", "pro_name", "pro_type", "price", "data_type", "update_time")
VALUES (old.pro_id, old.pro_name, old.pro_type, old.price, tg_op, to_char(now(),'YYYYMMDD'));
END IF;
return new;
END;
$$
LANGUAGE plpgsql;
3.4 创建触发器
create trigger product_trigger after insert or delete or update on product for each row
execute function product_record_fun();
3.5 在业务表product插入数据
insert into product (pro_id,pro_name,pro_type,price) values (10001,'ipad','padnote',4500);
insert into product (pro_id,pro_name,pro_type,price) values (10002,'ipad8','padnote',6000);
insert into product (pro_id,pro_name,pro_type,price) values (10003,'ipone','phone',8000);
insert into product (pro_id,pro_name,pro_type,price) values (10004,'ipone14','phone',8800);
insert into product (pro_id,pro_name,pro_type,price) values (10005,'notebook','computer',10000);
insert into product (pro_id,pro_name,pro_type,price) values (10006,'notebook2','computer',12000);
3.6 查看product业务表,和审计表product_record
select * from product;
select * from product_record;
可以查看到product_record审计表中记录了新insert的6条记录。
3.7 对product业务表中的记录做update操作
update product set price=9000 where pro_id=10002;
update product set price=8500 where pro_id=10003;
update product set price=9800 where pro_id=10004;
3.8 查看update操作后,product业务表和product_record审计表中的记录
select * from product;
select * from product_record;
可以查看到product_record审计表中记录了新update的3条记录
3.9 对product业务表中的记录做delete操作
delete from product where pro_id=10001;
delete from product where pro_id=10005;
3.10 查看delete操作后,product业务表和product_record审计表中的记录
select * from product;
select * from product_record;
可以查看到product_record审计表中记录了新delete的2条记录
以上是完成基于FOR EACH ROW的触发器的测试案例。
04 FOR EACH STATEMENT 触发器案例
这个是基于FOR EACH STATEMENT的触发器的测试案例,一张是业务表emp表,一张是审计表product_record表,当对product表做的每一行操作,都会触发触发器对product表的每个操作行为记录到审计表product_record表中。
4.1 连接到数据库
[root@kunlun1 ~]# su - kunlun
[kunlun@kunlun1 ~]$ psql -h 192.168.56.112 -p 47001 postgres
4.2 创建业务表emp和审计表emp_audit
CREATE TABLE emp (
empid int primary key,
empname varchar(50),
salary int
);
CREATE TABLE emp_audit(
operation char(10),
stamp timestamp,
userid varchar(50),
empid int,
empname varchar(50),
salary int
);
4.3 创建触发器函数
CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $emp_audit$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit
SELECT 'DELETE', now(), user, o.* FROM old_table o;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit
SELECT 'UPDATE', now(), user, n.* FROM new_table n;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit
SELECT 'INSERT', now(), user, n.* FROM new_table n;
END IF;
RETURN NULL;
END;
$emp_audit$ LANGUAGE plpgsql;
4.4 创建触发器
CREATE TRIGGER emp_audit_ins
AFTER INSERT ON emp
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_upd
AFTER UPDATE ON emp
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
CREATE TRIGGER emp_audit_del
AFTER DELETE ON emp
REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE FUNCTION process_emp_audit();
4.5 在业务表emp插入数据
insert into emp (empid,empname,salary) values(1001,'test',5000);
insert into emp (empid,empname,salary) values(1002,'admin',8000);
insert into emp (empid,empname,salary) values(1003,'operator',10000);
insert into emp (empid,empname,salary) values(1004,'auditor',12000);
insert into emp (empid,empname,salary) values(1005,'viewer',9000);
insert into emp (empid,empname,salary) values(1006,'test2',6000);
4.6 查看emp业务表,和审计表emp_audit
select * from emp;
select * from emp_audit;
可以查看到emp_audit审计表中记录了新insert的6条记录。
4.7 对emp业务表中的记录做update操作
update emp set salary=9000 where empid=1002;
update emp set salary=11000 where empid=1003;
update emp set salary=12500 where empid=1004;
4.8 查看update操作后,emp业务表和emp_audit审计表中的记录
select * from emp;
select * from emp_audit;
可以查看到emp_audit审计表中记录了新update的3条记录。
4.9 对emp业务表中的记录做delete操作
delete from emp where empid=1001;
delete from emp where empid=1005;
delete from emp where empid=1006;
4.10 查看delete操作后,emp业务表和emp_audit审计表中的记录
select * from emp;
select * from emp_audit;
可以查看到emp_audit审计表中记录了新delete的3条记录。
以上是完成基于FOR EACH STATEMENT的触发器的测试案例。