12.6 序列化LOB
把对象图序列化为数据库中的单个大对象字段,以牺牲查询能力换取整体读写的简单持久化。
学习目标
- 能画出对象图、LOB 字段、序列化格式与反序列化之间的读写边界
- 能为序列化载荷设计版本、校验和迁移策略,并说明损坏时在哪里停止
- 能依据查询需求、并发粒度和对象图大小,选择或拒绝序列化 LOB
为什么 12.6 序列化LOB 值得单独学习
先把问题说窄:订单中的优惠券、定价快照或规则配置有时天然是一整棵对象树,业务通常整棵读取、整棵写回。如果为了保存一个快照而拆出十几张只在一起使用的表,映射和迁移成本可能比一次大字段读写更难控制。↡把整个对象图编码到数据库单个大字段中的持久化模式提供的正是这条取舍:以内部字段的查询能力换取整体读写的简单性。
它不是“把 JSON 塞进数据库”就结束了。系统必须回答四个可复核的问题:谁拥有对象图,数据以什么格式落盘,旧格式如何被读取,以及一个用户只改一项时是否必须重写整块载荷。若这些问题没有边界,短期省下的表结构会变成长期不可查询、不可迁移的黑盒。
先建立直觉:一棵对象树如何穿过数据库
先猜一猜:一笔订单有 20 个明细和一份定价快照,页面只需要完整恢复订单;此时把它拆成多表 JOIN,还是把整棵数据编码到一列,哪一个更容易保证读写顺序?答案取决于“完整读取”是否稳定,以及未来是否需要按明细 SKU、状态或金额筛选。
↡运行时彼此引用、包含嵌套对象和集合的一组对象
是内存中的结构,而 LOB
是数据库中的存储形态。序列化器把对象图转换为字节或文本;数据库只认识这一列和行的主键,并不认识载荷内部的
items[3].sku。读取时,应用先取出整列,再用
↡把字节或文本载荷还原为对象图的过程恢复对象与集合。
因此,Serialized LOB 的真正边界是“对象图 ↔ 单字段载荷”,不是“应用 ↔ 数据库”。表可以仍然保留订单主键、租户键和更新时间等关系字段;只有需要整体恢复、很少独立查询的部分进入 LOB。把所有字段都放进去,反而会让索引、权限和审计失去清晰坐标。
目录单元到教学证据
12.6 序列化LOB
本章只覆盖一个目录单元:把复杂对象图保存为一行中的大对象字段。单元键为 poeaa24-pattern-17-serialized-lob;模式族为 mapping。本章的证据链不是“成功保存一次”,而是能从载荷看到版本、校验和迁移入口,并能在查询或并发要求变强时指出拒绝信号。
评审时固定同一条订单样本:id=42、两个明细、一个优惠券和一个定价快照。每一步都记录输入对象、落盘载荷、读取结果和失败位置。这样换 ORM、序列化库或数据库后,仍然可以用同一张评审卡检查模式语义,而不是依赖某个框架默认把对象“神奇地”存好。
专属设计案例:订单快照的保存边界
把订单快照拆成“对象图 → 编码 → 校验 → 写入 → 恢复”五个观察点。订单服务拥有业务状态,序列化器只负责表示转换,仓储负责把带主键的载荷写入 orders.snapshot;三者不能互相隐藏失败。↡限定一个聚合可以修改哪些状态、由谁维护不变量的业务范围应决定哪些对象必须整体恢复,不能由“字段很多”这种技术偶然性决定。
本章的裁决是:对象图整体读写、内部字段几乎不参与 SQL 筛选、并发修改可以按订单行串行化时,序列化 LOB 值得选择;如果报表要频繁按明细过滤,或两个用户经常同时修改同一对象图中的不同部分,应转向关系映射或拆分读写模型。评审记录保留五项指标:载荷大小、读取完整度、查询路径、并发冲突和迁移次数。
三步交互实验:从保存到拒绝
动手试:先预测第 2 步加入版本和校验后,系统能否在反序列化前拒绝损坏载荷;再观察第 3 步把“按 SKU 查询”加入需求后,读写边界如何改变。每一步都要看图,再用下方的选择矩阵写出理由。
1. 对象图变成单字段载荷
先固定 id=42 的订单对象图。序列化器把嵌套集合和优惠券编码为一个 snapshot 字段;表只负责按主键取回整行。这个步骤的收益是一次 I/O 恢复完整订单,代价是数据库不能理解载荷内部的字段。
常见误区
选择与拒绝矩阵
| 评审问题 | 选择序列化 LOB 的证据 | 应拒绝或改用其他方案的信号 |
|---|---|---|
| 读取 | 大多数请求都要完整恢复同一对象图 | 查询只需要其中一两个字段 |
| 变化 | 载荷有明确版本、校验和迁移入口 | 每次结构变更都靠手工脚本猜格式 |
| 查询 | 主键和少数外部字段足够定位记录 | 报表、搜索和排序依赖 LOB 内部字段 |
| 并发 | 以聚合为粒度检测版本冲突可接受 | 不同子对象必须独立高并发更新 |
| 替代 | 已与关系映射、拆分读模型比较 | 只是因为 ORM 映射麻烦就把数据藏起来 |
矩阵的使用顺序是先看读取和查询,再看变化与并发:如果查询信号已经明显拒绝,就不要用“迁移做得很好”掩盖错误的存储形态。一个可靠的设计还要记录何时退出该模式,以及退出时哪些外部列和历史载荷需要保留。
本章小结
- 序列化 LOB 保存的是对象图与单字段之间的映射,不是数据库内部的关系结构。
- 版本、校验和迁移必须先于反序列化,失败要停在载荷边界。
- 整体读取稳定、内部查询少且按聚合并发可接受时,模式才有优势。
- 高频内部查询或局部并发修改,是提取关系字段或更换方案的拒绝信号。
本章练习
练习
问题 1: orders.snapshot 中存着订单明细和优惠券。一次读取只按 id 查整笔订单,为什么这比拆成多表更适合序列化 LOB?请写出一个必须保留在关系列中的字段。
问题 2: 新版本把 coupon.percent 改成了 coupon.rate,旧载荷仍然会到达。读取流程应如何安排版本、校验和迁移?
问题 3: 产品要求按 SKU 搜索所有订单,并允许两名操作员同时修改不同明细。你会继续扩展序列化 LOB 吗?请给出改造方向。
前后导航
来源与改写范围
- Martin Fowler 作者图书页:核对全书主题与模式参考结构。
- Martin Fowler 模式目录:核对 Serialized LOB 的公开摘要与模式边界。
- Pearson 出版社页面:交叉核对英文版出版信息与目录范围。
本章不复现原书正文、插图或代码;上述目录和公开资料只限定学习范围,订单快照、评审矩阵、练习、答案与 SVG 图均为本课程独立重写。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 序列化 LOB
把整个对象图编码到数据库单个大字段中的持久化模式,读写简单但内部字段不天然可查询。
- 对象图
运行时彼此引用、包含嵌套对象和集合的一组对象,可以被整体保存或恢复。
- 反序列化
把数据库中的字节或文本载荷还原为应用可以使用的对象与集合。
- 版本化
给载荷标记结构版本,让读取方选择对应解析器或迁移器,而不是猜测字段含义。
- 聚合边界
限定一组对象由谁维护不变量、应以什么粒度整体读取和提交的业务范围。