外观
Oracle 表管理
表的创建和设计
表的创建
基本创建语法:
sql
-- 创建基本表
CREATE TABLE employees (
employee_id NUMBER(6) PRIMARY KEY,
first_name VARCHAR2(20),
last_name VARCHAR2(25) NOT NULL,
email VARCHAR2(100) UNIQUE,
phone_number VARCHAR2(20),
hire_date DATE DEFAULT SYSDATE,
job_id VARCHAR2(10),
salary NUMBER(8,2),
commission_pct NUMBER(2,2),
manager_id NUMBER(6),
department_id NUMBER(4)
);
-- 使用存储参数创建表
CREATE TABLE orders (
order_id NUMBER(12) PRIMARY KEY,
customer_id NUMBER(6),
order_date DATE DEFAULT SYSDATE,
status VARCHAR2(15),
comments VARCHAR2(4000)
) PCTFREE 10 PCTUSED 40
INITRANS 2 MAXTRANS 255
TABLESPACE users
STORAGE (
INITIAL 64K
NEXT 1M
MAXEXTENTS UNLIMITED
PCTINCREASE 0
);表的设计原则
- 数据类型选择:选择合适的数据类型,避免过大或过小
- 约束设计:合理使用主键、外键、唯一约束、检查约束和非空约束
- 存储参数:根据表的使用场景设置合适的存储参数
- 表空间分配:将表分配到合适的表空间,考虑性能和管理需求
- 分区策略:对于大型表,考虑使用分区表提高性能和可管理性
- 命名规范:使用清晰、一致的命名规范,便于管理和维护
表的设计最佳实践
- 规范化与反规范化平衡:根据业务需求和性能要求,在规范化和反规范化之间取得平衡
- 避免过度设计:不要创建不必要的列或约束
- 考虑未来增长:设计时考虑数据量的增长,预留足够的空间和扩展性
- 测试设计方案:在生产环境部署前,在测试环境验证表设计的性能和可维护性
- 文档化设计:记录表的设计思路、字段含义和约束条件,便于后续维护
表结构管理
表结构修改
添加列:
sql
-- 添加新列
ALTER TABLE employees ADD (
middle_name VARCHAR2(20),
preferred_name VARCHAR2(50)
);
-- 添加带默认值的列
ALTER TABLE employees ADD (
active_flag VARCHAR2(1) DEFAULT 'Y' NOT NULL
);修改列:
sql
-- 修改列数据类型
ALTER TABLE employees MODIFY (
phone_number VARCHAR2(30)
);
-- 修改列默认值
ALTER TABLE employees MODIFY (
hire_date DATE DEFAULT SYSDATE NOT NULL
);删除列:
sql
-- 删除列
ALTER TABLE employees DROP COLUMN middle_name;
-- 标记列为unused(快速删除)
ALTER TABLE employees SET UNUSED COLUMN preferred_name;
-- 稍后清理unused列
ALTER TABLE employees DROP UNUSED COLUMNS;约束管理
添加约束:
sql
-- 添加主键约束
ALTER TABLE employees ADD CONSTRAINT emp_pk PRIMARY KEY (employee_id);
-- 添加外键约束
ALTER TABLE employees ADD CONSTRAINT emp_dept_fk
FOREIGN KEY (department_id) REFERENCES departments(department_id);
-- 添加唯一约束
ALTER TABLE employees ADD CONSTRAINT emp_email_uk UNIQUE (email);
-- 添加检查约束
ALTER TABLE employees ADD CONSTRAINT emp_salary_ck
CHECK (salary > 0);禁用和启用约束:
sql
-- 禁用约束
ALTER TABLE employees DISABLE CONSTRAINT emp_dept_fk;
-- 启用约束
ALTER TABLE employees ENABLE CONSTRAINT emp_dept_fk;
-- 启用约束并验证数据
ALTER TABLE employees ENABLE VALIDATE CONSTRAINT emp_salary_ck;删除约束:
sql
-- 删除约束
ALTER TABLE employees DROP CONSTRAINT emp_email_uk;
-- 级联删除约束
ALTER TABLE employees DROP PRIMARY KEY CASCADE;索引管理
创建索引:
sql
-- 创建B树索引
CREATE INDEX emp_last_name_idx ON employees(last_name);
-- 创建唯一索引
CREATE UNIQUE INDEX emp_email_idx ON employees(email);
-- 创建位图索引(适用于低基数列)
CREATE BITMAP INDEX emp_active_flag_idx ON employees(active_flag);
-- 创建函数索引
CREATE INDEX emp_hire_year_idx ON employees(EXTRACT(YEAR FROM hire_date));重建索引:
sql
-- 重建索引
ALTER INDEX emp_last_name_idx REBUILD;
-- 在线重建索引(不阻塞DML操作)
ALTER INDEX emp_last_name_idx REBUILD ONLINE;
-- 重建索引并修改存储参数
ALTER INDEX emp_last_name_idx REBUILD
TABLESPACE index_ts
STORAGE (INITIAL 1M NEXT 1M);删除索引:
sql
-- 删除索引
DROP INDEX emp_last_name_idx;表数据管理
数据插入
基本插入:
sql
-- 插入单行数据
INSERT INTO employees (employee_id, first_name, last_name, email, hire_date, job_id, salary)
VALUES (1001, 'John', 'Doe', 'john.doe@example.com', SYSDATE, 'IT_PROG', 6000);
-- 插入多行数据
INSERT ALL
INTO employees (employee_id, first_name, last_name, email, job_id, salary)
VALUES (1002, 'Jane', 'Smith', 'jane.smith@example.com', 'HR_REP', 5000)
INTO employees (employee_id, first_name, last_name, email, job_id, salary)
VALUES (1003, 'Bob', 'Johnson', 'bob.johnson@example.com', 'SA_REP', 7000)
SELECT * FROM DUAL;
-- 从其他表插入数据
INSERT INTO employees_backup (employee_id, first_name, last_name, email)
SELECT employee_id, first_name, last_name, email
FROM employees
WHERE department_id = 10;数据更新
基本更新:
sql
-- 更新单行数据
UPDATE employees
SET salary = salary * 1.1,
job_id = 'IT_MANAGER'
WHERE employee_id = 1001;
-- 更新多行数据
UPDATE employees
SET commission_pct = 0.1
WHERE job_id LIKE '%_REP';
-- 使用子查询更新
UPDATE employees e
SET salary = (
SELECT AVG(salary) * 1.05
FROM employees
WHERE job_id = e.job_id
)
WHERE job_id = 'IT_PROG';数据删除
基本删除:
sql
-- 删除单行数据
DELETE FROM employees
WHERE employee_id = 1001;
-- 删除多行数据
DELETE FROM employees
WHERE department_id = 10;
-- 截断表(快速删除所有数据)
TRUNCATE TABLE employees_backup;
-- 截断分区表的特定分区
ALTER TABLE sales TRUNCATE PARTITION sales_q1_2023;数据导入导出
使用数据泵导出:
bash
# 导出指定表
expdp system/password@orcl tables=employees,departments
dumpfile=hr_tables.dmp
directory=DATA_PUMP_DIR;
# 导出表的特定数据
expdp system/password@orcl tables=employees
dumpfile=emp_active.dmp
directory=DATA_PUMP_DIR
query="WHERE active_flag='Y'";使用数据泵导入:
bash
# 导入表
impdp system/password@orcl tables=employees
dumpfile=hr_tables.dmp
directory=DATA_PUMP_DIR;
# 导入表并重命名
impdp system/password@orcl tables=employees
dumpfile=hr_tables.dmp
directory=DATA_PUMP_DIR
remap_table=employees:employees_new;
# 仅导入表结构
impdp system/password@orcl tables=employees
dumpfile=hr_tables.dmp
directory=DATA_PUMP_DIR
content=metadata_only;分区表管理
分区表类型
范围分区:
sql
-- 创建范围分区表
CREATE TABLE sales (
sale_id NUMBER(12) PRIMARY KEY,
sale_date DATE NOT NULL,
customer_id NUMBER(6),
amount NUMBER(10,2),
region VARCHAR2(20)
)
PARTITION BY RANGE (sale_date) (
PARTITION sales_q1_2023 VALUES LESS THAN (TO_DATE('2023-04-01', 'YYYY-MM-DD')),
PARTITION sales_q2_2023 VALUES LESS THAN (TO_DATE('2023-07-01', 'YYYY-MM-DD')),
PARTITION sales_q3_2023 VALUES LESS THAN (TO_DATE('2023-10-01', 'YYYY-MM-DD')),
PARTITION sales_q4_2023 VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD'))
);列表分区:
sql
-- 创建列表分区表
CREATE TABLE customers (
customer_id NUMBER(6) PRIMARY KEY,
customer_name VARCHAR2(50),
region VARCHAR2(20),
status VARCHAR2(10)
)
PARTITION BY LIST (region) (
PARTITION region_asia VALUES ('ASIA', 'JAPAN', 'CHINA'),
PARTITION region_europe VALUES ('EUROPE', 'FRANCE', 'GERMANY'),
PARTITION region_americas VALUES ('AMERICAS', 'USA', 'CANADA'),
PARTITION region_rest VALUES (DEFAULT)
);哈希分区:
sql
-- 创建哈希分区表
CREATE TABLE orders (
order_id NUMBER(12) PRIMARY KEY,
customer_id NUMBER(6),
order_date DATE,
total_amount NUMBER(10,2)
)
PARTITION BY HASH (order_id)
PARTITIONS 8;复合分区:
sql
-- 创建范围-哈希复合分区表
CREATE TABLE sales_detail (
sale_id NUMBER(12),
sale_date DATE,
product_id NUMBER(6),
quantity NUMBER(5),
amount NUMBER(10,2)
)
PARTITION BY RANGE (sale_date)
SUBPARTITION BY HASH (product_id)
SUBPARTITIONS 4 (
PARTITION p2023_q1 VALUES LESS THAN (TO_DATE('2023-04-01', 'YYYY-MM-DD')),
PARTITION p2023_q2 VALUES LESS THAN (TO_DATE('2023-07-01', 'YYYY-MM-DD')),
PARTITION p2023_q3 VALUES LESS THAN (TO_DATE('2023-10-01', 'YYYY-MM-DD')),
PARTITION p2023_q4 VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD'))
);分区管理
添加分区:
sql
-- 为范围分区表添加新分区
ALTER TABLE sales ADD PARTITION sales_q1_2024
VALUES LESS THAN (TO_DATE('2024-04-01', 'YYYY-MM-DD'));
-- 为列表分区表添加新分区
ALTER TABLE customers ADD PARTITION region_africa
VALUES ('AFRICA', 'SOUTH AFRICA', 'EGYPT');删除分区:
sql
-- 删除分区(同时删除数据)
ALTER TABLE sales DROP PARTITION sales_q1_2023;
-- 截断分区(保留分区结构,删除数据)
ALTER TABLE sales TRUNCATE PARTITION sales_q2_2023;
-- 合并分区
ALTER TABLE sales MERGE PARTITIONS sales_q3_2023, sales_q4_2023
INTO PARTITION sales_h2_2023;
-- 拆分分区
ALTER TABLE sales SPLIT PARTITION sales_h2_2023
AT (TO_DATE('2023-10-01', 'YYYY-MM-DD'))
INTO (PARTITION sales_q3_2023, PARTITION sales_q4_2023);交换分区:
sql
-- 创建交换表
CREATE TABLE sales_q1_2024_temp (
sale_id NUMBER(12) PRIMARY KEY,
sale_date DATE NOT NULL,
customer_id NUMBER(6),
amount NUMBER(10,2),
region VARCHAR2(20)
);
-- 加载数据到交换表
INSERT INTO sales_q1_2024_temp
SELECT * FROM sales_staging WHERE sale_date >= TO_DATE('2024-01-01', 'YYYY-MM-DD');
-- 交换分区
ALTER TABLE sales EXCHANGE PARTITION sales_q1_2024
WITH TABLE sales_q1_2024_temp
WITHOUT VALIDATION;临时表管理
临时表类型
会话级临时表:
sql
-- 创建会话级临时表
CREATE GLOBAL TEMPORARY TABLE temp_employees (
employee_id NUMBER(6),
first_name VARCHAR2(20),
last_name VARCHAR2(25),
department_id NUMBER(4)
) ON COMMIT PRESERVE ROWS;事务级临时表:
sql
-- 创建事务级临时表
CREATE GLOBAL TEMPORARY TABLE temp_orders (
order_id NUMBER(12),
customer_id NUMBER(6),
order_date DATE
) ON COMMIT DELETE ROWS;临时表使用
会话级临时表使用:
sql
-- 插入数据
INSERT INTO temp_employees
SELECT employee_id, first_name, last_name, department_id
FROM employees
WHERE department_id = 10;
-- 提交事务(数据保留)
COMMIT;
-- 查询数据(仍可访问)
SELECT * FROM temp_employees;
-- 会话结束时数据自动清除事务级临时表使用:
sql
-- 插入数据
INSERT INTO temp_orders
SELECT order_id, customer_id, order_date
FROM orders
WHERE order_date = SYSDATE;
-- 提交事务(数据自动清除)
COMMIT;
-- 查询数据(已清除)
SELECT * FROM temp_orders;临时表最佳实践
- 适用于中间结果:使用临时表存储复杂查询的中间结果
- 减少 redo 生成:临时表的数据变更不生成 redo 日志,提高性能
- 会话隔离:不同会话的临时表数据相互隔离,避免冲突
- 合理使用:不要过度使用临时表,避免内存和临时表空间过度消耗
- 索引策略:根据查询需求为临时表创建适当的索引
- 统计信息:临时表的统计信息不会自动收集,需要手动收集或使用动态采样
表的监控和维护
表空间使用监控
检查表空间使用情况:
sql
-- 检查表空间使用情况
SELECT tablespace_name,
SUM(bytes)/1024/1024 AS total_mb,
SUM(bytes - blocks * block_size/1024/1024) AS used_mb,
SUM(blocks * block_size/1024/1024) AS free_mb,
ROUND((SUM(bytes) - SUM(blocks * block_size/1024/1024))/SUM(bytes)*100, 2) AS used_percent
FROM dba_free_space
GROUP BY tablespace_name;
-- 检查表的大小
SELECT segment_name,
segment_type,
SUM(bytes)/1024/1024 AS size_mb
FROM dba_segments
WHERE owner = 'SCOTT' AND segment_type = 'TABLE'
GROUP BY segment_name, segment_type
ORDER BY size_mb DESC;表的统计信息
收集表的统计信息:
sql
-- 收集表的统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'SCOTT',
tabname => 'EMPLOYEES',
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
method_opt => 'FOR ALL COLUMNS SIZE AUTO',
cascade => TRUE,
no_invalidate => FALSE
);
-- 收集模式下所有表的统计信息
EXEC DBMS_STATS.GATHER_SCHEMA_STATS(
ownname => 'SCOTT',
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE,
method_opt => 'FOR ALL COLUMNS SIZE AUTO',
cascade => TRUE
);查看统计信息:
sql
-- 查看表的统计信息
SELECT table_name, num_rows, blocks, avg_row_len
FROM user_tables
WHERE table_name = 'EMPLOYEES';
-- 查看列的统计信息
SELECT column_name, num_distinct, density, num_nulls
FROM user_tab_col_statistics
WHERE table_name = 'EMPLOYEES';表的碎片整理
检查表的碎片:
sql
-- 检查表的碎片情况
SELECT table_name,
num_rows,
blocks,
empty_blocks,
avg_space,
chain_cnt
FROM user_tables
WHERE table_name = 'EMPLOYEES';
-- 检查表的行迁移和链接
SELECT table_name, chain_cnt
FROM user_tables
WHERE chain_cnt > 0
ORDER BY chain_cnt DESC;整理表的碎片:
sql
-- 使用ALTER TABLE MOVE整理碎片
ALTER TABLE employees MOVE TABLESPACE users;
-- 在线整理碎片(Oracle 12c+)
ALTER TABLE employees MOVE ONLINE TABLESPACE users;
-- 整理后重建索引
ALTER INDEX emp_pk REBUILD;
ALTER INDEX emp_last_name_idx REBUILD;
-- 使用SHRINK SPACE整理碎片(需要启用行移动)
ALTER TABLE employees ENABLE ROW MOVEMENT;
ALTER TABLE employees SHRINK SPACE COMPACT;
ALTER TABLE employees SHRINK SPACE;表的性能优化
表访问优化
全表扫描优化:
- 对于小表,全表扫描可能比索引扫描更高效
- 合理设置 DB_FILE_MULTIBLOCK_READ_COUNT 参数
- 使用并行查询加速大表的全表扫描
索引访问优化:
- 为频繁查询的列创建索引
- 避免在索引列上使用函数
- 考虑使用复合索引覆盖查询
- 定期重建碎片化的索引
分区表访问优化:
- 使用分区剪枝减少需要扫描的数据量
- 为分区表选择合适的分区键
- 考虑使用本地分区索引提高性能
表设计优化
数据类型优化:
- 使用最小必要的数据类型
- 对于固定长度的列,使用 CHAR 类型
- 对于可变长度的列,使用 VARCHAR2 类型
- 避免使用 LONG 和 LONG RAW 类型,改用 CLOB 和 BLOB
存储参数优化:
- 为表设置合适的 PCTFREE 和 PCTUSED
- 对于频繁更新的表,设置较高的 PCTFREE
- 对于静态表,设置较低的 PCTFREE
- 使用自动段空间管理(ASSM)
分区策略优化:
- 对于时间序列数据,使用范围分区
- 对于离散值数据,使用列表分区
- 对于随机分布的数据,使用哈希分区
- 对于复杂查询模式,考虑使用复合分区
表维护优化
批量操作优化:
- 使用批量绑定减少上下文切换
- 对于大批量插入,考虑使用 DIRECT PATH INSERT
- 对于大批量更新,考虑使用 MERGE 语句
- 对于大批量删除,考虑使用 TRUNCATE 或分区交换
并行操作:
sql
-- 并行插入
INSERT /*+ PARALLEL(employees, 4) */ INTO employees
SELECT /*+ PARALLEL(emp_staging, 4) */ *
FROM emp_staging;
-- 并行更新
UPDATE /*+ PARALLEL(employees, 4) */ employees
SET salary = salary * 1.05
WHERE department_id = 10;
-- 并行删除
DELETE /*+ PARALLEL(employees, 4) */ FROM employees
WHERE hire_date < TO_DATE('2020-01-01', 'YYYY-MM-DD');
-- 并行创建表
CREATE TABLE employees_backup PARALLEL 4 AS
SELECT * FROM employees;版本差异考虑
Oracle 11g
- 支持基本的分区表功能
- 临时表功能相对简单
- 表压缩功能有限
- 统计信息收集功能相对基础
Oracle 12c
- 引入可插拔数据库(PDB)中的表管理
- 增强了分区表功能,支持间隔分区
- 引入临时表的增强功能
- 改进了统计信息收集机制
- 支持在线表重定义的增强功能
Oracle 19c
- 进一步优化了分区表性能
- 增强了表压缩功能
- 改进了统计信息自动收集
- 引入了更多自动化的表管理功能
- 支持区块链表(用于不可篡改数据)
Oracle 21c
- 引入了混合分区表
- 增强了临时表的功能
- 改进了表的在线操作性能
- 支持更多的表级安全特性
- 引入了自动表优化功能
常见问题(FAQ)
Q1: 如何选择合适的表分区策略?
A1: 选择表分区策略的考虑因素:
- 数据分布:根据数据的分布特性选择分区类型
- 查询模式:考虑常见查询的过滤条件
- 维护需求:考虑数据加载、删除和归档的需求
- 性能目标:根据性能要求选择合适的分区粒度
- 存储管理:考虑存储布局和管理的便利性
Q2: 临时表和普通表的区别是什么?
A2: 临时表和普通表的主要区别:
- 数据隔离:临时表的数据与会话或事务隔离
- 日志生成:临时表的操作不生成 redo 日志
- 存储位置:临时表的数据存储在临时表空间
- 生命周期:临时表的数据在会话结束或事务提交后自动清除
- 性能特性:临时表的操作通常比普通表更快
Q3: 如何处理表的行迁移和行链接?
A3: 处理行迁移和行链接的方法:
- 预防措施:设置合适的 PCTFREE 参数
- 检测方法:使用 ANALYZE TABLE 或查询 user_tables.chain_cnt
- 解决方法:使用 ALTER TABLE MOVE 或 SHRINK SPACE 整理碎片
- 后续处理:整理后重建相关索引
- 监控机制:定期检查行迁移和链接的情况
Q4: 如何优化大表的查询性能?
A4: 优化大表查询性能的方法:
- 分区策略:使用分区表减少扫描的数据量
- 索引设计:创建合适的索引,考虑覆盖索引
- 查询优化:编写高效的 SQL 语句,避免全表扫描
- 并行查询:对于大型查询使用并行执行
- 统计信息:确保统计信息的准确性
- 存储优化:使用表压缩减少存储开销
Q5: 表空间不足时如何处理?
A5: 表空间不足的处理方法:
- 添加数据文件:为表空间添加新的数据文件
- 扩展数据文件:增大现有数据文件的大小
- 启用自动扩展:设置数据文件自动扩展
- 表空间清理:删除或归档不需要的数据
- 分区策略:考虑使用分区表进行数据管理
- 存储重组织:将表移动到其他表空间
Q6: 如何安全地重命名表?
A6: 重命名表的步骤:
- 检查依赖对象:确认没有其他对象依赖该表
- 获取锁:确保表上没有活动的事务
- 执行重命名:使用 ALTER TABLE RENAME TO 语句
- 更新依赖对象:更新视图、存储过程等依赖对象
- 更新应用代码:修改引用该表的应用代码
- 测试功能:验证重命名后所有功能正常
Q7: 如何处理表的统计信息过期问题?
A7: 处理统计信息过期的方法:
- 自动收集:启用自动统计信息收集任务
- 手动收集:定期手动收集统计信息
- 增量收集:对于分区表使用增量统计信息收集
- 动态采样:对于临时表使用动态采样
- 统计信息锁定:对于稳定的表锁定统计信息
- 执行计划管理:使用 SQL 计划管理稳定执行计划
Q8: 如何监控表的增长趋势?
A8: 监控表增长趋势的方法:
- 定期收集:定期收集表的大小和行数信息
- 历史记录:将收集的数据存储在历史表中
- 趋势分析:分析数据增长的趋势和速率
- 预测模型:基于历史数据预测未来的增长
- 告警机制:设置表大小的告警阈值
- 自动报告:生成定期的表增长报告
Q9: 如何优化表的批量加载性能?
A9: 优化表批量加载性能的方法:
- 直接路径插入:使用 INSERT /*+ APPEND */ 提示
- 并行加载:使用并行插入提高性能
- 禁用约束:在加载期间临时禁用约束
- 禁用日志:使用 NOLOGGING 选项减少日志生成
- 使用外部表:对于大型数据集,使用外部表加载
- 批量绑定:使用 PL/SQL 批量绑定减少上下文切换
Q10: 如何处理分区表的历史数据?
A10: 处理分区表历史数据的方法:
- 分区交换:使用分区交换快速归档数据
- 分区删除:直接删除不再需要的分区
- 分区合并:将多个小分区合并为一个
- 分区压缩:对历史分区应用高级压缩
- 分区移动:将历史分区移动到低成本存储
- 生命周期管理:使用自动分区管理策略
