Oracle 事务与约束详解
事务和约束是 Oracle 数据库数据完整性和一致性 的两大基石。事务确保操作的原子性 ,约束确保数据的业务规则。
一、事务(Transaction)基础
1.1 ACID 特性
| 特性 | 说明 | Oracle 实现 |
|---|---|---|
| 原子性 (Atomicity) | 要么全部成功,要么全部回滚 | Undo 表空间、回滚段 |
| 一致性 (Consistency) | 事务前后数据状态合法 | 约束检查、触发器 |
| 隔离性 (Isolation) | 并发事务互不干扰 | 锁机制、MVCC |
| 持久性 (Durability) | 提交后永久保存 | Redo 日志、数据文件 |
1.2 事务生命周期
sql
-- 1. 开始事务(Oracle 自动开始)
UPDATE employees SET salary = 8000 WHERE employee_id = 101;
-- 2. 执行 DML 操作
INSERT INTO departments VALUES (280, 'Cloud Engineering', 1700);
-- 3. 保存点(可选)
SAVEPOINT sp1;
-- 4. 继续操作
DELETE FROM employees WHERE employee_id = 999;
-- 5. 回滚到保存点
ROLLBACK TO sp1; -- 仅撤销 DELETE
-- 6. 提交事务
COMMIT; -- 所有更改永久生效
-- 或回滚整个事务
ROLLBACK; -- 撤销所有更改
二、Oracle 事务控制语句
2.1 显式控制
sql
-- COMMIT:提交事务
COMMIT [WORK] [COMMENT '注释']; -- COMMENT 用于分布式事务
-- ROLLBACK:回滚事务
ROLLBACK [WORK];
ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称; -- 部分回滚
-- SAVEPOINT:设置保存点
SAVEPOINT 保存点名称;
-- SET TRANSACTION:设置事务属性
SET TRANSACTION READ ONLY; -- 只读事务
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- 设置隔离级别
SET TRANSACTION NAME '订单处理'; -- 命名事务
2.2 隐式提交
以下操作会自动提交当前事务:
- DDL 语句 :
CREATE,ALTER,DROP,TRUNCATE - DCL 语句 :
GRANT,REVOKE - 退出 SQL*Plus(正常退出)
陷阱示例:
sql
UPDATE employees SET salary = 9000 WHERE employee_id = 101;
CREATE TABLE temp_table (id NUMBER); -- 自动提交 UPDATE!
ROLLBACK; -- 已无法回滚 salary 的修改
2.3 自治事务
在触发器或存储过程中独立提交,不影响主事务。
sql
CREATE OR REPLACE PROCEDURE log_error(err_msg VARCHAR2) IS
PRAGMA AUTONOMOUS_TRANSACTION; -- 声明自治事务
BEGIN
INSERT INTO error_log (message, log_date)
VALUES (err_msg, SYSDATE);
COMMIT; -- 独立提交
END;
/
三、约束(Constraint)类型
3.1 约束分类总览
sql
CREATE TABLE employees (
employee_id NUMBER(6) CONSTRAINT emp_pk PRIMARY KEY, -- 主键约束
email VARCHAR2(25) CONSTRAINT emp_email_uk UNIQUE, -- 唯一约束
first_name VARCHAR2(20) NOT NULL, -- 非空约束
last_name VARCHAR2(25) NOT NULL,
salary NUMBER(8,2) CONSTRAINT emp_salary_ck CHECK (salary > 0), -- 检查约束
hire_date DATE DEFAULT SYSDATE,
department_id NUMBER(4) CONSTRAINT emp_dept_fk
REFERENCES departments(department_id) -- 外键约束
);
3.2 主键约束(PRIMARY KEY)
作用:唯一标识一行,非空且唯一
sql
-- 建表时创建
CREATE TABLE products (
product_id NUMBER PRIMARY KEY,
name VARCHAR2(100)
);
-- 表级约束(推荐,可命名)
CREATE TABLE products (
product_id NUMBER,
name VARCHAR2(100),
CONSTRAINT prod_pk PRIMARY KEY (product_id)
);
-- 组合主键
CREATE TABLE order_items (
order_id NUMBER,
item_id NUMBER,
quantity NUMBER,
CONSTRAINT pk_order_items PRIMARY KEY (order_id, item_id)
);
-- 修改表添加主键
ALTER TABLE products ADD CONSTRAINT prod_pk PRIMARY KEY (product_id);
-- 禁用主键(数据仓库批量加载时)
ALTER TABLE products DISABLE CONSTRAINT prod_pk;
ALTER TABLE products ENABLE CONSTRAINT prod_pk;
3.3 外键约束(FOREIGN KEY)
作用:维护表间引用完整性
sql
-- 基础外键
CREATE TABLE employees (
employee_id NUMBER PRIMARY KEY,
department_id NUMBER CONSTRAINT emp_dept_fk
REFERENCES departments(department_id)
);
-- 级联删除
ALTER TABLE employees
ADD CONSTRAINT emp_dept_fk FOREIGN KEY (department_id)
REFERENCES departments(department_id)
ON DELETE CASCADE; -- 删除部门时,员工自动删除
-- 级联置空
ALTER TABLE employees
ADD CONSTRAINT emp_dept_fk FOREIGN KEY (department_id)
REFERENCES departments(department_id)
ON DELETE SET NULL; -- 删除部门时,员工 dept_id 设为 NULL
-- 外键限制
- 外键列必须是主键或唯一键
- 外键列类型必须与引用列一致
- 外键可以引用同一表(自引用)
外键检查时机:
sql
-- IMMEDIATE(默认):每行 DML 后立即检查
-- DEFERRED:事务提交时检查(用于循环依赖)
ALTER TABLE employees
ADD CONSTRAINT emp_dept_fk FOREIGN KEY (department_id)
REFERENCES departments(department_id)
DEFERRABLE INITIALLY DEFERRED; -- 可延迟约束
3.4 唯一约束(UNIQUE)
作用:确保列值唯一,但允许多个 NULL
sql
-- 列级唯一约束
CREATE TABLE users (
user_id NUMBER PRIMARY KEY,
email VARCHAR2(100) UNIQUE
);
-- 表级唯一约束(可命名)
CREATE TABLE users (
user_id NUMBER PRIMARY KEY,
email VARCHAR2(100),
CONSTRAINT users_email_uk UNIQUE (email)
);
-- 组合唯一约束
CREATE TABLE user_roles (
user_id NUMBER,
role_id NUMBER,
CONSTRAINT uk_user_roles UNIQUE (user_id, role_id)
);
3.5 非空约束(NOT NULL)
作用:列不能为 NULL
sql
-- 建表时定义
CREATE TABLE products (
product_id NUMBER NOT NULL,
name VARCHAR2(100) NOT NULL
);
-- 修改表添加非空约束
ALTER TABLE products MODIFY (name NOT NULL);
-- 删除非空约束(通过 MODIFY)
ALTER TABLE products MODIFY (name NULL);
3.6 检查约束(CHECK)
作用:强制列值满足条件
sql
-- 检查 salary 范围
CREATE TABLE employees (
employee_id NUMBER PRIMARY KEY,
salary NUMBER(8,2) CHECK (salary BETWEEN 1000 AND 50000)
);
-- 复杂检查约束
CREATE TABLE employees (
employee_id NUMBER PRIMARY KEY,
salary NUMBER(8,2),
commission_pct NUMBER(2,2),
CONSTRAINT chk_salary_commission CHECK (
(salary > 0 AND commission_pct IS NULL) OR
(salary > 5000 AND commission_pct BETWEEN 0 AND 0.5)
)
);
-- 修改表添加检查约束
ALTER TABLE employees ADD CONSTRAINT chk_hire_date CHECK (hire_date <= SYSDATE);
3.7 默认值(DEFAULT)
作用:插入时未指定值则使用默认值
sql
CREATE TABLE orders (
order_id NUMBER PRIMARY KEY,
order_date DATE DEFAULT SYSDATE, -- 当前日期
status VARCHAR2(20) DEFAULT 'NEW',
created_by VARCHAR2(50) DEFAULT USER -- 当前用户
);
-- 插入时使用默认值
INSERT INTO orders (order_id) VALUES (101); -- order_date=SYSDATE, status='NEW'
四、约束状态管理
4.1 约束状态组合
每个约束有四种状态组合:
- ENABLE VALIDATE:默认状态,启用且验证现有数据(最严格)
- ENABLE NOVALIDATE:启用但不验证现有数据(仅对新数据生效)
- DISABLE VALIDATE:禁用但验证现有数据(阻止新DML)
- DISABLE NOVALIDATE:禁用且不验证(数据仓库场景)
sql
-- 查看约束状态
SELECT constraint_name, status, validated
FROM user_constraints
WHERE table_name = 'EMPLOYEES';
-- 禁用约束(数据加载时提升性能)
ALTER TABLE employees DISABLE CONSTRAINT emp_salary_ck;
-- 启用并验证(加载后重新启用)
ALTER TABLE employees ENABLE VALIDATE CONSTRAINT emp_salary_ck;
-- 启用但不验证(存在脏数据时)
ALTER TABLE employees ENABLE NOVALIDATE CONSTRAINT emp_salary_ck;
-- 延迟约束检查(事务级)
ALTER TABLE employees MODIFY CONSTRAINT emp_dept_fk
DEFERRABLE INITIALLY IMMEDIATE;
-- 在事务中临时延迟
SET CONSTRAINT emp_dept_fk DEFERRED;
INSERT INTO employees (...) VALUES (...); -- 暂时违反外键
UPDATE departments SET department_id = ...; -- 使引用有效
COMMIT; -- 提交时检查
五、事务与约束的交互
5.1 约束检查时机
IMMEDIATE 约束(默认):
sql
-- 每行 DML 后立即检查
UPDATE employees SET department_id = 999 WHERE employee_id = 101;
-- 报错:ORA-02291: 违反外键约束
DEFERRED 约束:
sql
-- 事务提交时检查
SET CONSTRAINT emp_dept_fk DEFERRED;
INSERT INTO employees (employee_id, department_id) VALUES (999, 999); -- 成功
-- 在提交前插入被引用的部门
INSERT INTO departments (department_id) VALUES (999);
COMMIT; -- 检查通过,成功提交
5.2 违反约束的事务回滚
sql
BEGIN
INSERT INTO employees (employee_id, salary) VALUES (999, -100); -- 违反检查约束
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('违反约束: ' || SQLERRM);
ROLLBACK; -- 回滚整个事务
END;
/
5.3 约束与性能
sql
-- 批量插入时禁用约束可提升性能
ALTER TABLE employees DISABLE CONSTRAINT emp_salary_ck;
-- 批量插入
INSERT INTO employees SELECT * FROM employees_staging;
-- 启用并验证
ALTER TABLE employees ENABLE VALIDATE CONSTRAINT emp_salary_ck;
六、高级主题
6.1 约束异常处理
sql
-- 创建异常表
EXEC DBMS_ERRLOG.CREATE_ERROR_LOG('EMPLOYEES', 'EMPLOYEES_ERRLOG');
-- 批量插入时记录异常
INSERT INTO employees
SELECT * FROM employees_temp
LOG ERRORS INTO EMPLOYEES_ERRLOG ('Batch Load') REJECT LIMIT UNLIMITED;
-- 查看异常记录
SELECT ora_err_number$, ora_err_mesg$, first_name
FROM EMPLOYEES_ERRLOG;
6.2 视图上的约束
sql
-- WITH CHECK OPTION:防止通过视图插入不符合视图条件的数据
CREATE VIEW high_emp AS
SELECT * FROM employees WHERE salary > 10000
WITH CHECK OPTION;
-- 尝试插入 low salary 将失败
INSERT INTO high_emp VALUES (..., 5000); -- ORA-01402: 违反 CHECK OPTION
6.3 物化视图约束
sql
-- 物化视图可添加约束
CREATE MATERIALIZED VIEW emp_mv
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
ENABLE QUERY REWRITE
AS
SELECT employee_id, salary FROM employees;
-- 添加主键约束
ALTER MATERIALIZED VIEW emp_mv ADD CONSTRAINT emp_mv_pk PRIMARY KEY (employee_id);
七、最佳实践与避坑指南
7.1 约束设计最佳实践
✅ 命名规范 :表名_列名_约束类型(如 emp_salary_ck)
✅ 尽早定义 :建表时定义约束,避免后期数据污染
✅ 优先使用 NOT NULL :约束开销最小,性能影响最低
✅ 外键加索引 :外键列必须创建索引,避免全表锁
✅ 检查约束简单 :避免复杂函数,影响 DML 性能
✅ 文档化约束:记录业务含义,便于维护
7.2 常见陷阱
❌ 外键无索引 :导致子表全表锁,并发性能差
❌ 循环外键 :A→B→C→A,设计缺陷
❌ 延迟约束滥用 :增加复杂性,难以调试
❌ CHECK 中使用函数 :导致无法创建函数索引
❌ 大批量 DML 不禁用约束:性能慢且产生大量 UNDO
7.3 外键索引示例
sql
-- 外键列必须创建索引
CREATE TABLE order_items (
order_id NUMBER,
item_id NUMBER,
CONSTRAINT fk_order_items FOREIGN KEY (order_id) REFERENCES orders(order_id)
);
-- 创建索引(否则删除父表记录时会锁整个子表)
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
八、查询与维护
8.1 查询约束信息
sql
-- 查询表的所有约束
SELECT
constraint_name,
constraint_type,
search_condition,
status,
validated
FROM user_constraints
WHERE table_name = 'EMPLOYEES'
ORDER BY constraint_type;
-- 约束类型说明:
-- P: Primary Key, U: Unique, R: Foreign Key, C: Check
-- 查询外键引用关系
SELECT
a.constraint_name,
a.table_name,
b.column_name,
a.r_constraint_name,
c.table_name AS ref_table,
d.column_name AS ref_column
FROM user_constraints a
JOIN user_cons_columns b ON a.constraint_name = b.constraint_name
JOIN user_constraints c ON a.r_constraint_name = c.constraint_name
JOIN user_cons_columns d ON c.constraint_name = d.constraint_name
WHERE a.constraint_type = 'R'
AND a.table_name = 'EMPLOYEES';
8.2 批量操作约束
sql
-- 导出约束定义(数据迁移时使用)
SELECT DBMS_METADATA.GET_DDL('CONSTRAINT', constraint_name)
FROM user_constraints
WHERE table_name = 'EMPLOYEES';
-- 禁用表的所有约束
BEGIN
FOR c IN (SELECT constraint_name FROM user_constraints WHERE table_name = 'EMPLOYEES') LOOP
EXECUTE IMMEDIATE 'ALTER TABLE employees DISABLE CONSTRAINT ' || c.constraint_name;
END LOOP;
END;
/
九、总结
事务 vs 约束对比
| 维度 | 事务 | 约束 |
|---|---|---|
| 作用 | 保证操作原子性 | 保证数据合法性 |
| 粒度 | 会话级(跨语句) | 行级(单条数据) |
| 生命周期 | 显式/隐式开始和结束 | 持久化在数据字典中 |
| 性能影响 | UNDO/REDO 开销 | 索引维护、检查开销 |
| 典型场景 | 转账、订单创建 | 主键唯一、外键关联 |
设计原则
- 事务要短:避免长事务占用资源
- 约束要早:建表时定义,数据入库前校验
- 外键要索引:避免性能陷阱
- 延迟要谨慎:仅用于循环依赖场景
掌握事务和约束,是构建健壮、可靠、高性能 Oracle 应用的基石。