把程序里的对象存进数据库,听起来像是把内存中的结构“保存一下”。真正动手后,我们会碰到一串不能靠序列化解决的问题:两个字段完全相同的对象是不是同一个对象?引用跨越一次重启后还有效吗?继承层次怎样查询?对象方法、事务边界和缓存一致性又该由谁负责?
对象数据库讨论的正是这组问题。它不是“把一段 JSON 放进某个字段”的别名,而是一套围绕对象身份、复杂类型、引用导航、继承、多态和持久化生命周期组织数据的思路。工程上又分成三条常见路线:在关系系统中加入对象能力、用映射层连接对象与关系表,以及让数据库原生保存持久对象。理解这三条路线的边界,比记住某一种产品语法更有用。
关系模型擅长把事实整理成行与列,并用声明式查询从大量数据中筛选、连接和聚合。它要求核心关系中的属性值保持原子性,这能让约束和查询拥有清楚的语义。工资、余额、日期这类扁平数据因此非常适合关系表。
问题出现在对象本身就是一张结构图时。一个建筑设计项目可能包含楼层、构件、材料、几何形状、版本和依赖关系;一个地理对象可能包含坐标序列、边界、多边形与空间参考;一项工程仿真还会把模型、参数、网格和结果组合成深层嵌套对象。如果把每个集合、复合属性和引用都拆成独立关系,数据当然仍能表示,但一次“取回完整项目”可能需要许多连接,应用还要负责把结果重新装配成对象。
这并不说明规范化失效。拆表依然能降低冗余、集中维护约束,也便于跨对象分析。真正的矛盾是访问单位不同:关系查询以值和集合为中心,对象程序常以一个带身份的对象图为中心。我们需要根据读写模式决定,究竟把复杂结构拆开查询,还是把它当作一个更完整的持久化单位。
实践中有三种集成方式:

对象能力的目标不是消灭关系模型,而是缩短“应用中的对象结构”和“持久化表示”之间的距离。只要业务主要做跨记录筛选、聚合和报表,关系模型仍通常是更直接的起点。
对象模型首先区分身份与状态。身份回答“它是哪一个对象”,状态回答“它现在有哪些值”。设对象标识函数为 ,两个对象即使所有可见字段都相等,也可能满足 ;同一个对象经过更新后字段已经变化,身份仍可满足 。
举个具体例子。仓库里有两台同型号、同批次、同颜色的设备,它们的值看起来完全相同,但维修记录必须分别归属,因此要有不同身份。反过来,一台设备更换电池、升级固件后状态变化,历史记录仍应指向原来那台设备。若只把当前字段拼成“身份”,字段一改,所有旧引用都可能失去目标。
原生对象系统通常给持久对象分配稳定的对象标识,引用保存这个标识,而不是复制目标值。对象标识应满足三个工程条件:在数据库范围内唯一、在对象生命周期内稳定、不能因为普通属性更新而改变。它可以由系统生成,也可以借助业务键建立外部可见的定位方式,但业务键与内部对象身份仍应分开思考,因为业务规则可能变化。
ORM 的情况不同。表中的主键通常充当重新装配对象的定位依据,会话内的“身份映射”可以保证同一行只装配成一个对象实例;会话结束后,内存对象本身是暂时的。下一次读取同一主键会创建新的内存实例。因此,ORM 提供的是对象视图和会话级身份管理,不等同于数据库原生的持久对象身份。

普通内存对象随着进程结束而消失,持久对象则要跨越进程、重启甚至软件版本继续存在。一次完整的生命周期通常包括:创建暂态对象、把它附着到持久化上下文、分配或确认身份、提交状态、按身份重新取回、修改并再次提交,最后删除或归档。对象从内存进入数据库时,不能只保存一块内存地址,因为地址在下一次运行时没有意义。
封装希望外部代码通过方法维护对象不变量。例如,账户.扣款(金额) 可以检查余额和限额,而不是允许任意代码直接修改余额字段。某些对象关系系统允许结构化类型携带方法,子类型还可以重写方法;原生对象系统也可能持久化类型信息并把方法调用绑定到对象类型。不过,数据约束仍不能全部藏在应用方法里。多个应用、批处理任务或管理工具都能修改同一份数据时,唯一性、引用完整性和事务约束最好由数据库继续兜底。
还要区分“持久化状态”和“可执行代码”。长期保存的数据需要能被新版本程序读取,而方法实现会随部署变化。更稳妥的做法是把对象身份、字段、类型版本和不变量定义清楚,把可替换的行为实现交给受版本控制的应用代码或数据库方法。这样升级方法时,不必重写每个对象;升级对象结构时,也能安排明确的数据迁移。
直接把内存指针当成持久引用会把进程地址、对象布局和数据库状态绑在一起。一次重启、压缩或对象移动就可能让指针失效。持久引用必须建立在稳定标识和数据库管理的解引用规则上。
复杂对象并不只是“一行里多几个字段”。它常由几类结构组合而成:

对象关系系统可以把这些结构作为用户定义类型、数组类型或表值类型放进关系属性。原生对象系统则倾向于直接保存对象内的集合与引用。两者都要回答同样的问题:集合元素是否独立共享、删除父对象时怎样处理子对象、集合内部能否建立索引、局部更新是否需要重写整个对象。
嵌套能减少装配和往返,但会改变查询与更新成本。若每次都整块读取用户画像,把一组兴趣标签嵌入对象很自然;若系统经常统计所有对象中的某个标签,拆成可索引的关系可能更好。判断标准不是“看起来更像代码”,而是读取边界、共享程度、更新频率和约束位置。
下面的概念性 SQL 展示结构化属性与数组。具体产品在类型声明、构造函数和数组运算上的语法并不统一:
CREATE TYPE address_type AS (
city VARCHAR(40),
street VARCHAR(120),
postal_code VARCHAR(12)
);
CREATE TABLE project_member (
member_id BIGINT PRIMARY KEY,
name VARCHAR(80),
address address_type,
skill_tags VARCHAR(40
类型继承表达“学生是人员、教师也是人员”。父类型保存编号、姓名等共同属性,子类型增加学位或薪资。方法也可以被继承;子类型重写方法后,同一个父类型接口会根据对象的实际类型执行不同实现,这就是多态分派。
数据库中的继承至少有两个层面,不能混成一件事:
这类设计与概念模型中的泛化/特化相呼应。查询“所有人员”时,可以得到人员、学生和教师;查询结果只能直接保证父类型公开的属性,但方法调用可以按实际子类型分派。不同数据库对父表查询是否自动包含子表、约束是否向子表传播、主键是否跨层次唯一等细节处理不同,迁移前必须用真实语法和执行计划验证。

继承也会增加演化成本。一个对象能否同时属于两个子类?从学生转为教师是改变类型、创建新对象,还是同时拥有两个角色?如果业务关系会频繁变化,用“人员—角色”组合往往比深层继承更稳定。继承适合表达稳定的“是一个”关系,不适合把每项可变政策都压进类型树。
对象引用保存目标对象的身份。例如部门对象的 head 字段引用人员对象,人员对象的 address 又引用地址对象。应用可以沿 department.head.address.city 逐层导航,这种写法贴近对象图,也减少了显式编写连接的负担。

引用仍需要关系约束。一个引用类型至少要知道目标类型,最好还能限定目标所在的对象集合或表,这相当于给引用规定作用域。删除目标对象时,系统要阻止删除、级联处理、把引用置空,或者交给业务规则修复。若没有作用域和删除策略,导航很容易碰到悬空引用。
对象关系 SQL 常用 REF 取得行对象的引用,用 DEREF 还原目标对象,再用路径表达式访问属性。下面是概念性示意,语法会因系统而异:
CREATE TYPE department_type AS (
dept_name VARCHAR(60),
head REF(person_type)
);
SELECT DEREF(d.head).name
FROM departments AS d;路径表达式只是把连接意图藏进类型系统,并没有让连接成本凭空消失。优化器仍要定位目标对象、检查引用范围并选择访问路径。连续逐对象导航还可能形成大量随机读取;需要筛选很多对象时,应把路径条件交给声明式查询,让数据库整体优化,而不是在应用循环里逐个解引用。
引用图还可能形成环,例如员工引用部门、部门又引用负责人。序列化整张图时要通过对象标识去重并设置边界,否则会无限递归。持久化框架常用延迟加载控制读取范围,但延迟加载又可能制造 N+1 查询,必须结合预取策略和查询日志检查。
对象关系阻抗失配不是单一语法问题,而是两种模型在多个维度上的差异:对象有身份、引用、继承和生命周期;关系表以键、值和集合运算组织数据。对象中的列表可能对应一对多关系,双向引用对应两端外键维护,继承还要选择不同的表布局。
ORM 用映射元数据连接这两边。它按主键或条件查询关系数据,装配成对象;对象发生修改后,工作单元在提交时把变化翻译成 INSERT、UPDATE 或 DELETE。对象查询语言可以再翻译成 SQL,结果被还原成对象集合。这样常规增删改查不必反复手写装配代码,数据库连接、事务和脏数据检查也能集中管理。

继承有三种典型映射:单表映射把整个层次放进一张表并用类型列区分,查询简单但会出现很多空列;连接表映射把公共字段与子类字段分开,结构规整但取回子类需要连接;具体类表映射让每个子类拥有完整字段,读取单类直接,但父类型查询和公共约束更复杂。没有一种策略在所有场景都最好。
ORM 最常见的性能陷阱包括:
解决方式不是放弃 ORM,而是保留“逃生口”:预取明确需要的关系、给列表接口做投影、批量写入用集合操作、复杂分析直接写 SQL,并用执行计划和查询计数验证。映射层负责减少重复劳动,不应遮住数据库本身的集合能力。
对象关系数据库是在关系基础上增加更丰富的类型系统。它仍把声明式 SQL、集合语义、优化器和事务处理放在核心位置,同时允许表的行采用结构化用户定义类型,属性采用数组或表值类型,类型之间建立继承,字段保存对象引用。
例如,先定义人员类型,再创建“该类型的表”,可以把每一行视为人员类型实例。若人员类型声明允许子类型,学生和教师类型可继承公共属性。表继承再把子表纳入父表查询范围。引用字段可以指向人员实例,并通过路径表达式访问姓名或地址。
CREATE TYPE person_type AS (
person_id VARCHAR(20),
name VARCHAR(80),
address VARCHAR(160)
) NOT FINAL;
CREATE TYPE student_type UNDER person_type AS (
degree VARCHAR(40)
);
CREATE TABLE people OF person_type;
CREATE这段代码表达的是能力边界,不是可直接跨库运行的模板。结构化类型、UNDER、表继承、引用生成和方法定义在不同产品中的支持差异很大。真正采用前,要逐项确认 DDL、索引、约束传播、备份恢复、客户端驱动和迁移工具是否支持,而不能只看类型能否创建。
对象关系扩展的价值在于:需要复杂类型时,不必放弃 SQL 的声明式查询;已有关系系统也能渐进加入对象能力。它与原生对象数据库的区别是,关系与 SQL 仍是基础,对象特性是类型和查询语言的扩展,不是把数据库内部完全变成编程语言的对象堆。
持久对象寿命可能比某一版应用更长,因此模式变化不是例外。新增字段相对容易,但仍要决定旧对象的默认值以及何时补齐;字段拆分、类型改名、继承层次调整或集合元素类型变化,则会影响查询、索引、序列化和应用代码。
先把三类版本分开:
三者一起升级风险很高。常用迁移策略有两种:急切迁移在部署窗口一次转换全部对象,读取逻辑简单但停机和回滚成本高;惰性迁移在对象被读取或修改时逐步升级,发布更平滑,但一段时间内必须兼容多个版本。大规模系统还会采用“扩展—回填—切换—收缩”:先增加兼容字段,双写或兼容读取,后台回填,确认新版本稳定后再移除旧结构。
对象标识在迁移中应尽量保持稳定。如果升级类型时重新创建对象并分配新身份,所有外部引用、历史记录和缓存键都要一起重写。确需拆分对象时,应建立旧标识到新对象集合的显式映射,并让迁移过程可重试、可审计。
模式设计也要区分永久约束和政策约束。对象编号唯一可能是长期不变量,“每个人只能属于一个部门”却可能随业务政策改变。把后者硬编码成单值字段或类型层次,会让将来的联合任职变成大规模迁移;把它建成带有效期的关系通常更容易演化。
把数据称为对象,并不会削弱事务要求。一次业务操作同时修改项目、构件和版本记录时,仍要满足原子性;多个会话并发编辑同一对象时,仍要避免丢失更新;提交成功后的状态仍要经受故障恢复。对象图只是应用视角,事务边界必须明确落到数据库操作上。
ORM 和对象客户端常把读取对象缓存在会话或客户端内存中,以减少网络往返。缓存带来两个问题。第一是身份一致性:同一会话内读取同一标识,最好返回同一实例,否则两个副本会互相覆盖。第二是缓存一致性:另一个事务提交后,本地对象可能已经过期。跨事务缓存需要失效通知、版本检查、锁或可接受陈旧读取的明确策略。
版本字段是一种常见的乐观并发控制。事务甲、乙都读取版本 7,并在本地修改。甲先提交时执行带版本条件的更新:

UPDATE project
SET state = :new_state,
version = 8
WHERE project_id = :id
AND version = 7;若影响一行,甲提交成功。乙随后用同样的 version = 7 条件更新,影响行数为 0,于是得知对象已被修改,应该回滚、重新读取或进入冲突合并。这个规则能避免两个写者悄悄覆盖,但若只验证写对象、不验证读取集合,它并不自动保证所有业务判断可串行化。
原生对象数据库或数据服务器还可能把整个对象送到客户端计算,再把更新送回服务器。这适合计算密集的设计与仿真,但客户端缓存必须处理对象失效、锁回收、故障和预取边界。预取太少会频繁往返,预取太多会浪费带宽并扩大缓存一致性范围。
延迟加载对象依赖活跃的持久化上下文。事务或会话结束后再访问未加载集合,可能直接失败;为了避免失败而无限延长会话,又会扩大缓存和事务边界。接口层应明确决定哪些数据在事务内完成加载。
原生对象数据库适合对象图本身就是核心资产的场景,例如计算机辅助设计、工程模型、仿真数据、设备拓扑和某些多媒体对象库。它们往往需要按身份反复导航,取回一个对象后在客户端做大量计算,再把修改后的对象图提交。此时,减少拆表与装配工作可能比跨全库聚合更有价值。
典型业务系统若以订单、账户、库存等稳定事实为主,同时依赖成熟的 SQL、报表、审计和运维工具,关系数据库加 ORM 往往更平衡。ORM 负责对象开发体验,复杂查询和批量操作保留直接 SQL。需要在关系查询中保存少量复合类型、数组或空间结构时,可以评估对象关系扩展,而不必更换整个数据平台。
选型时可以按下面五个问题做实证测试:

JSON 和 XML 需要单独澄清。它们是能表示嵌套结构的文本数据格式,适合交换数据,也可以被数据库保存和按路径查询。JSON 里的“对象”本质上是属性名到值的映射;它本身不提供稳定对象标识、类型继承、方法、多态分派或跨文档持久引用。因此,保存 JSON 文档不等于使用对象数据库。反过来,对象数据库也可以把 JSON 用作交换格式,但对象身份仍由数据库的持久化机制管理。
选型不应止于功能清单。用同一批代表性对象图、查询、批量更新和并发冲突做原型,记录装配次数、查询数量、延迟、存储放大和迁移步骤。能稳定解释这些数据,才算真正理解所选方案的代价。