ADB-03 对象关系与面向对象数据库

📅 预计 60 分钟 | ⭐ = 重要知识点 | 📌 中英术语见文末
🛠 建表/类型扩展部分基于 SQLite(或兼容标准 SQL 的写法),可直接运行


3.1 关系模型的局限

一个塞不进标准货架的东西

把关系模型想成一个标准货架:每层格子都规定了大小和形状,只能放固定规格的盒子。放一个订单没问题——一行一个格子。但"一个订单含五件商品、每件数量不同"、"一个客户有手机、座机、邮箱三个联系方式"这种天然嵌套、一对多的信息就塞不进去了,只能拆成多件分放不同货架,再在账本上登记"哪件和哪件是一套的"。

关系模型以二维表为核心,靠主键/外键把表连起来。这套体系非常成熟,但有三个突出局限。

局限一:复杂对象难表达 ⭐

现实世界的对象往往是嵌套的(订单里面套着明细)和多值的(一个客户有多个电话)。关系模型只有扁平的二维表,表达这种结构只能拆表

-- 一个客户有多个联系方式,关系模型只能拆成两张表
CREATE TABLE customer (
  id   INTEGER PRIMARY KEY,
  name TEXT
);
CREATE TABLE contact (
  customer_id INTEGER REFERENCES customer(id),
  phone       TEXT
);

INSERT INTO customer VALUES (1, '张三');
INSERT INTO contact VALUES (1, '13800000000');
INSERT INTO contact VALUES (1, '010-88888888');

-- 想查"张三的所有联系方式",必须 JOIN
SELECT c.name, t.phone
FROM customer c JOIN contact t ON c.id = t.customer_id
WHERE c.name = '张三';
-- 输出:
-- 张三 13800000000
-- 张三 010-88888888

"订单含明细"同理:订单表、明细表各一张,中间用外键连。写起来不难,但每次查询都要 JOIN,插入一个订单还要分好几条 INSERT,任何一步失败数据就不完整了。

局限二:类型系统弱 ⭐

关系数据库只提供一批内置类型INTEGERVARCHARDATEDECIMAL……用户不能定义自己的类型。像"地址"这种由省、市、街道、邮编组成的结构,只能拆成四列每次写一遍,或者拼成一个字符串 "贵阳市花溪大道 550025"——存是存进去了,想按城市统计就做不到了。

同样,数据库也没有数组/集合这种"一个属性装多个值"的类型。多值信息只能拆表或拼串,两头受气。

局限三:缺乏继承与封装

现实里有清晰的类型层次:本科生和研究生都是"学生",学生和教师都是"人",他们有公共属性(姓名、年龄)。关系表无法表达这种继承关系——你不能定义"教师表继承人员表",只能把公共列在每张表里重复写。

同时,关系模型里数据和操作是分离的:表里只有数据,处理逻辑写在应用层。同一份数据被多个应用操作,规则不统一,也没有"封装"这回事。

💡 记忆口诀:关系模型三软肋——复杂对象难表达、类型系统弱、无继承无封装。记住这三点,就能理解后面两种模型各自在补什么课。

⚠️ 常见错误

  1. 以为拆表只是"麻烦一点":拆表把对象完整性拆散了——插入一个客户要分多次 INSERT,中途失败就出现只有客户没有联系方式的脏数据。
  2. 把多值直接拼进一个 TEXT 列'13800000000,010-88888888' 能存,但 SQL 没法精确匹配"电话是 13800000000 的客户"。
  3. 把地址当字符串存:存进去容易,等做"按城市统计客户数"时才发现提取不出来——类型弱的后果是分析能力弱

3.2 对象关系数据模型(对象关系数据库)⭐

能定制的货架

关系模型是标准货架,对象关系模型就是能定制的货架。货架还是那个货架(还是表、还是用 SQL 查),但格子的形状、大小、层数都可以自定义;你还能规定"这种格子继承另一种格子的规格"。仓库管理员不需要学习一套全新的取货方式——只是货架更能装东西了。

对象关系数据库(ORDB, object-relational database)的思路很明确:不推翻关系模型,而是给它打补丁,让表"更像对象"。核心扩展有以下几类。

扩展一:用户定义类型 UDT ⭐

CREATE TYPE 定义自己的结构类型,一个类型就是一个"复合的列类型":

-- 用户定义类型 UDT:把"地址"做成一个整体
CREATE TYPE address AS (
  city   VARCHAR(50),
  street VARCHAR(100),
  zip    CHAR(6)
);

-- 用自定义类型做列
CREATE TABLE customer (
  id   INTEGER PRIMARY KEY,
  name VARCHAR(50),
  addr address        -- 一个"复合"类型的列,不再拆成三列
);

INSERT INTO customer VALUES (1, '张三', ('贵阳', '花溪大道', '550025'));
-- 输出: 插入成功,addr 一个值就装下了整条地址

说明:CREATE TYPE 是标准 SQL:1999 的写法,PostgreSQL、Oracle 原生支持。SQLite 不支持此语法,以下 CREATE TYPE / 嵌套表示例均为示意代码。

扩展二:集合类型(多值 / 数组)⭐

一个属性可以直接装多个值,不用再拆表:

-- 标准 SQL 写法(示意):phones 是一个字符串数组
CREATE TABLE customer2 (
  id     INTEGER PRIMARY KEY,
  name   VARCHAR(50),
  phones VARCHAR(20) ARRAY   -- 一个客户多个电话,一列搞定
);

INSERT INTO customer2 VALUES (1, '张三', ARRAY['13800000000', '010-88888888']);

SQLite 里没有数组列类型,但可以用 JSON 数组达到同样的"一个属性多个值"效果,而且真的能跑:

-- SQLite 可运行的近似:用 JSON 装多个值
CREATE TABLE customer3 (
  id     INTEGER PRIMARY KEY,
  name   TEXT,
  phones TEXT   -- 存 JSON 数组
);

INSERT INTO customer3 VALUES (1, '张三', '["13800000000","010-88888888"]');

-- 把数组"拆开"成多行来查询
SELECT c.name, p.value AS phone
FROM customer3 c, json_each(c.phones) p
WHERE c.name = '张三';
-- 输出:
-- 张三 13800000000
-- 张三 010-88888888

说明:json_each 需要 SQLite 3.38+,老版本可装 JSON1 扩展。重点理解集合类型让"一对多"收进一列,查询时再展开

扩展三:引用类型与继承

引用类型(reference type)让一个值直接"指向"另一行的对象,类似指针,而不是存一串数字再 JOIN:

-- 标准 SQL(示意):订单直接"引用"客户对象
CREATE TABLE orders_ref (
  order_id  INTEGER PRIMARY KEY,
  customer  REF(customer)  -- 引用类型,指向 customer 表的某一行
);

继承(inheritance)让子类型自动拥有父类型的所有属性:

-- 类型继承:教师"是一个"人员(示意)
CREATE TYPE person AS (
  name VARCHAR(50),
  age  INTEGER
);
CREATE TYPE teacher UNDER person (   -- UNDER 表示继承 person
  dept VARCHAR(50)
);

扩展四:嵌套表 ⭐

嵌套表(nested table)允许某一列本身又是一张表。"订单含多条明细"不再拆成两张表,而是明细直接作为订单的一列:

-- 嵌套表示意:订单里内嵌明细集合
CREATE TYPE order_item AS (
  product VARCHAR(50),
  qty     INTEGER
);
CREATE TABLE orders (
  order_id      INTEGER PRIMARY KEY,
  customer_name VARCHAR(50),
  items         order_item ARRAY   -- 嵌套表列:一个订单多条明细
);

关键:它仍是表,仍用 SQL 查

对象关系模型最重要的特征——没有发明新的查询语言。扩展只是让表能表达更复杂的结构,查询依旧是 SELECT ... FROM ... WHERE ...,这也是 ORDB 能顺利落地的原因:老用户不用重学查询,只是表"更能装"了。

💡 记忆口诀:ORDB 四大扩展——自定义类型 UDT、集合/数组、引用、继承(含嵌套表)。货架还是货架,格子更灵活。

⚠️ 常见错误

  1. 以为 ORDB 放弃了表和 SQL:它恰恰是"在表 + SQL 基础上扩展类型",查询方式没变,这是它与 OODB 最本质的分野。
  2. 把继承理解成"重复复制一份列":继承是类型层次——查询父类型能自动看到子类型的行,不用手动复制数据。
  3. 以为嵌套表/集合"存进去就完事":类型扩展让存储变方便,但查询展开、更新、索引都要额外处理,性能问题常从这里冒出来。

3.3 面向对象数据库(OODB)

一个个完整的盒子

关系数据库像仓库里的账本:东西拆成条目登记在表格里,按条目查。面向对象数据库(OODB, object-oriented database)则像一个个完整的盒子——每个对象就是一个盒子,既有数据又有"使用说明"(方法);盒子之间用绳子直接相连,抓住一个盒子顺藤摸瓜就能找到一串,不用对着账本翻条目。

OODB 把程序设计语言里的对象模型直接搬进数据库。四个核心概念:

概念 含义 生活类比
对象 / 类 对象是现实实体的模型,类是对象的模板 类是"模具",对象是"成品"
封装 数据 + 操作数据的方法打包,外部只能通过公开接口访问 盒子关着盖,只能从拉手(方法)打开
继承 子类复用父类的属性和方法 子承父业,还额外学新技能
多态 同一个操作对不同对象有不同行为 同一个"打印"指令,打印机和 3D 打印机的输出不同

对象标识 OID:比主键更"硬"的身份 ⭐

每个对象有系统分配的唯一标识 OID(object identifier),一经分配永不改变,与对象内容无关。这一点跟关系模型的主键有本质区别:

关系模型主键 OODB 的 OID
谁指定 用户选定某一列作主键 系统自动分配
是不是数据的一部分 是,主键值就是行里的数据 不是,OID 独立于内容
能不能改 能改(改了就"换了身份") 永不改变
类比 名字,可以改 身份证号,终身不变

举个例子:学生表用 学号 做主键,2023001 改成 2024001,这一行在数据库里就被认为是"另一行"了;而 OODB 里不管对象内容怎么改,它的 OID 不变,身份永远稳定。

对象间关系:引用 vs 外键 ⭐

关系模型表示联系靠外键——表里存另一个表的主键值,查的时候 JOIN 连接。OODB 表示联系靠引用——对象直接存另一个对象的 OID(相当于指针),查的时候顺着引用导航,不需要 JOIN。

对比"查张三有哪些订单",两种模型的路数完全不同:

-- 关系数据库:靠外键 + JOIN 找到"身份"
SELECT o.order_id
FROM orders o JOIN customer c ON o.cust_id = c.id
WHERE c.name = '张三';

-- OODB 视角(示意,不是 SQL):直接从客户对象导航到他的订单
customer("张三").orders
-- 输出: {订单1001, 订单1002}   顺着引用一次到位

关系模型是"按值匹配":先知道张三的 id,再拿这个值去订单表比对。OODB 是"按引用导航":对象之间本来就连着线,走线就到,不需要等值匹配。

OODB 的优势:复杂对象导航快、与编程语言的对象模型天然一致,程序里怎么组织对象,数据库里就怎么存。代价:查询语言弱化、标准化不足、与既有 SQL 生态兼容性差,这是它没能取代关系数据库的根本原因。

💡 记忆口诀:主键是"名字"能改,OID 是"身份证号"永不变;关系靠"值连接"(JOIN),OODB 靠"引用导航"。

⚠️ 常见错误

  1. 把 OID 和主键混为一谈:主键是用户选的数据,值变了身份就变;OID 是系统分配的记号,内容随便改、身份不变。
  2. 以为 OODB 里也有外键和 JOIN:OODB 用引用导航建立联系,JOIN 是关系模型的概念,混在一起就理解偏了。
  3. 以为 OODB 抛弃了所有数据库能力:事务、并发、持久化这些能力它仍然有,变的只是数据组织方式

3.4 ODMG 标准

给各自为政的方言统一"普通话"

面向对象数据库最大的问题之一,是每个厂商都在说自己的方言。ODMG(Object Data Management Group,对象数据管理组织)做了一件事:统一一份"普通话"——发布一套规范,让对象定义和查询有统一写法,用户学到一套就能用在多家产品上。ODMG 规范(先后发布 1.0 / 2.0 / 3.0)核心是两个部分。

对象定义语言 ODL ⭐

ODL(Object Definition Language,对象定义语言)用来描述类、属性、方法、继承关系和对象间联系,相当于 OODB 世界里的"DDL"。示意:

// ODL 定义(示意):Student 继承 Person
interface Student : Person {
  attribute string major;               // 属性
  relationship Set<Course> courses;     // 对象间关系:一个学生多门课
  void enroll(in Course c);             // 方法
};

注意它描述的是""而不是"表":attribute 定义属性,relationship 定义对象间的联系,void enroll(...) 定义方法。

对象查询语言 OQL ⭐

OQL(Object Query Language,对象查询语言)用来查询对象集合,语法长得像 SQL,但查询的对象是"对象"而不是"行",支持沿着引用导航。示意:

-- OQL 查询(示意):从对象集合里筛选,还能顺着引用继续走
SELECT s.name
FROM Students s
WHERE s.major = "计算机";

-- 顺着引用导航:查"张三选的所有课程名"
SELECT c.name
FROM Students s, s.courses c
WHERE s.name = "张三";

ODMG 借鉴了关系数据库"声明式查询"的思想,又补上了对象导航能力。不过它只是行业组织规范,没有强制力,商业 OODB 的实现在细节上各不相同,这也让面向对象数据库始终没能形成关系数据库那样的统一生态。

💡 记忆口诀:ODMG 两件套——ODL 管"定义对象",OQL 管"查询对象";一个像 DDL,一个像 DML。

⚠️ 常见错误

  1. 把 ODL 当成 SQL 的 DDL:ODL 描述"类"(属性 + 方法 + 关系),SQL DDL 描述"表"(列 + 约束)。对象和表不是一回事。
  2. 以为 OQL 就是 SQL:两者语法相近,但 OQL 面向对象集合、支持引用导航,语义完全不同。
  3. 以为 ODMG 是 ISO 官方标准就"一统天下":它只是行业组织规范,没有强制力,各厂商实现并不统一。

3.5 三种路线对比

三个仓库的不同管法

同样是"存数据",三种路线是三套不同的管法:关系数据库是标准货架 + 统一账本,东西拆件登记;对象关系数据库是能定制隔层的货架,账本不变但格子能装复杂结构;面向对象数据库干脆不用货架和账本,让一个个"完整的盒子"在仓库里用绳子连成网。

对比表 ⭐

维度 关系数据库 RDB 对象关系数据库 ORDB 面向对象数据库 OODB
数据组织 二维表,靠主键/外键连接 表 + 用户定义类型/集合/嵌套表,表间仍可外键 对象/类,靠引用(OID)导航
类型系统 只有内置类型 可自定义类型、集合、继承 完整的面向对象类型系统(封装/继承/多态)
查询方式 SQL,靠 JOIN 连接 SQL 扩展,仍是声明式查询 OQL / 对象导航,没有 JOIN 概念
复杂对象支持 弱,必须拆表 中,能表达嵌套但保持 SQL 兼容 强,对象天然支持嵌套
与编程语言衔接 靠 ORM 转换 较好 天然一致
标准与生态 最成熟 SQL:1999 支持,实际应用广 标准弱,生态小
适用场景 事务型业务数据 关系数据为主、夹杂复杂结构 CAD、GIS、多媒体等复杂对象领域

三条路线的本质差异

一句话概括三种路线的思路:

  • 关系数据库:数据扁平化,连接靠(外键 + JOIN)。
  • 对象关系数据库:还是表,但能装复杂结构(类型扩展)。
  • 面向对象数据库:对象即本体,连接靠引用(OID 导航)。

真实市场中,对象关系数据库(典型代表 PostgreSQL)把"兼容 SQL"和"对象扩展"结合,取得了广泛成功;纯面向对象数据库则收缩在 CAD 辅助设计、GIS 地理信息、多媒体等复杂对象密集的特定领域。理解"谁在什么场景更合适",比记哪条路线更先进更重要——它们各自解决的问题不同。

⚠️ 常见错误

  1. 把 ORDB 和 OODB 画等号:ORDB 保留表 + SQL、只扩展类型;OODB 抛弃表、以对象为核心。一个补丁、一个革命。
  2. 以为 OODB 一定比关系数据库先进:它只更适合复杂对象场景,事务处理、标准统一、生态成熟度都不如关系数据库。
  3. 对比时只盯数据组织、忽略查询方式:OODB"没有 JOIN"是它与关系模型最大的行为差异,也最容易被误读。

🧠 记忆口诀

  • 关系模型三软肋:复杂对象难表达、类型系统弱、无继承无封装。
  • ORDB 四大扩展:自定义类型 UDT、集合/数组、引用、继承(含嵌套表)。
  • ORDB 的本质:货架还是货架——仍是表、仍用 SQL,只是格子更灵活。
  • OODB 四概念:封装、继承、多态、OID。
  • 主键 vs OID:主键是"名字"能改,OID 是"身份证号"永不变。
  • 连接方式:关系靠"值连接"(JOIN),OODB 靠"引用导航"。
  • ODMG 两件套:ODL 管定义(像 DDL),OQL 管查询(像 DML)。
  • 三条路线一句话:RDB 扁平表 + 值连接;ORDB 扩展表 + 类型扩展;OODB 纯对象 + 引用导航。

⭐ 考点清单

  1. 关系模型的三大局限,以及各自对应的典型场景(订单含明细、客户多联系方式)。
  2. 对象关系数据库的四大扩展:UDT、集合类型、引用类型、继承(含嵌套表)。
  3. CREATE TYPE 定义结构类型、UNDER 写继承的示意语法。
  4. OID 与关系主键的区别:谁分配、是不是数据的一部分、能不能改。
  5. 对象间关系两种建立方式:引用导航 vs 外键 JOIN。
  6. OODB 四个核心概念:对象/类、封装、继承、多态。
  7. ODMG 标准:ODL 定义对象、OQL 查询对象。
  8. 三种数据库路线在数据组织、查询方式、适用场景三个维度上的对比。

📌 中英术语表

中文 English 记忆点
关系模型 relational model 二维表 + 主外键
用户定义类型 user-defined type (UDT) CREATE TYPE
集合类型 collection type 一个属性多个值
嵌套表 nested table 表中表
引用类型 reference type 指向对象的"指针"
继承 inheritance 子类复用父类
对象标识 object identifier (OID) 系统分配的身份证号
对象关系数据库 object-relational database (ORDB) 关系 + 对象扩展
面向对象数据库 object-oriented database (OODB) 以对象为核心
封装 encapsulation 数据 + 方法打包
多态 polymorphism 同一操作不同行为
对象定义语言 Object Definition Language (ODL) 定义类,像 DDL
对象查询语言 Object Query Language (OQL) 查询对象,像 DML
对象数据管理组织 Object Data Management Group (ODMG) 制定 OODB 规范的行业组织