尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Oracle 19c数据表对象全解析:从建表语法到约束设计

Oracle 19c数据表对象全解析:从建表语法到约束设计 讲Oracle 19c的数据表对象其实没那么多玄乎的东西。你把它想成Excel里的一张工作表就行有表头、有行、有列规定了每列填什么类型的数据。但数据库表比Excel严谨得多它不仅要存数据还要保证数据的完整性、一致性甚至在高并发下依然稳定。这篇是“Oracle 19c从入门到精通”系列的第10篇专门把数据表对象相关的语法知识点掰开揉碎配合案例讲清楚。无论你是刚看完安装教程准备上手还是已经在Linux环境里装好了Oracle 19c、正在琢磨怎么建表这篇都适合你。我会从最基础的CREATE TABLE说起一直讲到ALTER TABLE维护、约束设计、数据字典查询最后用一个完整的订单模块案例把知识点全部串起来。1. 数据表对象到底是什么先弄清Oracle 19c里“表”的位置1.1 从逻辑存储结构看表很多初学者一上来就背CREATE TABLE语法这是本末倒置。你连表在Oracle里处于什么位置都不知道遇到ORA-01537这类表空间错误、ORA-00942表不存在这类问题就会一头雾水。Oracle 19c的逻辑存储结构从上到下是数据库Database→ 表空间Tablespace→ 段Segment→ 区Extent→ 数据块Data Block。表本身是一个段叫“表段”存在某个表空间里。表空间是物理数据文件的逻辑映射一个表空间可以对应一个或多个数据文件。你建表的时候如果不指定表空间Oracle就会把表建到用户的默认表空间里。这个层级关系非常重要它解释了三个常见现象为什么删除表后空间不一定立刻释放因为表段占用的区可能还在为什么同一个用户下不能建同名表因为Oracle靠“用户名.表名”来唯一标识一张表同用户下同名就是冲突为什么表可以跨多个数据文件因为段在表空间内分配区区可以落在不同的数据文件上。补充一点Oracle 19c里默认使用本地管理表空间LMT段空间管理用自动段空间管理ASSM。你在手工创建表空间时可以手动指定SEGMENT SPACE MANAGEMENT AUTO如果是用默认配置创建的系统会自动启用。这个机制决定了表段内部的空闲空间管理是自动的你不用手动设置PCTUSED、FREELISTS这些参数Oracle已经替你搞定了。1.2 表与用户、表空间、数据文件的关系在Oracle里用户Schema和表空间不是一回事但很多人混淆。用户是“数据库账号”表空间是“数据的容器”。你建的用户如果没有分配表空间它也可以存在但没法建表因为没有空间可用。所以标准的做法是先建表空间再建用户然后把表空间指定给用户最后用这个用户建表。实际项目中我习惯的创建顺序是这样CREATE TABLESPACE APP_DATA DATAFILE /u01/app/oracle/oradata/ORCL/app_data01.dbf SIZE 4G AUTOEXTEND ON NEXT 512M MAXSIZE 16G SEGMENT SPACE MANAGEMENT AUTO;然后创建用户并指定默认表空间CREATE USER app_user IDENTIFIED BY YourStrongPwd2025 DEFAULT TABLESPACE APP_DATA QUOTA UNLIMITED ON APP_DATA TEMPORARY TABLESPACE TEMP; GRANT CONNECT, RESOURCE TO app_user;这里有个细节RESOURCE角色在Oracle 19c里包含UNLIMITED TABLESPACE权限所以即使不单独赋QUOTA很多时候也能建表。但从最小权限原则考虑我建议还是显式给QUOTA后面回收权限时更清晰。你打开DBA_TABLESPACES视图可以看到表空间的使用情况打开DBA_DATA_FILES可以看到表空间对应的数据文件。我记得一次在生产环境排查慢查询发现某张表所在的表空间数据文件AUTOEXTEND关闭了空间涨满后导致INSERT一直报ORA-01653就是因为建表时没规划好表空间容量。这种问题在测试环境根本遇不到因为测试库数据量小生产库跑个把月就能把空间打满。所以记住一句话建表之前先看表空间别急着敲CREATE TABLE。2. 建表语法逐句拆解从CREATE TABLE到约束与默认值2.1 标准建表语句的完整骨架Oracle 19c的CREATE TABLE语法非常庞大光官方文档就有几十页。但实际开发中90%的场景只需要掌握三个“段”表定义段、约束定义段、存储参数段。我写一个最常用的骨架CREATE TABLE schema_name.table_name ( column_name1 data_type [DEFAULT expr] [column_constraint], column_name2 data_type [DEFAULT expr] [column_constraint], ..., [table_constraint] ) [TABLESPACE tablespace_name] [STORAGE (...)] [LOGGING | NOLOGGING] [PARALLEL n];字段约束有三种位置列级约束直接写在列定义后面表级约束写在所有列定义之后用逗号分隔还有一种是通过ALTER TABLE事后追加。列级约束适合单个字段表级约束适合联合主键、多个字段的UNIQUE约束。看一个简单的例子CREATE TABLE EMP ( EMPNO NUMBER(4) CONSTRAINT PK_EMP PRIMARY KEY, ENAME VARCHAR2(20) NOT NULL, HIREDATE DATE DEFAULT SYSDATE, SAL NUMBER(7,2) CHECK (SAL 0), DEPTNO NUMBER(2) REFERENCES DEPT(DEPTNO) );这个语句包含了主键、非空、默认值、检查约束、外键约束五类最常见的约束都在里面。注意一点列级外键约束不会自动创建索引外键列上如果缺索引删除父表记录时可能会引发全表锁后面我会专门讲。生成列虚拟列也值得一提。Oracle 11g以后支持虚拟列19c里已经非常成熟。比如一张订单表里有单价和数量你可以加一个虚拟列自动计算总价CREATE TABLE ORDER_ITEM ( ITEM_ID NUMBER(10) PRIMARY KEY, PRODUCT_ID NUMBER(10) NOT NULL, QTY NUMBER(6) NOT NULL, UNIT_PRICE NUMBER(10,2) NOT NULL, TOTAL_AMT NUMBER(12,2) GENERATED ALWAYS AS (QTY * UNIT_PRICE) VIRTUAL );虚拟列不占用实际存储空间查询时直接当普通列用省去了应用层计算的麻烦。但不能往虚拟列里INSERT或者UPDATE否则会报ORA-54013。2.2 字段类型选型VARCHAR2、NUMBER、DATE、CLOB怎么选字段类型选错是很多新手容易踩的坑。选型原则很简单能用数值就别用字符串能用定长就别用变长能用DATE就别用VARCHAR2存时间。VARCHAR2是Oracle最常用的字符串类型必须指定长度比如VARCHAR2(20)。注意19c里默认单位是字节还是字符取决于参数NLS_LENGTH_SEMANTICS。如果库里存中文建议设置为CHAR语义否则VARCHAR2(20)只能存20个字节也就是6~7个汉字。设置语句ALTER SYSTEM SET NLS_LENGTH_SEMANTICSCHAR SCOPEBOTH;NUMBER类型可以表示整数和小数。NUMBER(4)表示最多4位整数NUMBER(7,2)表示总共7位其中2位小数也就是最大整数部分5位。做金额计算时要注意如果你用NUMBER(10,2)最大金额是99999999.99够用。但如果涉及分账系统精度要求高建议把scale加大或者用NUMBER不指定精度。DATE类型在Oracle里本身就包含时分秒。很多人用VARCHAR2存日期比如‘2025-01-09 10:30:00’用起来似乎没问题但等你要做日期函数运算months_between、trunc时就麻烦了。Oracle 9i以后还有TIMESTAMP类型支持小数秒和时区如果业务需求精度到毫秒用TIMESTAMP。CLOB是字符大对象可以存4GB的字符数据但只能通过DBMS_LOB包或SQL函数操作不能直接做等值比较。如果你只是存备注、日志内容用VARCHAR2(4000)就够了。超过4000字节才考虑CLOB。还有一个补充BOOLEAN类型在Oracle 19c里已经有了但只支持PL/SQL上下文SQL语句里不能直接用它建表。所以建表时要用NUMBER(1)或CHAR(1)来表示布尔值。2.3 约束与默认值别等数据坏了才后悔约束就是数据库的“质检员”。你把数据扔进去之前约束帮你检查是不是合规。最常见的五类约束PRIMARY KEY主键唯一且非空一张表只能有一个UNIQUE唯一允许NULL而且Oracle里多个NULL不冲突NOT NULL就是字段必填CHECK检查限制取值范围FOREIGN KEY外键保证引用完整性。我见过不少项目为了方便建表时什么约束也不加说是“等应用层去校验”。实际结果就是一年后数据脏得没法看重复主键、负数金额、悬空的外键引用application里要写一堆if else去兜底。性能还总出问题因为应用层校验需要前置查询高并发下就是雪上加霜。约束设计有个技巧约束尽量用有意义的名称比如PK_EMP、FK_EMP_DEPT、CK_EMP_SAL。不然Oracle会自动生成SYS_C00xxxx后续想drop约束还得先查出来名字。下面这个例子演示如何给约束命名CREATE TABLE EMP ( EMPNO NUMBER(4) CONSTRAINT PK_EMP PRIMARY KEY, ENAME VARCHAR2(20) CONSTRAINT NN_EMP_NAME NOT NULL, SAL NUMBER(7,2) CONSTRAINT CK_EMP_SAL CHECK (SAL 0), DEPTNO NUMBER(2), CONSTRAINT FK_EMP_DEPT FOREIGN KEY (DEPTNO) REFERENCES DEPT(DEPTNO) );外键约束还要注意一个行为设定ON DELETE CASCADE还是ON DELETE SET NULL。CASCADE会连坐删除子表记录SET NULL会把子表外键值置空。生产环境里我一般不太愿意用CASCADE万一误删除数据找不回来用SET NULL相对安全但前提是外键列允许NULL。延迟约束Deferred Constraint是进阶内容。默认情况下约束是立即检查的也就是说一行数据不满足约束就立刻报错。如果想在事务提交时才统一检查可以在建约束时指定DEFERRABLE INITIALLY DEFERRED。典型场景是批量导入数据先插订单再插订单明细明细里要引用订单ID但明细先插入时会报外键异常。改成延迟约束后只要提交时整体一致就行。3. 表结构维护实操ALTER TABLE的增删改以及常用查询3.1 加列、改列、删列的注意点需求迭代太快表结构不可能一成不变。ALTER TABLE是你天天要用的语句但这里坑很多。加列是最安全的操作Oracle 11g以后加有默认值的非空列也不会锁表太久因为默认值被记录到了数据字典里不用回填每一行。比如ALTER TABLE ORDERS ADD (ORDER_STATUS NUMBER(1) DEFAULT 0 NOT NULL);这条语句在19c里执行很快即使表里有几千万行也不会把每条记录的ORDER_STATUS都UPDATE一遍。改列就要小心了。你能改长度、改默认值、改数据类型但有几个限制VARCHAR2改大可以直接改改小必须确保现有数据不超长NUMBER改精度时要确认现有数据不会溢出DATE改TIMESTAMP通常可以反过来TIMESTAMP改DATE会丢小数秒。我遇到过最经典的问题把VARCHAR2(20)改成VARCHAR2(10)表里有一行数据是12个字符ALTER TABLE执行时不会立刻报错但等到你UPDATE那一行数据时才报ORA-12899。删列操作在19c里分两种。一种是直接DROP COLUMN如果列上有大对象或者表很大这个操作会特别耗时还可能占用大量UNDO。另一种是先ALTER TABLE ... SET UNUSED COLUMN标记为未使用再在业务低峰期用ALTER TABLE ... DROP UNUSED COLUMNS真正清理。这是大表删列的推荐姿势。改列还有一个隐藏技巧如果只是想修改列名用RENAME COLUMN如果是调整列顺序Oracle没有直接改顺序的语法只能通过重建表来实现。18c以后可以部分在线重建表19c支持得更好但一般业务场景真没必要纠结列顺序别在这上面浪费时间。3.2 用数据字典视图掌握表的结构新手经常问我怎么看表建得对不对除了DESC更专业的方式是查数据字典。Oracle里关于表的数据字典非常丰富选几个常用的USER_TABLES当前用户下的表信息包括表名、表空间、行数估算、压缩属性、日志属性。写脚本统计库里的表大小时我最常用的是它的NUM_ROWS列但注意这个值只有统计信息更新后才准确。USER_TAB_COLUMNS当前用户下所有表的列信息包括字段名、数据类型、长度、精度、可空性、默认值。想批量查表结构可以用这份视图。USER_CONSTRAINTS约束信息包括约束名称、类型P、U、C、R以及约束状态。拆解一个别人留下的库时靠它快速判断哪些表有主键哪些外键被禁用了。USER_CONS_COLUMNS约束与列的关联信息能查到每个约束涉及哪些列很方便还原复合主键。举个例子查询一张表上所有约束SELECT c.CONSTRAINT_NAME, c.CONSTRAINT_TYPE, cc.COLUMN_NAME, cc.POSITION FROM USER_CONSTRAINTS c JOIN USER_CONS_COLUMNS cc ON c.CONSTRAINT_NAME cc.CONSTRAINT_NAME WHERE c.TABLE_NAME ORDERS ORDER BY c.CONSTRAINT_NAME, cc.POSITION;在19c里推荐用ALL_或DBA_前缀的视图DBA_需要较高的权限但能看到所有用户的对象。做DBA日常巡检时DBA_TABLES、DBA_TAB_COLUMNS、DBA_INDEXES这些是必备工具。你把查询结果导出成Excel可以形成一份完整的表结构文档市面上很多数据字典工具无非就是包装了这些SQL。3.3 基于实践临时表、外部表、分区表什么时候用除了普通表Oracle还有几种特殊表对象在特定场景下极其好用。全局临时表Global Temporary Table数据是会话级的。你建好表结构每个会话插入的数据只有自己能看见会话结束数据自动消失。适合做中间结果集、ETL临时加工。注意选项ON COMMIT PRESERVE ROWS表示事务提交后数据还在适合需要跨多个事务的会话级别临时表ON COMMIT DELETE ROWS表示提交后清空。默认是DELETE ROWS很多人弄混导致别的事务还没结束数据就没了。外部表External Table可以让你直接用SQL查文件系统里的数据文件比如CSV、文本文件表结构是固定的数据存在OS目录上。适合数据加载。前提是必须在Oracle目录对象指向的操作系统目录中且文件格式符合你定义的ACCESS PARAMETERS。用外部表加载几百兆的CSV非常快比SQL*Loader还好写。分区表Partition Table是大数据量场景的关键方案。按范围分区、列表分区、哈希分区、复合分区各有适用场景。对于一张几千万行的流水表按月做RANGE分区后查询单月数据可以走分区裁剪只扫一个分区性能提升立竿见影。给个分区表示例CREATE TABLE ORDERS_PART ( ORDER_ID NUMBER(12), ORDER_DATE DATE, CUSTOMER_ID NUMBER(10) ) PARTITION BY RANGE (ORDER_DATE) ( PARTITION P_2024_Q1 VALUES LESS THAN (TO_DATE(2024-04-01,YYYY-MM-DD)), PARTITION P_2024_Q2 VALUES LESS THAN (TO_DATE(2024-07-01,YYYY-MM-DD)), PARTITION P_2025_FUTURE VALUES LESS THAN (MAXVALUE) );用分区表后旧数据归档也非常简单ALTER TABLE ... EXCHANGE PARTITION或者DROP PARTITION比DELETE大表高效得多。21c以后还有分区维护的增强19c已经够用。4. 案例实践用一个订单模块把知识点串起来4.1 需求分析与表设计光讲语法不落地等于白学。下面我用一个电商订单模块的简化案例把上面的知识点全部用上。需求并不复杂需要记录客户、订单主表、订单明细、商品四类核心对象。客户和商品是基础主数据订单主表存订单头信息比如订单号、客户、下单时间、总金额订单明细存每个订单买了什么商品、数量、单价。设计表时要注意几个点金额字段统一用NUMBER(12,2)足够覆盖一般业务状态字段用NUMBER(1)0表示待支付、1表示已支付、2表示已发货、3表示已完成、4表示已取消日期字段统一用DATE订单时间带时分秒存中文备注用VARCHAR2(500)商品名称不会特别长VARCHAR2(200)就够。外键关系是订单主表的CUSTOMER_ID引用客户表的客户ID订单明细的ORDER_ID引用订单主表的订单ID订单明细的PRODUCT_ID引用商品表的商品ID。为了让删除行为可控外键全部加上约束名称。4.2 完整建表SQL与注释下面这段DDL是我日常项目中比较常见的写法你可以直接抄注意根据自己的表空间调整-- 客户表 CREATE TABLE CUSTOMERS ( CUST_ID NUMBER(10) CONSTRAINT PK_CUST PRIMARY KEY, CUST_NAME VARCHAR2(50) NOT NULL, PHONE VARCHAR2(20), EMAIL VARCHAR2(100), REG_DATE DATE DEFAULT SYSDATE, CONSTRAINT CK_CUST_NAME CHECK (LENGTH(TRIM(CUST_NAME)) 0) ); -- 商品表 CREATE TABLE PRODUCTS ( PROD_ID NUMBER(10) CONSTRAINT PK_PROD PRIMARY KEY, PROD_NAME VARCHAR2(200) NOT NULL, CATEGORY VARCHAR2(50), LIST_PRICE NUMBER(12,2) CHECK (LIST_PRICE 0), STOCK_QTY NUMBER(10) DEFAULT 0 ); -- 订单主表 CREATE TABLE ORDERS ( ORDER_ID NUMBER(12) CONSTRAINT PK_ORD PRIMARY KEY, CUST_ID NUMBER(10) NOT NULL, ORDER_DATE DATE DEFAULT SYSDATE, TOTAL_AMOUNT NUMBER(12,2) CHECK (TOTAL_AMOUNT 0), STATUS NUMBER(1) DEFAULT 0, CONSTRAINT FK_ORD_CUST FOREIGN KEY (CUST_ID) REFERENCES CUSTOMERS(CUST_ID) ); -- 订单明细表 CREATE TABLE ORDER_ITEMS ( ITEM_ID NUMBER(12) CONSTRAINT PK_OI PRIMARY KEY, ORDER_ID NUMBER(12) NOT NULL, PROD_ID NUMBER(10) NOT NULL, QTY NUMBER(6) NOT NULL CHECK (QTY 0), UNIT_PRICE NUMBER(12,2) NOT NULL CHECK (UNIT_PRICE 0), CONSTRAINT FK_OI_ORDER FOREIGN KEY (ORDER_ID) REFERENCES ORDERS(ORDER_ID), CONSTRAINT FK_OI_PROD FOREIGN KEY (PROD_ID) REFERENCES PRODUCTS(PROD_ID) );这段SQL里包含了主键、非空、外键、检查约束、默认值、命名规范。你自己动手敲一遍胜过看十遍教程。执行完以后用DESC验证一下表结构再用USER_TAB_COLUMNS查询字段信息把数据字典查询的常用SQL也练一练。验证约束是否生效可以故意插入一条STATUS为9的订单如果报ORA-02290说明检查约束生效了。再试试插入一条不存在的CUST_ID应该报ORA-02291。初学者用这种“故意报错”的方式学约束印象最深。4.3 用一条SQL验证学习成果表建好后写几条DML验证一下-- 插入客户 INSERT INTO CUSTOMERS(CUST_ID, CUST_NAME, PHONE, EMAIL) VALUES (1001, 张三, 13800000000, zhangsanexample.com); -- 插入商品 INSERT INTO PRODUCTS(PROD_ID, PROD_NAME, CATEGORY, LIST_PRICE, STOCK_QTY) VALUES (5001, 机械键盘, 外设, 399.00, 100); -- 插入订单 INSERT INTO ORDERS(ORDER_ID, CUST_ID, TOTAL_AMOUNT, STATUS) VALUES (90001, 1001, 399.00, 1); -- 插入明细 INSERT INTO ORDER_ITEMS(ITEM_ID, ORDER_ID, PROD_ID, QTY, UNIT_PRICE) VALUES (1, 90001, 5001, 1, 399.00); COMMIT;然后查询验证SELECT o.ORDER_ID, c.CUST_NAME, p.PROD_NAME, oi.QTY, oi.UNIT_PRICE, oi.QTY * oi.UNIT_PRICE AS LINE_TOTAL, o.STATUS FROM ORDERS o JOIN ORDER_ITEMS oi ON o.ORDER_ID oi.ORDER_ID JOIN CUSTOMERS c ON o.CUST_ID c.CUST_ID JOIN PRODUCTS p ON oi.PROD_ID p.PROD_ID;如果你能顺畅地写出这条多表连接查询说明你已经具备表设计和SQL查询的基本功了。后面再做PL/SQL、索引优化、分区管理都是在这些基础之上展开的。5. 常见问题与排查技巧实录5.1 新手最容易踩的5个坑第一个坑把双引号用在对象名上。Oracle里默认的标识符是大写如果你写成“orders”这种带双引号的对象名对象的实际名字会变成小写后续所有引用都必须带双引号极其痛苦。项目中一律不手动加双引号让Oracle统一用大写。第二个坑外键列不建索引。外键约束本身不会自动创建索引。删除父表记录时如果子表外键没有索引Oracle可能锁住整个子表或者执行效率低下。我曾经遇到一个订单系统删除某个客户导致整个订单明细表被锁就是因为两个外键列都没有索引。所以建完外键后立刻在外键列上创建索引CREATE INDEX IDX_OI_ORDER ON ORDER_ITEMS(ORDER_ID); CREATE INDEX IDX_OI_PROD ON ORDER_ITEMS(PROD_ID);第三个坑VARCHAR2长度单位混淆。默认NLS_LENGTH_SEMANTICS是BYTE存中文很容易报ORA-12899。我一般初始化数据库后立刻ALTER SYSTEM设置CHAR语义或者建表时直接用VARCHAR2(20 CHAR)。第四个坑建表后不看统计信息。对于大表如果没做统计信息收集优化器可能选错执行计划。19c默认开启了自动统计信息收集周一至周五晚上、周末全天但对于分区表、临时表自动收集未必及时。批量INSERT之后手动调用一下EXEC DBMS_STATS.GATHER_TABLE_STATS(APP_USER, ORDERS);第五个坑DELETE大表数据后空间不释放。DELETE之后表还是占用原来的区空间不还给操作系统。如果确认数据不需要保留用TRUNCATE如果只是伪删除用UPDATE状态字段然后定期归档。5.2 从入门到精通的路线建议如果你正从Oracle 19c安装开始学建议按这条路线走先掌握Linux环境下Oracle 19c的安装包括openeuler、RHEL这些系统上的静默安装和手动安装把ORACLE_SID、ORACLE_HOME、监听器这些概念弄清楚然后学表空间、用户、权限接着就是本篇的数据表对象随后进入索引、视图、序列、同义词、PL/SQL再往后是性能优化、备份恢复、RAC和Data Guard。每一步都要亲手敲SQL不要光看。特别是表对象这块你可以自己建一套“图书管理系统”或者“员工考勤系统”的表反复做增删改查直到不看文档也能写出完整建表语句。学习过程中多做“破坏性实验”故意写错类型、故意违反约束、故意删数据看看Oracle报什么错误。错误代码是老师ORA-00942、ORA-01400、ORA-02291、ORA-12899、ORA-01552多做过几次自然就背下来了。我在实际带人的过程中发现最快进步的人往往不是记忆力最好的而是那些愿意花时间把一张表设计三遍的人第一遍照猫画虎第二遍考虑约束和索引第三遍考虑分区和归档。你如果也能这样练数据表对象这块就算真正入门了。等你自己维护过一张上亿行的大表回头再看这些语法细节会更有体会。
返回列表