模式与违例检测

  通过本文的学习,您将能够:

  • 启用和禁用约束和索引

  • 对约束和索引使用过滤模式

  • 协调数据库中记录的违例

1. 数据库对象类型

  数据库对象可以是:

  • 约束
    唯一约束
    引用约束
    检查约束
    NOT NULL 约束

  • 索引

  • 触发器

2. 数据库对象模式

  数据库对象可以有以下其中一种模式:

  • 启用:正常状态
  • 禁用:不强制(约束)或使用(触发器或索引)
  • 过滤(触发器除外):启用,但会记录错误。不会回滚事务。
模式 说明
  启用 对象的默认状态或模式。已启用的约束处于活动状态,并强制执行其规则。已启用的索引可用于查询,并包含所有条目。触发器事件发生时,将触发已启用的触发器。
  禁用 不检查或强制执行禁用的约束。禁用索引时,插入、删除或更新行时不会更新内容。禁用的触发器不会触发,数据库服务器会忽略该触发器。即使禁用了约束、触发器或索引条目,它仍保留在系统目录表中。
  过滤 检查并强制执行筛选约束,就像它是已启用的约束一样。但是,任何错误都放在错误日志表中。过滤对象的一个重要影响是,如果违反了约束,则不会回滚事务的其余部分。触发器不能处于过滤模式。唯一可以处于过滤模式的索引类型是唯一索引。

3. 为什么使用对象模式?

  • 没有约束、触发器或索引的情况下,数据库载入速度更快
  • 重新启用对象比重新创建对象更容易,更准确
  • 当对象处于过滤模式时容易找到约束违例
  • 在过滤模式下,可以跳过事务中违反约束的语句,而不是导致语句回滚。。

对象模式方便的原因如下:

  • 当数据库服务器必须检查约束或将键插入到索引中时,插入或更新大量行会显著变慢。为了获得最快的性能,请禁用对象,载入数据并重新启用对象。但是,如果您不确定正载入的数据的完整性,请将约束设置为筛选模式并禁用索引。

  • 在以前的数据库版本中,如果不想要启用对象,则必须删除该对象。载入数据后,您必须重新创建约束和索引。通过禁用对象而不是删除对象,您只需在需要时重新启用即可。一个SQL语句就可以重新启用表中的所有对象。

  • 有时当单个UPDATE语句,DELETE语句或INSERT游标影响许多行时,将很难找到违反约束的行。通过更改对象为过滤模式来将包含错误的行放在违例表中。

  • 如果没有对象模式,事务中的任何 SQL 错误都会导致语句自动回滚(对于日志记录的数据库)。在过滤模式下,您可以跳过导致冲突错误的任何语句,同时仍允许事务继续。

4. 禁用对象

  • 禁用单个对象:
    可以使用dbschemadbaccess 实用程序来找到约束和索引名称。
   SET CONSTRAINTS c117_11, c117_12 DISABLED;
   SET INDEXES idx_x1 DISABLED;
   SET TRIGGERS upd_cust DISABLED;
  • 禁用表的所有对象:
   SET CONSTRAINTS, INDEXES, TRIGGERS FOR customer DISABLED;
  • 结果:
    — 不检查禁用约束
    — 不执行禁用触发器
    — 不更新索引或不将其用于查询

  如果索引是通过添加参照约束或唯一约束而创建的,则只要启用约束,该索引就会始终处于启用状态。
  SET CONSTRAINT 语句在语句持续时间内在目标表上放置一个独占表锁。

4.1 创建禁用对象

  • 索引:
    CREATE UNIQUE INDEX idx1 ON employee(emp_no) DISABLED;
  • 约束:
    CREATE TABLE customer(
        customer_num SERIAL,
        state CHAR(2) CHECK(state IN("CA","AZ"))
    DISABLED);
  • 触发器:
    CREATE TRIGGER t1 UPDATE ON orders
          BEFORE(EXECUTE PROCEDURE x1())
    DISABLED;

  您可以在创建对象时指定禁用该对象。将 DISABLED 关键字添加到 CREATE UNIQUE INDEX 语句、CREATE TRIGGER 语句或 CREATE TABLE 语句中的列或表级约束定义的末尾。

  ALTER TABLE语句也可用于创建过滤或禁用的约束。

    ALTER TABLE employee ADD CONSTRAINT
        CHECK(age<100)CONSTRAINT agelimit FILTERING;

5. 启用约束

  • 启用单个对象:
    SET CONSTRAINTS c117_11, c117_12 ENABLED;
    SET INDEXES idx_x1 ENABLED;
    SET TRIGGERS upd_cust ENABLED;
  • 启用表的所有对象:
    SET CONSTRAINTS, INDEXES, TRIGGERS FOR customer ENABLED;

  如果启用了设置为禁用的约束,那么所有现有行都必须满足约束。 如果某些行违反约束,那么将返回一个错误。

  当从禁用模式启用约束时,将检查所有现有行以查看是否满足约束。如果没有任何行满足约束,那么将返回错误并且约束将保持禁用。

  当从过滤模式启用约束时,不会重新检查现有行,因为它们已经满足约束条件。

  当从禁用模式启用索引时,将有效重建整个索引。

6. 记录违例

  当约束模式更改为过滤启用 时,可以在两个表中记录任何后续违例:违例表violations诊断表diagnostics。表上的约束或索引的所有违例都放在其相应的违例表和诊断表中。每个数据库表中只能存在一对这两个表。

  违例表保存有关发生违例的行的信息。诊断表包含发生的每个违例的一行。在某些情况下,一行可以有多个违例。

  插入的行可能违反NOT NULL约束,引用约束和主键约束。在这种情况下,在诊断表中放置三行,并在违例表中放置一行。

  除违例表中记录的违例外,您还可能会收到一个错误。如果启用约束,那么您会在发生违例时收到一个错误。本模块稍后将介绍过滤模式中违例的错误处理。

7. 违例表设置

  • 创建违例表和诊断表:
  START VIOLATIONS TABLE FOR tab_name;
  • 显式命名违例表:
  START VIOLATIONS TABLE FOR tab_name
      USING vio_tabname,diag_tabname;
  • 限制诊断表中的行数:
  START VIOLATIONS TABLE FOR tab_name MAX ROWS x;

  要开启违例日志记录,请运行 START VIOLATIONS TABLE 语句。

  该语句可以做以下两件事:

  • 创建违例表和诊断表
  • 导致数据库服务器记录违例(如果发生违例的话)

  可以使用 MAX ROWS 子句限制诊断表中插入的单个数据行的行数,最多限制为 2,147,483,647 行。请记住,MAX ROWS 仅限制每个数据行创建的行数,而不限制插入到诊断表中的总行数。

  只有表所有者(具有数据库的resource 权限)或DBA可以对表执行START VIOLATIONS TABLE语句。

表名
  如果没有使用USING子句显式命名表,那么它们将被命名为tabname_viotabname_dia ,其中tabname 是要记录违例的表的名称。如果表名长度大于14个字符,那么需显式指定表名(因为表名称的限制为18个字符)。

表权限
  总而言之,如果用户对目标表具有INSERT,UPDATE或DELETE权限,那么用户也有权限INSERT到违例表中。

表的扩展数据块
  违例和诊断表的扩展数据块大小设置为默认值。违例表和诊断表放置在与目标表相同的数据库空间中。如果分段存储目标表,那么将以相同的方式分段存储违例表。在分段存储目标表的相同数据库空间中以循环方式分段存储诊断表。

8. 违例表模式

  违例表包含与目标表(记录违例的表)相同的列。此外,还有三个列存储唯一的序列号、导致错误的操作类型以及以及提交SQL 语句导致发生冲突的用户的登录名。如果目标表(记录违例的表)包含一个序列值,那么该值将作为整数存储在违例表中。

  每次SQL语句导致发生一个或多个违例时,违例表会收到一行。诊断表包含发生的每个约束或唯一索引违例的一行。此表存储有关违反约束的信息。

  sinodb_tupleid 列可以与违例表中相同名称的列相关联,以便将违例条目与其相应的诊断相关联。

  要确定违反什么约束,请运行dbschema 实用程序,并查找与诊断表中的objname 匹配的约束名称。

9. 过滤模式

  • 将单个对象设置为过滤:
  SET CONSTRAINTS c117_11, c117_12 FILTERING;
  SET INDEXES idx_x1 FILTERING;
  • 将表的所有对象设置为过滤:
  SET CONSTRAINTS,INDEXES FOR customer FILTERING;
  • 如果发生违例,那么将导致返回到应用程序的错误:
  SET CONSTRAINTS,INDEXES FOR customer FILTERING WITH ERROR;

  您可以将约束和唯一索引设置为过滤模式。在过滤模式中,任何约束或唯一索引违例都会记录在违例表中。

  将对象设置为过滤模式之前,请确保使用START VIOLATIONS TABLE语句对表启用了违例记录。

  您可以将触发器和索引(除了唯一索引)设置为过滤模式。

错误处理

  在过滤模式中,违例不会导致语句回滚。在默认过滤模式(WITHOUT ERROR)中,不会通知应用程序发生违例。要注意这种模式,因为您可能会错误假定事务已经全部完成,而实际上可能还没有完成。

  如果SET语句中包含WITH ERROR子句,则会向用户返回错误:

971: Integrity violations detected.

  但是,与启用模式不同,此错误不会自动回滚该语句。要回滚事务,请明确执行ROLLBACK WORK。

  将错误添加到诊断表和违例表的INSERT语句是当前事务的一部分。 如果您回滚该事务,那么也会回滚违例和诊断表中的行。

10.关闭违例日志记录

  要关闭日志记录:

STOP VIOLATIONS TABLE FOR tab_name;

  您可以使用STOP VIOLATIONS语句关闭表的违例日志记录。

  此语句不会删除违例表,管理员应该在停止违例日志记录后使用DROP TABLE语句进行删除。

  如果目标表在过滤模式中有任何约束,那么应该始终开启违例日志记录。如果关闭日志记录,那么任何过滤约束违例都会因为无法记录而导致错误。

11. 系统目录表: sysobjstate

  • sysobjstate 系统目录包含每个约束、触发器和索引的一行。
  • 表列为:
名称 类型 描述
objtype char(1) C = Constraint (约束);I = Index (索引);T = Trigger (触发器)
owner char(8) 对象的所有者
name char(18) 对象的所有者
tabid int 表的 tabid
state char(1) D = Disabled (禁用);E = Enabled (启用);F = Filtering with no error (无错误的过滤);G = Filtering with error(带错误的过滤)

11. 系统目录表:sysviolations

  • sysviolations 表存储有关目标数据库表的违例和诊断表的信息
  • 表列为:
名称 类型 描述
targettid int systables 连接以找到目标表名称的表ID
viotid int 违例表 ID
diatid int 诊断表 ID
maxrows int 违例表中允许的最大行数(如果没有最大值,则为空)

示例1:

CREATE TABLE customer(
  customer_num SERIAL NOT NULL CONSTRAINT nn_cn,
  name CHAR(15),
  PRIMARY KEY (customer_num) CONSTRAINT pk_cn);

CREATE TABLE orders(
  order_num SERIAL NOT NULL CONSTRAINT nn_on,
  customer_num INT NOT NULL CONSTRAINT nn_oncn,
  ship_instruct CHAR(40));

ALTER TABLE orders ADD CONSTRAINT
  FOREIGN KEY(customer_num)
    REFERENCES customer CONSTRAINT fk_cust);

  该示例显示两个表,customerorderscustomer 表是具有主键的父表,orders 表是具有引用customer 表的约束的子表。此引用约束意味着每个orders 行必须存在相应的customer 行。

--下列语句会生成错误:
SET CONSTRAINTS, TRIGGERS, INDEXES
  FOR customer DISABLED;

--替换:
SET CONSTRAINTS, TRIGGERS, INDEXES
  FOR orders DISABLED;
SET CONSTRAINTS, TRIGGERS, INDEXES
  FOR customer DISABLED;

--现在开启违例日志记录:
START VIOLATIONS TABLE FOR customer;
START VIOLATIONS TABLE FOR orders;

  假设您要执行大量载入,其中某些数据可能会导致一些临时引用完整性错误。您决定禁用customerorders 表的约束。

SET CONSTRAINTS 顺序

  您必须以正确的顺序执行SET CONSTRAINTS语句。当存在其他启用的对象引用对象时,不能禁用该对象。因为orders 的引用约束引用customer 表,所以禁用customer 约束会先产生错误。相反,应先禁用orders 约束。

START VIOLATIONS TABLE

  要记录违例,请使用START VIOLATIONS TABLE语句通知数据库服务器应记录表的违例。此语句为列出的表创建两个违例表。在该示例中,创建了四个附加表:customer_viocustomer_diaorders_vioorders_dia

-- 此语句执行成功:
INSERT INTO orders
(order_num,customer_num,ship_instruct)
VALUES (0,2,"ship tomorrow");

--  但是,当启用约束时会发生错误:
SET CONSTRAINTS, TRIGGERS, INDEXES
  FOR customer ENABLED;
SET CONSTRAINTS, TRIGGERS, INDEXES
  FOR orders ENABLED;

 971: Integrity violations detected.

  一旦禁用约束后,将不会检查这些约束。这就是上述INSERT语句成功的原因。如果启用约束,那么该语句将失败,因为您无法添加没有相应customer 行的order 行。

  但是当您尝试启用约束时,数据库服务器必须检查所有行以确保没有违例。上述SET CONSTRAINTS … ENABLED语句由于INSERT语句引入的违例而失败。

-- 违例表中的错误:
SELECT * FROM orders_vio, orders_dia
  WHERE orders_vio.informix_tupleid =
    orders_dia.informix_tupleid

order_num 1
customer_num        2
ship_instruct         ship tomorrow
informix_tupleid      1
informix_optype      S
informix_recowner    informix
informix_tupleid      1
objtype              C
objowner            informix
objname             fk_cust

  当执行SET CONSTRAINTS … ENABLED语句时,会将任何违例都放在违例表中(如果为表执行了START VIOLATIONS TABLE语句)。管理员可以浏览违例表并(由objname 列)确定违反了哪些约束。

在上述示例中,违反fk_cust约束。管理员可以运行dbschema并查找fk_cust约束:

 alter table orders add constraint
  (foreign key (customer_num) references customer

示例 2:

  • 在正常SQL活动期间检查违例:
  SET CONSTRAINTS, INDEXES FOR customer FILTERING;
  SET CONSTRAINTS, INDEXES FOR orders FILTERING;
  • 此行未插入,但不会返回错误!
  INSERT INTO orders (order_num,customer_num,ship_instruct) VALUES (0,4,"ship tomorrow");

  假设由于某种原因,管理员想知道在载入过程中发生哪些违例,而不会导致载入过程失败。为此,管理员将约束和索引(唯一)设置为过滤。

  由于WITH ERROR子句不包含在SET语句中,因此当返回任何错误时不会通知应用程序。上述INSERT语句将失败,并将一个条目放在违例表中。但是,应用程序没有收到错误。

序列值
  即使未插入上述示例中的行,表的序列值也会递增。如果插入上述示例中的行,那么违例表会显示order_num 的序列值。

  • 启用约束:
SET CONSTRAINTS, INDEXES FOR customer ENABLED;
SET CONSTRAINTS, INDEXES FOR orders ENABLED;
  • 关闭违例日志记录:
STOP VIOLATIONS TABLE FOR customer;
STOP VIOLATIONS TABLE FOR orders;
  • 修复导致违例发生的错误:
INSERT INTO customer(customer_num,name) VALUES (4,"SCHMIDT");
  • 将导致违例的行插入到目标表中:
INSERT INTO orders
  SELECT order_num, customer_num, ship_instruct
     FROM orders_vio;

  现在管理员想要协调任何违例。首先,他要启用约束和索引。然后关闭违例日志记录。这两个步骤需要将违例插回到目标表中,从而避免了无休止的循环(将违例添加到违例表中,然后再插回到 customer 表中)。

  接下来,管理员修复导致违例发生的任何错误。在上述示例中,父行缺少引用约束。

  最后,管理员可以使用INSERT INTO … SELECT FROM语句将违例表中的任何行复制到目标表中。