13 年发现 pg 有了 json 类型,便从 oracle 转 pg,几年下来也算比较熟稔了,总结几个有益的实践。
用途一:存储设计时无法预料的文档性的数据。比如,通常可以在人员表准备一个 json 类型字段,名字叫 info、tag 之类。人员表是系统很难设计的表,常常需要扩充各类信息,如驾照号、社保号等等,在设计时不能全盘考虑到,这些信息的特点是用于登记、检索,但与其它表没有外键关系。有的信息有多个,比如教育经历,包含有中学大学研究生等等。以往需要分出子表或以嵌套表存储,实现复杂,造成 ER 图日益庞大。引入 JSON 类型后,此类嵌套表几乎都可以合并入 JSON 字段。
又如,网站系统的会员信息中需要保存APP用户的设备信息。最开始可能设计为
device |
9I6HHL7K3L4OU |
后来发现,需要保存设备的类型,JSON 字段可以演变为:
device |
{id:'9I6HHL7K3L4OU', type: 'ios'} |
继而,用户可能有多个设备需要记录, JSON 字段可以追随演变为:
device |
[{id:'9I6HHL7K3L4OU', type: 'ios'}, ...] |
可见,JSON 字段就像可以装入大象的冰箱,打开了这道门就可以存储无尽的信息。
采用 JSON 字段后,数据库设计阶段只需要考虑主要业务实体之间的结构,抓大放小,设计思路也更清晰而不至于琐碎。我们知道,软件系统是一个生长的过程,很多东西在设计初期都难以预料,考虑再周详也有未尽之处,何况随着系统使用,需求变更,原以为是属性的,可能要改为数组,原以为是 flat 数组的,可能发现要改用对象数组,这些变迁屡见不鲜,如果采用静态设计,Schema 需要反复调整,对于使用SSH 框架进行开发的,每次调整带来的变动更多。JSON 类型使系统设计更能适合系统不断的演变进化。
将嵌套表放在 JSON 字段中,也避免了 JOIN 带来的性能损失。但有时需要从子表进行检索,如需要查询教育经历包含本科的所有人,这种场景还是应当使用子表,或 JSON 信息和子表同时使用,子表仅用于此类需要从子表出发的检索,这种数据冗余是值得的。
用途二:合并表间继承。表间继承关系通常采用“主从表同主键、从表主键外键到主表主键”实现,PG 数据库还设计了专门的表继承语法语义。引入 JSON 类型后,不少继承可以取消。例如,之前常用的组织机构与公司、部门、科室为继承关系,以往公司表、部门表、科室表都从组织机构表继承。引入 JSON 后子类表可取消,合并到组织机构表,该表通过新增 TYPE 字段区分是何种类型的组织机构,对于不同类型的TYPE,其 INFO 字段(JSON) 拥有不同的结构。
这得益于 JSON 数据没有固定结构,每个 JSON 对象都是自说明的,都可以拥有自己的结构,不像静态语言的对象只能获得从类型定义规定的结构。
用途三:从功能角度进行更高级别的抽象。
13 年编写过一个 ETL 工具,在异构数据库中间同步数据。该工具需要将源数据库的数据采集到中心,再由中心分发到目标数据库,数据要在中心 进行存储。中心应该是中立于数据的,不应考虑传输的数据是服装信息、药品信息、还是人员信息,其只应具备传输保管能力,不应具备业务知识,如同 TCP 一样,不应理解传输的是视频还是 HTML,不能为传输HTML搞一个TCP,为传输视频另搞一个TCP。
站在 ETL 功能的视角,所传输的内容都可以理解为对象,据此,可将采集到的数据行不识别结构的全部压缩到一个名为 OBJECT 的 JSON 字段。另外在实际使用时还要考虑对象之间的主从依赖关系,在新增数据时,先新增主表行,再新增子表行,在删除数据时反之。因此,还需要记忆所属对象。最终这个转储中心数据表结构如下:
ID OBJECT(JSON) TYPE PARENT_ID(FK:ID)
该表其它外围字段此处不赘述。
同样,设计流程系统可着眼于工作环节,将工作环节的其它信息,如地点等等压缩进 JSON 字段,根据这条原理我曾设计过一个万能的 task – actor 表结构,将之前若干貌似各不相干的业务合并为前后串联的 task。
activiti 如能用这种思路组织 task 等信息,当可达到更灵活便利的效果。
值得一提的是,d2js 框架高度支持 json 类型数据,数据库的 json 取出即成为编程语言的对象,编程语言的对象也可直接存入数据库,不需要借助 ORM 映射方式,为开发带来了极大便利。
在没有使用 json 前,很多人担心 json 字段是一个整体无法检索。这个担心大可不必,json 字段存储的对象也可对其属性进行检索,并可建立索引。当然,JSON字段也存在一点缺陷,作为一个整体,在更新时会一次性引发若干索引的更新。但相比得到的优势来说,优势更为明显。
另外,在实践中 pg 应使用 jsonb 字段类型,而不是 json 类型,jsonb 是 9.5 后推出的 binary 方式存储的 json 类型,类似 mongodb 的 bson,而 json 类型是字符串方式存储的,显然 jsonb 更节约空间且效率更高。
pg 提供了不少极有用的 json 操作符和函数,开发者应一一试验做一个接触式了解。