约束是Oracle数据库保证数据完整性的第一道防线,它直接在表级别定义规则,由数据库内核强制执行,任何违反约束的DML操作都会被拒绝。相比应用程序层面的校验逻辑,约束更可靠、性能开销更小,而且不会因为绕过应用而失效。本文围绕Oracle中常见的约束类型展开,并演示约束的添加、删除、禁用等日常操作,帮助你把数据完整性真正落实到数据库层面。

Oracle中的五种常用约束类型
Oracle提供了五种最常用的约束:NOT NULL(非空)、UNIQUE(唯一)、PRIMARY KEY(主键)、FOREIGN KEY(外键)和CHECK(检查)。每种约束解决的问题不同,理解它们的区别是正确设计表结构的前提。
非空约束最简单,它要求某一列必须提供值,通常用于业务上不允许缺失的字段,比如用户名、手机号等。唯一约束保证列值在整个表中不重复,但允许出现多个NULL,这一点经常被人误解,Oracle认为NULL之间互不相等,所以唯一键上可以插入多行NULL值。主键约束本质上是非空约束和唯一约束的组合,即列值既不能为空也不能重复,一张表最多只能有一个主键,这是主键与唯一键最本质的区别。
外键约束用于建立两张表之间的引用关系,子表的某一列必须引用父表中存在的值,或者为NULL。比如订单表的customer_id必须存在于客户表的customer_id中,这样就能防止出现孤儿数据。检查约束则允许你定义一个布尔表达式,插入或更新时只有表达式结果为真才被允许,例如限制年龄在0到150之间、限制性别只能取男或女。
需要注意的一点是,Oracle不支持某些数据库中的CHECK约束嵌套子查询,也不能在CHECK中调用SYSDATE、USER等非确定性函数,因为约束规则必须是可重复判断的静态条件。
建表时定义约束与事后添加约束
约束可以在CREATE TABLE时直接定义,也可以在表创建之后通过ALTER TABLE语句追加。定义方式又分为列级定义和表级定义两种,列级约束直接写在字段定义后面,表级约束则写在所有字段之后,适合定义复合约束和需要显式命名的场景。
先看一个在建表语句中定义各类约束的完整示例:
CREATE TABLE t_order (
order_id NUMBER(10) PRIMARY KEY,
customer_id NUMBER(10) NOT NULL,
order_no VARCHAR2(30) UNIQUE,
amount NUMBER(12,2) CHECK (amount >= 0),
status VARCHAR2(10) DEFAULT 'NEW' NOT NULL,
CONSTRAINT fk_order_customer
FOREIGN KEY (customer_id)
REFERENCES t_customer (customer_id)
);这个示例里既有匿名的系统自动命名约束,也有显式命名的fk_order_customer外键。强烈建议对外键、唯一键这类约束显式命名,否则Oracle会自动生成类似SYS_C0012345这样的随机名称,后续维护时会非常麻烦,你很难分辨某个系统命名到底对应哪个业务约束。
如果表已经存在,事后添加约束使用ALTER TABLE的ADD CONSTRAINT子句:
-- 追加主键约束
ALTER TABLE t_order ADD CONSTRAINT pk_order PRIMARY KEY (order_id);
-- 追加唯一约束
ALTER TABLE t_order ADD CONSTRAINT uk_order_no UNIQUE (order_no);
-- 追加检查约束
ALTER TABLE t_order ADD CONSTRAINT chk_order_amount CHECK (amount >= 0);
-- 追加外键约束,并指定删除时的处理策略
ALTER TABLE t_order ADD CONSTRAINT fk_order_customer
FOREIGN KEY (customer_id)
REFERENCES t_customer (customer_id)
ON DELETE CASCADE;添加约束时有一个细节值得注意:如果表中已有数据违反了新约束,ADD操作会直接报错。此时可以先清理数据,或者使用带有EXCEPTIONS INTO子句的方式定位违规行。而非空约束比较特殊,添加时要用MODIFY语法而不是ADD CONSTRAINT,因为NOT NULL在Oracle内部是作为列属性存储的:
ALTER TABLE t_order MODIFY (customer_id NOT NULL); -- 取消非空约束 ALTER TABLE t_order MODIFY (customer_id NULL);
外键的ON DELETE子句有三种策略可选:默认的NO ACTION(父行被引用时禁止删除)、CASCADE(删除父行时级联删除子行)和SET NULL(删除父行时把子表引用列置为NULL)。选择哪种策略取决于业务语义,订单明细通常适合级联删除,而交易流水类的历史数据则更适合禁止删除。
删除、禁用与查看约束的常用技巧
删除约束使用DROP CONSTRAINT,删除时必须给出约束名,这也是前面强调要显式命名的原因。删除主键和唯一约束时,如果该键上存在关联索引和外键引用,可能会遇到ORA-02273错误,此时需要先删除依赖的外键,或者使用CASCADE子句连带删除引用它的外键:
-- 删除普通约束 ALTER TABLE t_order DROP CONSTRAINT fk_order_customer; -- 删除主键并级联删除依赖的外键 ALTER TABLE t_order DROP CONSTRAINT pk_order CASCADE; -- 删除列时连带删除该列上的约束 ALTER TABLE t_order DROP COLUMN order_no CASCADE CONSTRAINTS;
生产环境中有时需要在批量导入数据前暂时关闭约束校验,导入完成后再重新启用。禁用和启用使用DISABLE与ENABLE关键字:
-- 禁用约束(不加VALIDATE可提升大数据量处理速度) ALTER TABLE t_order DISABLE CONSTRAINT fk_order_customer; -- 重新启用约束 ALTER TABLE t_order ENABLE CONSTRAINT fk_order_customer; -- 启用时校验已有数据,可用NOVALIDATE跳过存量校验只管增量 ALTER TABLE t_order ENABLE NOVALIDATE CONSTRAINT chk_order_amount;
DISABLE与ENABLE还可以配合VALIDATE、NOVALIDATE组合出四种模式。DISABLE NOVALIDATE是彻底关闭,ENABLE VALIDATE是最严格模式(校验存量数据和增量数据),而ENABLE NOVALIDATE只对新的DML生效,常用于历史数据有脏数据但又想阻止新的违规数据进入的场景。外键还有一个NOVALIDATE的孪生特性:可以在定义外键时加上DISABLE NOVALIDATE,让约束处于定义存在但不生效的状态。
日常排查问题时,查看表上有哪些约束离不开数据字典视图user_constraints和user_cons_columns:
-- 查看某张表的所有约束及其状态 SELECT constraint_name, constraint_type, status, validated FROM user_constraints WHERE table_name = 'T_ORDER'; -- 查看约束具体作用在哪些列上 SELECT uc.constraint_name, ucc.column_name, ucc.position FROM user_constraints uc JOIN user_cons_columns ucc ON uc.constraint_name = ucc.constraint_name WHERE uc.table_name = 'T_ORDER' ORDER BY uc.constraint_name, ucc.position;
其中constraint_type字段用一个字母表示类型:P代表主键、U代表唯一、R代表外键、C代表检查和非空、V和O分别对应视图的WITH CHECK OPTION和READ ONLY。掌握这两个视图的查询,就能快速定位任何表上的约束情况。
约束使用的几点实践建议
第一,所有约束都应显式命名,建议采用固定前缀规范,比如pk_、fk_、uk_、chk_加上表名或列名,让约束名本身就能表达业务含义。第二,不要滥用外键的ON DELETE CASCADE,级联删除在大数据量表上可能触发意外的批量删除,对于核心业务表更稳妥的做法是使用逻辑删除标志位。第三,禁用约束后一定要记得重新启用,最好将禁用和启用操作写在同一个维护脚本中,避免约束长期处于失效状态而无人察觉。
另外,约束与索引的关系也值得关注。Oracle在创建主键和唯一约束时会自动在同名列上创建唯一索引,若该列上已存在可用索引则会直接复用。删除约束时这个自动创建的索引会一并删除,如果你还需要该索引维持查询性能,可以改用KEEP INDEX子句保留索引。理解这些细节,才能在schema设计和日常运维中把约束用得既规范又高效。
修改时间:2026-09-03 13:29:01