通过本文的学习,您将能够:
-
启用和禁用约束和索引
-
对约束和索引使用过滤模式
-
协调数据库中记录的违例
1. 数据库对象类型
数据库对象可以是:
-
约束
唯一约束
引用约束
检查约束
NOT NULL 约束 -
索引
-
触发器
2. 数据库对象模式
数据库对象可以有以下其中一种模式:
- 启用:正常状态
- 禁用:不强制(约束)或使用(触发器或索引)
- 过滤(触发器除外):启用,但会记录错误。不会回滚事务。
模式 | 说明 |
---|---|
启用 | 对象的默认状态或模式。已启用的约束处于活动状态,并强制执行其规则。已启用的索引可用于查询,并包含所有条目。触发器事件发生时,将触发已启用的触发器。 |
禁用 | 不检查或强制执行禁用的约束。禁用索引时,插入、删除或更新行时不会更新内容。禁用的触发器不会触发,数据库服务器会忽略该触发器。即使禁用了约束、触发器或索引条目,它仍保留在系统目录表中。 |
过滤 | 检查并强制执行筛选约束,就像它是已启用的约束一样。但是,任何错误都放在错误日志表中。过滤对象的一个重要影响是,如果违反了约束,则不会回滚事务的其余部分。触发器不能处于过滤模式。唯一可以处于过滤模式的索引类型是唯一索引。 |
3. 为什么使用对象模式?
- 没有约束、触发器或索引的情况下,数据库载入速度更快
- 重新启用对象比重新创建对象更容易,更准确
- 当对象处于过滤模式时容易找到约束违例
- 在过滤模式下,可以跳过事务中违反约束的语句,而不是导致语句回滚。。
对象模式方便的原因如下:
-
当数据库服务器必须检查约束或将键插入到索引中时,插入或更新大量行会显著变慢。为了获得最快的性能,请禁用对象,载入数据并重新启用对象。但是,如果您不确定正载入的数据的完整性,请将约束设置为筛选模式并禁用索引。
-
在以前的数据库版本中,如果不想要启用对象,则必须删除该对象。载入数据后,您必须重新创建约束和索引。通过禁用对象而不是删除对象,您只需在需要时重新启用即可。一个SQL语句就可以重新启用表中的所有对象。
-
有时当单个UPDATE语句,DELETE语句或INSERT游标影响许多行时,将很难找到违反约束的行。通过更改对象为过滤模式来将包含错误的行放在违例表中。
-
如果没有对象模式,事务中的任何 SQL 错误都会导致语句自动回滚(对于日志记录的数据库)。在过滤模式下,您可以跳过导致冲突错误的任何语句,同时仍允许事务继续。
4. 禁用对象
- 禁用单个对象:
可以使用dbschema 或dbaccess 实用程序来找到约束和索引名称。
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_vio 和tabname_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);
该示例显示两个表,customer 和orders 。customer 表是具有主键的父表,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;
假设您要执行大量载入,其中某些数据可能会导致一些临时引用完整性错误。您决定禁用customer 和orders 表的约束。
SET CONSTRAINTS 顺序
您必须以正确的顺序执行SET CONSTRAINTS语句。当存在其他启用的对象引用对象时,不能禁用该对象。因为orders 的引用约束引用customer 表,所以禁用customer 约束会先产生错误。相反,应先禁用orders 约束。
START VIOLATIONS TABLE
要记录违例,请使用START VIOLATIONS TABLE语句通知数据库服务器应记录表的违例。此语句为列出的表创建两个违例表。在该示例中,创建了四个附加表:customer_vio 、customer_dia 、orders_vio 和orders_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语句将违例表中的任何行复制到目标表中。