一. 外键说明
1.1 官网上有关说明如下:
在应用程序开发中维护数据完整性
http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14251/adfns_constraints.htm#sthref748
管理外键完整性约束
有关定义、启用、禁用和删除所有类型的完整性约束的一般信息,请参阅“删除完整性约束”一节。本节对此信息进行了补充,特别关注有关 FOREIGN KEY 完整性约束的问题,这些约束强制实施不同表中列之间的关系。
注意:
如果引用的主键或唯一键的约束不存在或未启用,则无法启用外键完整性约束。
-- 当外键参考的主键不可用时,对应的外键也不可用。
外键列的数据类型和名称
必须对依赖表和引用表中的相应列使用相同的数据类型。列名不需要匹配。
-- 外键及其参考的字段名称可以不一样,但datatype 必须一致。
对复合外键中列的限制
由于外键引用父表的主键和唯一键,并且主键和唯一键约束是使用索引强制实施的,因此复合外键限制为 32 列。
默认情况下,外键引用主键
如果在定义外键约束(单列或复合)时,列列表未包含在“引用”选项中,则 Oracle 数据库假定您打算引用指定表的主键。
-- 当创建外键时,没有指定参考的字段,默认使用primary key
或者,可以在括号内显式指定父表中要引用的列。Oracle 数据库会自动检查以验证此列列表是否引用了父表的主键或唯一键。否则,将返回信息性错误。
-- 外键必须是primary key 或者是unique key。
创建外键完整性约束所需的权限
若要创建外键约束,约束的创建者必须具有对父表和子表的特权访问权。
(1)父表 参照完整性约束的创建者必须拥有父表,或者对构成父表父键的列具有 REFERENCES 对象特权。
(2)子表 参照物的创建者
在这两种情况下,都无法通过角色获得必要的特权。它们必须显式授予约束的创建者。
这些限制允许:
(1)子表的所有者明确决定强制实施哪些约束以及哪些其他用户可创建约束
(2)父表的所有者明确决定外键是否可以依赖于其表中的主键和唯一键。
选择外键如何强制实施参照完整性
Oracle 数据库允许强制执行不同类型的参照完整性操作,如使用外键约束的定义指定的那样:
(1)防止删除或更新父密钥
如果子表中有引用父键的行,则默认设置可防止删除或更新父键。例如:
SQL>创建表emp_tab(外键(deptno)引用dept_tab);
(2)删除父密钥时删除子行
“ON DELETE 级联”操作允许删除从子表中引用的父密钥数据,但不能更新这些数据。删除父键中的数据时,子表中依赖于已删除的父键值的所有行也将被删除。要指定此参照操作,请在外键约束的定义中包括“删除级联时”选项。例如:
SQL>创建表Emp_tab (
外键 (Deptno) 引用Dept_tab
在删除级联时);
(3)删除父键时将外键设置为空
“ON DELETE SET NULL”操作允许删除引用父项但未更新的数据。删除父键中引用的数据时,子表中依赖于这些父键值的所有行的外键都设置为 null。若要指定此参照操作,请在外键约束的定义中包括“删除时设置 NULL”选项。例如:
SQL>创建表Emp_tab (
外键 (Deptno) 引用Dept_tab
在删除时设置空);
查看完整性约束的定义
数据字典包含以下与完整性约束相关的视图:
ALL_CONSTRAINTS
ALL_CONS_COLUMNS
USER_CONSTRAINTS
USER_CONS_COLUMNS
DBA_CONSTRAINTS
DBA_CONS_COLUMNS
例如:
SQL>选择Constraint_name、Constraint_type、Table_name R_constraint_name
从User_constraints;
1.2 外键索引
如果在对应的child table 上,没有建立外键索引,那么可能造成enq: TM contention 的等待事件。 关于该等待事件具体参考我的Blog:
Oracle enq: TX contention 和 enq: TM contention 等待事件说明
Tom 同学提供的一个查询没有见外键索引的表的SQL:
/* 格式为 2011/6/4 15:39:24 (QP5 v5.163.1008.3004) */
选择table_name,
constraint_name,
cname1
||NVL2 (cname2, ',' || cname2, NULL)
||NVL2 (cname3, ',' || cname3, NULL)
||NVL2 (cname4, ',' || cname4, NULL)
||NVL2 (cname5, ',' || cname5, NULL)
||NVL2 (cname6, ',' || cname6, NULL)
||NVL2 (cname7, ',' || cname7, NULL)
||NVL2 (cname8, ',' || cname8, NULL)
列
从 ( 选择b.table_name,
b.constraint_name,
最大(解码(位置、1、column_name、空))cname1,
最大(解码(位置、2、column_name、空))cname2,
最大 (解码 (位置、 3、 column_name、 空)) cname3,
最大(解码(位置、4、column_name、空))别名4,
最大 (解码 (位置, 5, column_name, 空)) cname5,
最大(解码(位置、6、column_name、空))cname6,
最大 (解码 (位置, 7, column_name, 空)) cname7,
最大 (解码 (位置, 8, column_name, 空)) cname8,
计数 (*) col_cnt
从(选择子字符串(table_name、1、30)table_name、
substr (constraint_name, 1, 30) constraint_name,
substr (column_name, 1, 30) column_name,
位置
从user_cons_columns) a,
user_constraints b
其中 a.constraint_name = b.constraint_name
AND b.constraint_type = 'R'
GROUP BY b.table_name, b.constraint_name) cons
WHERE col_cnt >
ALL ( SELECT COUNT (*)
FROM user_ind_columns i
WHERE i.table_name = cons.table_name
AND i.column_name IN
(cname1,
cname2,
cname3,
cname4,
cname5,
cname6,
cname7,
cname8)
AND i.column_position <= cons.col_cnt
GROUP BY i.index_name)
/
可以通过该SQL 查看没有建外键索引的表。
1.3 外键的一些示例
-- 创建parent table
SQL>创建表parent_tab作为从dba_objects中选择不同的object_type;
已创建表。
-- 创建child table
SQL>创建表child_tab
2 作为
3 选择object_id、object_type、object_name
4 从 all_objects;
已创建表。
-- 父表添加主键
SQL>更改表parent_tab主键添加约束pk_parent_tab (object_type);
表已更改。
-- 子表 创建主键
SQL>更改表child_tab主键添加约束pk_child_tab (object_id);
表已更改。
--child table 创建 on delete cascde 外键
SQL>更改表child_tab在删除级联parent_tab添加外键(object_type)引用fk_child_parent_tab约束;
表已更改。
--查看约束信息
SQL>从table_name user_constraints选择constraint_name、constraint_type、table_name r_constraint_name(“PARENT_TAB”、“CHILD_TAB”);
constraint_name constraint_type table_name r_constraint_name
------------------------- --------------- -------------------- -----------------
SYS_C0019826 C CHILD_TAB
SYS_C0019827 C CHILD_TAB
PK_CHILD_TAB P CHILD_TAB
FK_CHILD_PARENT_TAB R CHILD_TAB PK_PARENT_TAB
PK_PARENT_TAB P PARENT_TAB
其中约束类型对应如下:
C - 检查表上的约束
P - 主键
U - 唯一键
R - 参照完整性
V - 使用选中选项,在视图上
O - 在视图上使用只读
H - 哈希表达式
F - 涉及 REF 列的约束
S - 补充日志记录
其中有2个为C 的约束,这个不是我们建的,我们看以下他们的内容:
SQL>选择table_name,constraint_name,search_condition,从table_name所在的user_constraints(“parent_tab”,“child_tab”)生成;
生成table_name constraint_name search_condition
-------------------- ------------------------- ------------------------- --------------------
child_tab sys_c0019826 “object_id”不是空生成的名称
child_tab sys_c0019827 “object_name”不是空生成的名称
child_tab pk_child_tab用户名
child_tab fk_child_parent_tab用户名
parent_tab pk_parent_tab用户名
这2个是系统自动生成的。
--测试: 从parent table 删除数据,看child table是否cascade 删除了
SQL>从parent_tab中选择 count(*);
计数(*)
----------
40
SQL>从child_tab中选择 count(*);
计数(*)
----------
43573
--从parent table 删除后,child table的数据也删除了
SQL>从parent_tab中删除;
已删除 40 行。
SQL>从child_tab中选择 count(*);
计数(*)
----------
0
SQL> commit;
提交完成。
-- 删除table
--直接drop parent table,报错ora-02449错误
SQL>删除表parent_tab;
删除表parent_tab
*
第 1 行错误:
ORA-02449:表中由外键引用的唯一键/主键
-- 先drop child table 后,在drop parent table,正常
SQL>删除表child_tab;
表已删除。
SQL>删除表parent_tab;
表已删除。
二. 有关外键的一些讨论
外键在维护数据的一致性和完整性有它的作用。 itpub 上有一个帖子,是讨论需不需要使用外键。
帖子链接如下:
http://www.itpub.net/thread-1313696-1-1.html
我的观点:事事没有绝对,存在既有它的价值,适当的使用还是有必要的。