自在学

我们与你共同进步

  • 分类课程
  • 文章
  • 工作台
  • 订阅

  • 关于我们
  • 隐私政策
  • 使用条款

探索

  • 分类课程
  • 文章
  • 工作台
  • 订阅

网站信息

  • 关于我们
  • 隐私政策
  • 使用条款

加入社区

自在学学习社区微信二维码

微信扫码,交流学习

株洲市自在学教育科技有限公司© 2025 - 2026 版权所有

© 2025 - 2026 株洲市自在学教育科技有限公司 版权所有

湘公网安备43020302000292号|湘ICP备2025148919号-1
分类课程工作台文章订阅
分类课程工作台文章价格

数据库管理

  1. 01数据库管理系统
  2. 02关系模型
  3. 03SQL
  4. 04进阶 SQL
  5. 05高级 SQL
  6. 06形式化关系查询语言
  7. 07数据库设计与实体关系模型
  8. 08关系数据库设计
  9. 09数据库的存储与物理文件结构
  10. 10索引与哈希
  11. 11数据库查询处理
  12. 12查询优化
  13. 13事务管理
  14. 14并发控制
  15. 15数据库恢复
  16. 16数据库系统架构
  17. 17并行数据库
  18. 18分布式数据库系统
  19. 19数据仓库与挖掘
  20. 20信息检索
  21. 21对象数据库
正在加载课程章节内容
课程编程数据库管理对象数据库

对象数据库:从对象身份到持久化工程

把程序里的对象存进数据库,听起来像是把内存中的结构“保存一下”。真正动手后,我们会碰到一串不能靠序列化解决的问题:两个字段完全相同的对象是不是同一个对象?引用跨越一次重启后还有效吗?继承层次怎样查询?对象方法、事务边界和缓存一致性又该由谁负责?

对象数据库讨论的正是这组问题。它不是“把一段 JSON 放进某个字段”的别名,而是一套围绕对象身份、复杂类型、引用导航、继承、多态和持久化生命周期组织数据的思路。工程上又分成三条常见路线:在关系系统中加入对象能力、用映射层连接对象与关系表,以及让数据库原生保存持久对象。理解这三条路线的边界,比记住某一种产品语法更有用。


关系模型遇到复杂对象时卡在哪里

关系模型擅长把事实整理成行与列,并用声明式查询从大量数据中筛选、连接和聚合。它要求核心关系中的属性值保持原子性,这能让约束和查询拥有清楚的语义。工资、余额、日期这类扁平数据因此非常适合关系表。

问题出现在对象本身就是一张结构图时。一个建筑设计项目可能包含楼层、构件、材料、几何形状、版本和依赖关系;一个地理对象可能包含坐标序列、边界、多边形与空间参考;一项工程仿真还会把模型、参数、网格和结果组合成深层嵌套对象。如果把每个集合、复合属性和引用都拆成独立关系,数据当然仍能表示,但一次“取回完整项目”可能需要许多连接,应用还要负责把结果重新装配成对象。

这并不说明规范化失效。拆表依然能降低冗余、集中维护约束,也便于跨对象分析。真正的矛盾是访问单位不同:关系查询以值和集合为中心,对象程序常以一个带身份的对象图为中心。我们需要根据读写模式决定,究竟把复杂结构拆开查询,还是把它当作一个更完整的持久化单位。

实践中有三种集成方式:

路线数据保存方式应用看到的接口主要取舍
对象关系扩展仍以关系系统为基础,增加结构化类型、集合、继承与引用扩展后的 SQL保留声明式查询,但不同系统的类型语法差异较大
对象关系映射表仍是权威存储,映射层按需创建应用对象类、对象查询语言或仓储接口生态成熟,但复杂查询和批量更新可能绕远
原生对象数据库数据库直接保存带身份、类型和引用的对象与对象语言贴近的 API,加对象查询能力对象图自然,但迁移、分析生态和标准化要单独评估

对象持久化的三种实现路线

对象能力的目标不是消灭关系模型,而是缩短“应用中的对象结构”和“持久化表示”之间的距离。只要业务主要做跨记录筛选、聚合和报表,关系模型仍通常是更直接的起点。

1
一个系统频繁按地区和月份汇总数百万条交易,数据结构本身很扁平。最应优先保留哪项能力?

对象标识和值相等不是一回事

对象模型首先区分身份与状态。身份回答“它是哪一个对象”,状态回答“它现在有哪些值”。设对象标识函数为 oid(o)oid(o)oid(o),两个对象即使所有可见字段都相等,也可能满足 oid(o1)≠oid(o2)oid(o_1) \ne oid(o_2)oid(o1​)=oid(o2​);同一个对象经过更新后字段已经变化,身份仍可满足 oid(obefore)=oid(oafter)oid(o_{before}) = oid(o_{after})oid(obefore​)=oid(oafter​)。

举个具体例子。仓库里有两台同型号、同批次、同颜色的设备,它们的值看起来完全相同,但维修记录必须分别归属,因此要有不同身份。反过来,一台设备更换电池、升级固件后状态变化,历史记录仍应指向原来那台设备。若只把当前字段拼成“身份”,字段一改,所有旧引用都可能失去目标。

原生对象系统通常给持久对象分配稳定的对象标识,引用保存这个标识,而不是复制目标值。对象标识应满足三个工程条件:在数据库范围内唯一、在对象生命周期内稳定、不能因为普通属性更新而改变。它可以由系统生成,也可以借助业务键建立外部可见的定位方式,但业务键与内部对象身份仍应分开思考,因为业务规则可能变化。

ORM 的情况不同。表中的主键通常充当重新装配对象的定位依据,会话内的“身份映射”可以保证同一行只装配成一个对象实例;会话结束后,内存对象本身是暂时的。下一次读取同一主键会创建新的内存实例。因此,ORM 提供的是对象视图和会话级身份管理,不等同于数据库原生的持久对象身份。

对象标识与属性值的区别

2
只要两个持久对象的全部业务字段相同,它们就必须拥有同一个对象标识。

封装、方法与持久化生命周期

普通内存对象随着进程结束而消失,持久对象则要跨越进程、重启甚至软件版本继续存在。一次完整的生命周期通常包括:创建暂态对象、把它附着到持久化上下文、分配或确认身份、提交状态、按身份重新取回、修改并再次提交,最后删除或归档。对象从内存进入数据库时,不能只保存一块内存地址,因为地址在下一次运行时没有意义。

封装希望外部代码通过方法维护对象不变量。例如,账户.扣款(金额) 可以检查余额和限额,而不是允许任意代码直接修改余额字段。某些对象关系系统允许结构化类型携带方法,子类型还可以重写方法;原生对象系统也可能持久化类型信息并把方法调用绑定到对象类型。不过,数据约束仍不能全部藏在应用方法里。多个应用、批处理任务或管理工具都能修改同一份数据时,唯一性、引用完整性和事务约束最好由数据库继续兜底。

还要区分“持久化状态”和“可执行代码”。长期保存的数据需要能被新版本程序读取,而方法实现会随部署变化。更稳妥的做法是把对象身份、字段、类型版本和不变量定义清楚,把可替换的行为实现交给受版本控制的应用代码或数据库方法。这样升级方法时,不必重写每个对象;升级对象结构时,也能安排明确的数据迁移。

直接把内存指针当成持久引用会把进程地址、对象布局和数据库状态绑在一起。一次重启、压缩或对象移动就可能让指针失效。持久引用必须建立在稳定标识和数据库管理的解引用规则上。

3
要让对象在进程重启后仍可安全使用,哪些信息或机制通常不可缺少?

复杂类型、集合与嵌套对象

复杂对象并不只是“一行里多几个字段”。它常由几类结构组合而成:

  • 复合类型把相关属性组织成一个有名字的整体,例如地址包含省、市、街道和邮编。
  • 数组保留顺序并允许按位置访问,适合坐标序列、传感器采样和图像像素。
  • 集合不强调顺序与重复,适合标签或权限;多重集允许同一值出现多次。
  • 映射以键定位值,适合不同产品拥有不同规格项的场景。
  • 对象引用连接独立生命周期的对象,让一个项目可以指向多个构件或负责人。

复杂对象中的复合属性、集合与引用

对象关系系统可以把这些结构作为用户定义类型、数组类型或表值类型放进关系属性。原生对象系统则倾向于直接保存对象内的集合与引用。两者都要回答同样的问题:集合元素是否独立共享、删除父对象时怎样处理子对象、集合内部能否建立索引、局部更新是否需要重写整个对象。

嵌套能减少装配和往返,但会改变查询与更新成本。若每次都整块读取用户画像,把一组兴趣标签嵌入对象很自然;若系统经常统计所有对象中的某个标签,拆成可索引的关系可能更好。判断标准不是“看起来更像代码”,而是读取边界、共享程度、更新频率和约束位置。

下面的概念性 SQL 展示结构化属性与数组。具体产品在类型声明、构造函数和数组运算上的语法并不统一:

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)[]
);
4
一个标签集合总是随用户画像整体读取,标签不被其他对象共享,也很少做全局统计。哪种表示最贴合当前访问模式?

继承、多态与表继承

类型继承表达“学生是人员、教师也是人员”。父类型保存编号、姓名等共同属性,子类型增加学位或薪资。方法也可以被继承;子类型重写方法后,同一个父类型接口会根据对象的实际类型执行不同实现,这就是多态分派。

数据库中的继承至少有两个层面,不能混成一件事:

  1. 类型继承定义值或对象的类型层次、可继承属性和方法。
  2. 表继承定义父表与子表的包含关系。查询父表时,系统可能默认包含子表对象;只查询直接存入父表的数据时,通常需要显式限定。

这类设计与概念模型中的泛化/特化相呼应。查询“所有人员”时,可以得到人员、学生和教师;查询结果只能直接保证父类型公开的属性,但方法调用可以按实际子类型分派。不同数据库对父表查询是否自动包含子表、约束是否向子表传播、主键是否跨层次唯一等细节处理不同,迁移前必须用真实语法和执行计划验证。

继承层次与多态查询

继承也会增加演化成本。一个对象能否同时属于两个子类?从学生转为教师是改变类型、创建新对象,还是同时拥有两个角色?如果业务关系会频繁变化,用“人员—角色”组合往往比深层继承更稳定。继承适合表达稳定的“是一个”关系,不适合把每项可变政策都压进类型树。

5
关于数据库中的继承,哪些说法是合理的?

引用、导航与路径查询

对象引用保存目标对象的身份。例如部门对象的 head 字段引用人员对象,人员对象的 address 又引用地址对象。应用可以沿 department.head.address.city 逐层导航,这种写法贴近对象图,也减少了显式编写连接的负担。

对象引用导航与外键路径查询

引用仍需要关系约束。一个引用类型至少要知道目标类型,最好还能限定目标所在的对象集合或表,这相当于给引用规定作用域。删除目标对象时,系统要阻止删除、级联处理、把引用置空,或者交给业务规则修复。若没有作用域和删除策略,导航很容易碰到悬空引用。

对象关系 SQL 常用 REF 取得行对象的引用,用 DEREF 还原目标对象,再用路径表达式访问属性。下面是概念性示意,语法会因系统而异:

sql
CREATE TYPE department_type AS (
  dept_name VARCHAR(60),
  head      REF(person_type)
);
 
SELECT DEREF(d.head).name
FROM departments AS d;

路径表达式只是把连接意图藏进类型系统,并没有让连接成本凭空消失。优化器仍要定位目标对象、检查引用范围并选择访问路径。连续逐对象导航还可能形成大量随机读取;需要筛选很多对象时,应把路径条件交给声明式查询,让数据库整体优化,而不是在应用循环里逐个解引用。

引用图还可能形成环,例如员工引用部门、部门又引用负责人。序列化整张图时要通过对象标识去重并设置边界,否则会无限递归。持久化框架常用延迟加载控制读取范围,但延迟加载又可能制造 N+1 查询,必须结合预取策略和查询日志检查。

6
使用路径表达式访问被引用对象后,数据库就不再需要定位目标对象,也不会产生类似连接的成本。

ORM 如何处理对象关系阻抗失配

对象关系阻抗失配不是单一语法问题,而是两种模型在多个维度上的差异:对象有身份、引用、继承和生命周期;关系表以键、值和集合运算组织数据。对象中的列表可能对应一对多关系,双向引用对应两端外键维护,继承还要选择不同的表布局。

ORM 用映射元数据连接这两边。它按主键或条件查询关系数据,装配成对象;对象发生修改后,工作单元在提交时把变化翻译成 INSERT、UPDATE 或 DELETE。对象查询语言可以再翻译成 SQL,结果被还原成对象集合。这样常规增删改查不必反复手写装配代码,数据库连接、事务和脏数据检查也能集中管理。

对象关系映射的读取与提交工作流

继承有三种典型映射:单表映射把整个层次放进一张表并用类型列区分,查询简单但会出现很多空列;连接表映射把公共字段与子类字段分开,结构规整但取回子类需要连接;具体类表映射让每个子类拥有完整字段,读取单类直接,但父类型查询和公共约束更复杂。没有一种策略在所有场景都最好。

ORM 最常见的性能陷阱包括:

  • 延迟加载集合时,先查一批父对象,再为每个父对象各查一次子集合,形成 N+1 查询。
  • 把大批量更新逐对象执行,产生数万条单行语句,而一条集合式 SQL 就能完成。
  • 会话缓存持有太多对象,既占内存,又让提交时的脏数据检查变慢。
  • 只在对象层写复杂筛选,生成冗长 SQL,优化器难以使用合适索引。

解决方式不是放弃 ORM,而是保留“逃生口”:预取明确需要的关系、给列表接口做投影、批量写入用集合操作、复杂分析直接写 SQL,并用执行计划和查询计数验证。映射层负责减少重复劳动,不应遮住数据库本身的集合能力。

7
页面先查出 100 个项目,随后在循环中分别读取每个项目的成员集合,共执行 101 次查询。这最符合哪类问题?

对象关系 SQL 扩展保留了什么

对象关系数据库是在关系基础上增加更丰富的类型系统。它仍把声明式 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 TABLE students OF student_type UNDER people;

这段代码表达的是能力边界,不是可直接跨库运行的模板。结构化类型、UNDER、表继承、引用生成和方法定义在不同产品中的支持差异很大。真正采用前,要逐项确认 DDL、索引、约束传播、备份恢复、客户端驱动和迁移工具是否支持,而不能只看类型能否创建。

对象关系扩展的价值在于:需要复杂类型时,不必放弃 SQL 的声明式查询;已有关系系统也能渐进加入对象能力。它与原生对象数据库的区别是,关系与 SQL 仍是基础,对象特性是类型和查询语言的扩展,不是把数据库内部完全变成编程语言的对象堆。

8
对象关系数据库在扩展复杂类型、继承和引用的同时,仍以 ____ 查询作为核心访问方式。

版本与模式演化要提前设计

持久对象寿命可能比某一版应用更长,因此模式变化不是例外。新增字段相对容易,但仍要决定旧对象的默认值以及何时补齐;字段拆分、类型改名、继承层次调整或集合元素类型变化,则会影响查询、索引、序列化和应用代码。

先把三类版本分开:

  • 数据库模式版本描述表、类型、约束和索引的结构。
  • 对象布局版本说明一份持久状态按照哪套字段规则写成。
  • 应用版本决定用哪些类和方法解释对象。

三者一起升级风险很高。常用迁移策略有两种:急切迁移在部署窗口一次转换全部对象,读取逻辑简单但停机和回滚成本高;惰性迁移在对象被读取或修改时逐步升级,发布更平滑,但一段时间内必须兼容多个版本。大规模系统还会采用“扩展—回填—切换—收缩”:先增加兼容字段,双写或兼容读取,后台回填,确认新版本稳定后再移除旧结构。

对象标识在迁移中应尽量保持稳定。如果升级类型时重新创建对象并分配新身份,所有外部引用、历史记录和缓存键都要一起重写。确需拆分对象时,应建立旧标识到新对象集合的显式映射,并让迁移过程可重试、可审计。

模式设计也要区分永久约束和政策约束。对象编号唯一可能是长期不变量,“每个人只能属于一个部门”却可能随业务政策改变。把后者硬编码成单值字段或类型层次,会让将来的联合任职变成大规模迁移;把它建成带有效期的关系通常更容易演化。

9
进行持久对象模式迁移时,哪些做法能降低风险?

事务、并发与对象缓存

把数据称为对象,并不会削弱事务要求。一次业务操作同时修改项目、构件和版本记录时,仍要满足原子性;多个会话并发编辑同一对象时,仍要避免丢失更新;提交成功后的状态仍要经受故障恢复。对象图只是应用视角,事务边界必须明确落到数据库操作上。

ORM 和对象客户端常把读取对象缓存在会话或客户端内存中,以减少网络往返。缓存带来两个问题。第一是身份一致性:同一会话内读取同一标识,最好返回同一实例,否则两个副本会互相覆盖。第二是缓存一致性:另一个事务提交后,本地对象可能已经过期。跨事务缓存需要失效通知、版本检查、锁或可接受陈旧读取的明确策略。

版本字段是一种常见的乐观并发控制。事务甲、乙都读取版本 7,并在本地修改。甲先提交时执行带版本条件的更新:

基于版本检查避免并发丢失更新

sql
UPDATE project
SET state = :new_state,
    version = 8
WHERE project_id = :id
  AND version = 7;

若影响一行,甲提交成功。乙随后用同样的 version = 7 条件更新,影响行数为 0,于是得知对象已被修改,应该回滚、重新读取或进入冲突合并。这个规则能避免两个写者悄悄覆盖,但若只验证写对象、不验证读取集合,它并不自动保证所有业务判断可串行化。

原生对象数据库或数据服务器还可能把整个对象送到客户端计算,再把更新送回服务器。这适合计算密集的设计与仿真,但客户端缓存必须处理对象失效、锁回收、故障和预取边界。预取太少会频繁往返,预取太多会浪费带宽并扩大缓存一致性范围。

延迟加载对象依赖活跃的持久化上下文。事务或会话结束后再访问未加载集合,可能直接失败;为了避免失败而无限延长会话,又会扩大缓存和事务边界。接口层应明确决定哪些数据在事务内完成加载。

10
两个事务都读取版本 7,事务甲先把对象提交为版本 8。事务乙按“WHERE version = 7”更新时影响 0 行,正确处理是什么?

适用场景、边界与选型方法

原生对象数据库适合对象图本身就是核心资产的场景,例如计算机辅助设计、工程模型、仿真数据、设备拓扑和某些多媒体对象库。它们往往需要按身份反复导航,取回一个对象后在客户端做大量计算,再把修改后的对象图提交。此时,减少拆表与装配工作可能比跨全库聚合更有价值。

典型业务系统若以订单、账户、库存等稳定事实为主,同时依赖成熟的 SQL、报表、审计和运维工具,关系数据库加 ORM 往往更平衡。ORM 负责对象开发体验,复杂查询和批量操作保留直接 SQL。需要在关系查询中保存少量复合类型、数组或空间结构时,可以评估对象关系扩展,而不必更换整个数据平台。

选型时可以按下面五个问题做实证测试:

  1. 主要读取单位是一行、一组行,还是带循环引用的对象图?
  2. 查询更多是沿身份导航,还是跨大量对象筛选、连接和聚合?
  3. 对象结构与继承层次变化频率有多高,旧数据如何迁移?
  4. 并发冲突发生在单个对象、整个聚合,还是跨对象业务约束?
  5. 团队需要哪些备份、分析、驱动、监控和迁移工具?

对象数据方案的选型决策地图

JSON 和 XML 需要单独澄清。它们是能表示嵌套结构的文本数据格式,适合交换数据,也可以被数据库保存和按路径查询。JSON 里的“对象”本质上是属性名到值的映射;它本身不提供稳定对象标识、类型继承、方法、多态分派或跨文档持久引用。因此,保存 JSON 文档不等于使用对象数据库。反过来,对象数据库也可以把 JSON 用作交换格式,但对象身份仍由数据库的持久化机制管理。

选型不应止于功能清单。用同一批代表性对象图、查询、批量更新和并发冲突做原型,记录装配次数、查询数量、延迟、存储放大和迁移步骤。能稳定解释这些数据,才算真正理解所选方案的代价。

11
哪些判断能正确区分对象数据库与 JSON/XML 数据表示?
上一章信息检索