现在的位置: 首页 > 综合 > 正文

如何在 DB2 Universal Database 中暂时禁用触发器(转)

2013年01月05日 ⁄ 综合 ⁄ 共 8237字 ⁄ 字号 评论关闭

转自:http://www.ibm.com/developerworks/cn/data/library/techarticles/0211yip/

请注意:在阅读本文前请先阅读 免责声明

简介

有时您会想暂时禁用表上的触发器。例如,尽管您可能需要触发器执行日常的 SQL 操作,但可能不希望在运行特定脚本时触发那些触发器。标准做法是删除触发器,当再次需要它时再重新创建,但如果您必须跟踪许多触发器,那就有点为难了。(现在,我该把那些触发器的源代码保存到哪里呢?)

本文提供了三种解决这个问题的方法:

每种方法都有其优缺点,但我们将这方面的 讨论留到文章末尾。

方法 1:对特定用户禁用触发器

用来执行数据库维护任务的用户标识通常与用于应用程序的用户标识不同,这种方法利用了这一情况。要使这种方法有效,您只需选择在不希望触发触发器时要使用的用户标识。

example1.db2 脚本中的 SQL 向您演示了这种方法。

设置

要设置这个示例:

  1. 创建两个表 t1  t2 。我们将在 t1 上创建一个样本触发器,它将引起对 t2 进行插入操作。

    	CREATE TABLE db2admin.t1 (c1 int)
    	CREATE TABLE db2admin.t2 (c1 int)

  2. 创建触发器:
    	CREATE TRIGGER db2admin.trig1
    	AFTER INSERT ON db2admin.T1
    	REFERENCING NEW AS o 
    	FOR EACH ROW MODE DB2SQL
    	WHEN (USER <> 'ADMINISTRATOR')
    	BEGIN ATOMIC
    		INSERT INTO db2admin.t2 values (o.c1);
    	END

这个触发器很简单。每当连接的用户标识(由 USER 专用寄存器返回的)与 ADMINISTRATOR 不匹配时,则将插入到 t1 中的值也插入到 t2 。因此,当不想触发触发器时,以用户 ADMINISTRATOR进行连接以执行您的任务。

测试示例

  1. 在创建了表 t1  t2 和触发器 trig1 之后,以不同于 ADMINISTRATOR 的任何用户进行连接并将值插入 t1 

    	INSERT INTO db2admin.t1 VALUES (111)

  2. 验证该值已由触发器复制到了表 t2 中:
    	SELECT * FROM db2admin.t2
    	C1
    	-----------
            	111
      	1 record(s) selected.
    

  3. 接下来,以用户 ADMINISTRATOR 连接,并尝试再次插入值:
    	INSERT INTO t1 VALUES (222)

  4. 验证表 t2 未更改,因为触发器未被激活:
    	SELECT * FROM db2admin.t2
    	C1
    	-----------
            	111
      	1 record(s) selected.

方法 2:用框架来允许禁用触发器

本节描述了一个触发器框架,您可以将它用于任何可能需要暂时禁用的触发器。使用框架要求触发器开发人员进行规划并对这种概念取得一致意见,但这样做的结果可以得到该问题非常清晰的解决方案。

example2.db2 脚本中的 SQL 向您演示了这种方法。

下面说明了这种机制的工作原理:

  • 定义触发器查找表 trigger_state ,它维护一个由触发器的名称和状态(active='Y' 或 'N')所组成的列表
  • 在定义触发器时,向 trigger_state 表添加一次查寻(在该触发器的 WHEN 子句中)以确定该触发器是否应该激活

设置

要设置这个示例:

  1. 创建两个表 t1  t2 。我们将在 t1 上创建一个样本触发器,它将引起对 t2 进行插入操作。

    	CREATE TABLE db2admin.t1 (c1 int)
    	CREATE TABLE db2admin.t2 (c1 int)

  2. 创建 trigger_state 表。
    	CREATE TABLE db2admin.trigger_state 
    	(
    		trigschema VARCHAR(128) not null,
    		trigname VARCHAR(30) not null, 
    		active char(1) not null
    	)
    

    乍一看,您很可能想在含有 trigschema  trigname 列的 trigger_state 表中放置一个主键。但是,我们稍后会在 性能优化中讨论优化问题。目前,我们先不在表上放置任何约束。

  3. 假定您想要在表 t1 上创建名为 trig1 的触发器。我们要做的第一件事情是向 trigger_state 表注册该触发器:
    	INSERT INTO db2admin.trigger_state VALUES ('DB2ADMIN','TRIG1','Y')
    

    提示:对所有值都使用 大写,与系统目录表保持一致。

  4. 接下来,为方便起见,我们将创建用户定义的函数(UDF)。当我们创建触发器时,它的用途将变得很明显:
    	CREATE FUNCTION db2admin.trigger_enabled (
    		v_schema VARCHAR(128), 
    		v_name VARCHAR(30))
    	RETURNS VARCHAR(1)
    	RETURN (SELECT active FROM db2admin.trigger_state 	WHERE trigschema=v_schema and trigname=v_name)
    

    重要:如果查寻失败,则这个函数返回空值。因此,确保正确填写 trigger_state 表,并在调用这个函数时传递正确的参数。

    如您所见,该函数将模式和触发器的名称作为输入,以在 trigger_state 表中执行查寻,并返回 active 列中的值。

  5. 创建触发器:
    	CREATE TRIGGER db2admin.trig1
    	AFTER INSERT ON db2admin.T1
    	REFERENCING NEW AS o
    	FOR EACH ROW MODE DB2SQL
    	WHEN (db2admin.trigger_enabled('DB2ADMIN','TRIG1') = 'Y')
    	BEGIN ATOMIC
    		INSERT INTO db2admin.t2 values (o.c1);
    	END
    

    这个触发器很简单。当启用它时,插入 t1 的值也会插入到 t2 。但是,在激活它之前,它调用 UDF trigger_enabled() 来确定该触发器是否被禁用。用该函数封装这个查询降低了出错的可能性,尤其是在需要创建许多触发器的情况下。

    提示:如果您的触发器已经将 WHEN 子句用于其它条件,则只需用 AND 操作符将条件串到一起。

测试示例

  1. 首先,我们测试该触发器是否按预期的方式工作:

    	INSERT INTO db2admin.t1 values (123)
    	DB20000I  The SQL command completed successfully.

  2. 验证 t2 也包含值 123,因为激活了触发器:
    	SELECT * FROM db2admin.t2
    	C1
    	-----------
            	123
      	1 record(s) selected.

  3. 现在,我们将禁用该触发器:
    	UPDATE db2admin.trigger_state SET active='N' 
    	WHERE trigschema='DB2ADMIN' and trigname='TRIG1'

  4. 然后将另一行插入 t1 
    	INSERT INTO db2admin.t1 values (456)

  5. 现在,让我们通过确定表 t2 未经更改来验证触发器已被禁用。
    	SELECT * FROM db2admin.t2
    	C1
    	-----------
            	123
      	1 record(s) selected.

重新启用触发器 

要重新启用触发器,只要再次设置触发器的状态。

	UPDATE db2admin.trigger_state SET active='Y' 
	WHERE trigschema='DB2ADMIN' and trigname='TRIG1'

性能优化

至此,我们还没有在 trigger_state 上创建任何唯一性约束或索引,因为有理由对此进行更彻底的讨论,另外还因为演示该技术时不需要这些约束或索引。

因为 trigger_state 可能维护数百甚至数千个触发器,所以我们希望使对该表执行查寻的开销最小化。与其在列 trigschema trigname 上创建主键(由它又可以创建唯一性索引),不如将创建这个唯一性索引作为单独的步骤,以便我们在该索引页中包含名为 active 的列。从基表取回多余的字节会占用额外的 I/O,这是对资源的浪费。

以下是该索引的定义,它用关键字 INCLUDE来指定在这个唯一性索引中添加 active 列:

	CREATE UNIQUE INDEX db2admin.trigstateIX 
		ON db2admin.trigger_state (trigschema, trigname) 
		INCLUDE (active)

如果使用 trigger_state 表来维护数千个触发器,则可能希望将这个表放在它自己的表空间中,并给它指定专门的缓冲池。这样,就可以将该查找表始终保存在内存中。尝试确定缓冲池的大小,以便确保 trigger_state 的所有行都在内存中,但不要使缓冲池太大以至浪费内存(您可以使用命令 LIST TABLESPACES SHOW DETAIL 的输出来帮助确定缓冲池的大小)。如果触发器数目在几千之内,则这种优化可能是不值得的,因为,假定 trigger_state 的行大小只有 41 字节左右(假定 trigschema 占 20 个字节, trigname 占 20 个字节,状态占 1 个字节),那么每 4 KB 的页能存储 100 个触发器的信息。

如果您有几千个触发器,请记住对 trigger_state 表运行统计。

当然,另一个重要的考虑事项是,只对需要定期禁用的触发器才使用这种技术。

方法 3:使用 SQL 存储过程维护触发器

 方法 1 方法 2中,我们描述了禁用触发器的方法,这样您就不必为删除和重建它们所引发出来的问题而担心了。在本节中,我们提供了一种解决方案,它使用 SQL 存储过程来封装和管理触发器的删除和重建。其机制是这样设计的:源代码始终存在于数据库中,因此不必跟踪触发器的源代码。

以下是这种机制的工作原理:

  1. 创建三个存储过程:

    • disable_trigger() — 禁用触发器
    • enable_trigger() — 启用触发器
    • show_disabled_triggers() — 显示所有已禁用的触发器
  2. 创建名为 trigtool.disabled_triggers 的表,它看上去类似于 syscat.triggers 系统目录表。这个表维护已删除触发器的副本。最初它是空的。
  3. 当需要禁用触发器时,调用 disable_trigger() ,它将触发器定义从 syscat.triggers 复制到 disabled_triggers 表,然后删除这个触发器。
  4. 要启用触发器,调用 enable_trigger() ,它从 trigtool.disabled_triggers 表重新创建触发器。

限制:不能通过这种方法禁用代码文本超过大约 30KB 的触发器。

用存储过程禁用和启用触发器提供了一种有别于实际删除和重建触发器的抽象级别。我们提供了实现该存储过程的所有源代码。但是,请确保阅读后面有关我们代码的 免责声明

设置

使用 trigtool 模式创建所有对象,并且脚本 example3.db2 提供了所有 DDL。

  1. 创建 32KB 页大小的缓冲区和 32KB 页大小的表空间。

    	CREATE BUFFERPOOL BP32K SIZE 1000 PAGESIZE 32K
    	CREATE TABLESPACE TS32K PAGESIZE 32K
    	MANAGED BY SYSTEM USING ('c:\ts32k\') BUFFERPOOL BP32K

  2. 创建 trigtool.disabled_triggers 表:
    	CREATE TABLE TRIGTOOL.DISABLED_TRIGGERS (
    		TRIGSCHEMA VARCHAR(128) not null,
    		TRIGNAME VARCHAR(128) not null,
    		TABSCHEMA VARCHAR(128) not null,
    		TABNAME VARCHAR(128) not null,
    		QUALIFIER VARCHAR(128) not null,
    		FUNC_PATH VARCHAR(254) not null,
    		TEXT VARCHAR(31500) not null
    		) in TS32K
    	ALTER TABLE TRIGTOOL.DISABLED_TRIGGERS 
    		ADD CONSTRAINT disabledtrig_pk PRIMARY KEY 
    		(trigschema, trigname)
    

    以下是这个表的一些重要特性:

    • 该表看起来几乎(但并非完全)与 syscat.triggers 相同。我们只包括了需要用来重新创建触发器的列。
    • 该表是在表空间 TS32K 中创建的,该表空间是 32KB 页大小
    • 在触发器模式与触发器名称上创建了主键约束
    • TEXT 列的类型是 VARCHAR(31500),这与 syscat.triggers 中使用 CLOB 类型的 TEXT 列不同。稍后讨论原因。
  3. 创建 trigtool.show_disabled_triggers() 过程,它提供了一个显示当前已禁用触发器的方法。基本上,它将已禁用游标的模式和名称作为一个游标返回给该过程的调用者,应用程序或用户可以从命令行处理器(CLP)检索它。以下是这个过程的源代码:
    	CREATE PROCEDURE TRIGTOOL.SHOW_DISABLED_TRIGGERS ()
    	LANGUAGE SQL
    	RESULT SETS 1
    	BEGIN
    	DECLARE c_triggers CURSOR WITH RETURN FOR
        		SELECT trigschema, trigname FROM TRIGTOOL.DISABLED_TRIGGERS;
    	OPEN c_triggers;
    	END

  4. 既然我们拥有查看已禁用触发器的方法,我们就可以创建一个名为 trigtool.disable_trigger() 的过程,它实际复制和删除触发器。
    	CREATE PROCEDURE TRIGTOOL.DISABLE_TRIGGER (
        		IN v_schema VARCHAR(128), 
        		IN v_name VARCHAR(128))
    	SPECIFIC DISABLE_TRIGGER
    	LANGUAGE SQL
    	BEGIN
    	DECLARE SQLCODE INT DEFAULT 0;
    	DECLARE v_stmt VARCHAR(250);
    	DECLARE EXIT HANDLER FOR NOT FOUND
        		SIGNAL SQLSTATE '80000' 
            		SET MESSAGE_TEXT='Trigger Not Found';
    	DECLARE EXIT HANDLER FOR SQLWARNING
        		SIGNAL SQLSTATE '80001' 
            		SET MESSAGE_TEXT='Unable to disable trigger';
    	INSERT INTO TRIGTOOL.DISABLED_TRIGGERS
    	SELECT
    		TRIGSCHEMA, TRIGNAME, TABSCHEMA, TABNAME,
    		QUALIFIER, FUNC_PATH, CAST(TEXT as VARCHAR(31500))
    	FROM SYSCAT.TRIGGERS
    	WHERE TRIGSCHEMA = v_schema and TRIGNAME = v_name
        		AND VALID='Y';
    	SET v_stmt =  'DROP TRIGGER ' || v_schema || '.' ||v_name;
    	EXECUTE IMMEDIATE v_stmt;
    	END

    该过程接收两个参数:要禁用的触发器的模式和名称。

    第一个操作是 INSERT,它从 syscat.triggers 表将信息复制到 trigtool.disabled_triggers 表。注: syscat.triggers 的 TEXT 列的 CLOB 数据类型被强制转化成了 VARCHAR(31500) 数据类型。复制完成之后,就用动态 SQL 删除触发器。因为没有定义异常处理程序,所以发生的任何错误都将导致回滚,从而使这个操作被拒绝。为了安全,声明一个 SQLWARNING 处理程序。这个处理程序发出 SQLEXCEPTION 信号,该信号将引起回滚。换句话说,只有在整个操作序列都完成,并不出现错误或警告的情况下,触发器才会被删除。

  5. 创建 trigtool.enable_trigger() ,它将从 trigtool.disabed_triggers 表重新创建触发器。
    	CREATE PROCEDURE TRIGTOOL.ENABLE_TRIGGER (
        		IN v_schema VARCHAR(128),
        		IN v_name VARCHAR(128))
    	LANGUAGE SQL
    	BEGIN
    	DECLARE SQLCODE INT DEFAULT 0;
    	DECLARE v_qualifier VARCHAR(128);
    	DECLARE v_func_path VARCHAR(1000);
    	DECLARE v_stmt VARCHAR(32672);
    	DECLARE v_curr_qualifier VARCHAR(128);
    	DECLARE v_curr_funcpath VARCHAR(1000);
    	DECLARE EXIT HANDLER FOR SQLWARNING
        		SIGNAL SQLSTATE '80000'
    		SET MESSAGE_TEXT = 'Error. Manual recreation required';
    	SET v_curr_qualifier = CURRENT SCHEMA;
    	SET v_curr_funcpath = CURRENT FUNCTION PATH;
    	SELECT qualifier, func_path, TEXT into v_qualifier, v_func_path, v_stmt
    	FROM TRIGTOOL.DISABLED_TRIGGERS
        		WHERE trigschema=v_schema and trigname=v_name;
    	SET v_func_path = 'SET CURRENT FUNCTION PATH = ' || v_func_path;
    	EXECUTE IMMEDIATE v_func_path;
    	SET v_qualifier = 'SET CURRENT SCHEMA = ' || v_qualifier;
    	EXECUTE IMMEDIATE v_qualifier;
    	EXECUTE IMMEDIATE v_stmt;
    	DELETE FROM TRIGTOOL.DISABLED_TRIGGERS
        		WHERE trigschema=v_schema and trigname=v_name;
    	SET v_curr_qualifier = 'SET SCHEMA = ' || v_curr_qualifier;
    	SET v_curr_funcpath = 'SET CURRENT FUNCTION PATH = ' || v_curr_funcpath;
    	EXECUTE IMMEDIATE v_curr_qualifier;
    	EXECUTE IMMEDIATE v_curr_funcpath;
    	END

    该过程(与前面那个删除触发器的过程类似)接收两个参数:要启用触发器的模式和名称。首先,存储当前会话的当前模式和当前函数路径,以便我们能够在该过程执行完成之后恢复它。然后,从 trigtool.disabled_triggers 表检索 qualifier, func_path text

    qualifier所包含的模式在最初创建触发器时限定未限定的表和视图。类似地, func_path值表示在最初创建触发器时使用的函数路径。函数路径用来解析触发器定义中可能存在的未限定函数。 text列包含用来创建触发器的原始文本。

    在恢复触发器之前,设置函数路径和当前模式值,以便在执行触发器文本时,对所有未限定的对象引用使用适当的限定符和函数路径。然后,使用 text,重新创建触发器,并 从 trigtool.disabled_triggers 删除该触发器的副本。您可以从代码中看出存在 30 KB 触发器文本大小限制的原因。EXECUTE IMMEDIATE 不支持 CLOB 类型作为参数,这就是我们必须将来自syscat.triggers 的原始文本从 CLOB 强制转化成 VARCHAR 的原因。

抱歉!评论已关闭.