当前,一些应用程序在数据库层使用 JSON格式的字段。JSON 有很好的灵活性,它可以自由地包含不同键。然后,关系型数据库对JSON的处理能力天生不足。因此,在关系型数据库中使用JSON时应当遵循一定的思想,从而既能受益于JSON的灵活性,又能发挥关系型数据库的强大功能。
本文根据实际工作中的经验,结合一些国内外现有的资料,总结了一些在关系型数据库中使用JSON 的设计思想和注意事项。文章旨在指导读者更好地进行应用的数据库设计。
本文使用的数据库是PostgreSQL。
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
JSON建构于两种结构:
1.“名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
2. 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。
JSON对象是一个无序的“名称/值”对集合。一个对象以“{” (左括号) 开始,以“}” (右括号) 结束。每个“名称”后跟一个 “:”(冒号);“名称/值”对之间使用 “,”(逗号 )分隔。例如:
{"a": 1, "b": [1, 2, 3]}
JSON数组是值(value)的有序集合。一个数组以“[” 左中括号 开始, “]”右中括号 结束。值之间使用“,” 逗号分隔。例如:
[1, 2, "3", {"a": 4}]
JSON的值(value)可以是双引号(””)括起来的字符串(string)、数值(number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。
1. JSON 只能作为一种辅助型的数据格式,不要试图用JSON 去取代表中大多数字段,也不要将不合适的属性放在JSON中[2]。
2. JSON 格式的字段对数据库最好是透明的[2]。传统的关系型数据库并不提供JSON处理功能。近年来Oracle,PostgreSQL 和 MySQL 引入了JSON 处理功能,但功能有限。另外,关系型数据库的第一范式,要求所有字段都是原子性的(虽然我们不必严格遵守),这个规则应尽量遵守。
3. 如果因业务需求,一些 JSON 格式的字段要用数据库处理,应该保证,JSON 结构尽量是单层的,由简单的键值对组成。这样便于读取,修改和扩展。如果它有嵌套结构,也应保证内层结构对数据库尽可能透明。
例如,这里的JSON是单层的。
{
"doorNo": "1",
"secret": "15F50xyeyEE3nLqop7ub7fd7d7R1zPYgMNBg/D+6t8+el8gnxl4Wc0ZBZlkgkMdb",
"readerInOrOut": "1",
"controllerLevel": "3",
"devEngModeEnable": null,
"secParentIndexCode": null,
"acsReaderCardCapacity": "0",
"ReaderFaceCapacity": "0",
"ReaderFingerCapacity": "0"
}
使用关系型数据库查询JSON 中的值时,应该明确地给定JSON 键的路径。不能在键的未知的情况下,通过值来获取它的键。就如同在关系型数据库,我们不能在字段未知的情况下,通过值来获取字段名一样。
上面的例子也说明,不是所有的属性都适合放在JSON 中。
不是所有属性都适合放在JSON中。不合理,不周全的设计会不仅会会影响程序的性能,还会给软件的开发和维护造成困难。因此,我们应该在追求JSON 的灵活性和发挥关系型数据库的功能之间取得平衡。
下面是不适合放在JSON中的属性:
1.作为重要的选择条件,或用于连接(join)、排序(使用了order by)、去重(使用了distinct)或分组(使用了group by)运算的属性[3][5]。尽管我们可以通过数据库提供的函数或操作符获取 JSON 中的属性,并通过它进行上述运算,但处理较复杂,性能不佳,从2.2节的案例中就可以看出。因此,这样的属性应当作为原子性的字段存在。
以下面的人员表tb_person为例:
create table tb_person
(
person_id varchar(48) default gen_random_uuid() not null,
person_index_code varchar(48),
person_name varchar(64),
sex integer,
organization_indexcode varchar(48),
organization_name varchar(48),
status integer,
phone varchar(64),
email varchar(64),
age integer,
birthday date,
create_time timestamp with time zone default current_timestamp(3),
update_time timestamp with time zone default current_timestamp(3),
extended_attribute jsonb,
constraint uk_tb_person_person_index_code unique (person_index_code, status),
constraint pk_tb_person primary key (person_id, status)
);
人员表tb_person这些属性中,人员id(person_id),编码(person_index_code),所属组织的编码(organization_indexcode),当前状态(status)和创建时间(create_time)都是业务中重要的选择条件;而所属组织的编码(organization_indexcode)是人员表和组织表的关联字段,也做为分组运算的条件(我们可以根据组织对人员分组);创建时间(create_time)是常用的排序字段。它们都需要作为独立的字段,而不适合放在JSON中。
2.某个属性的读取和更新频率比其他属性频繁很多,则它们不适合放在同一个 JSON 字段中。因为 JSON 字段占用空间很大,读写代价都很高。因此为提高性能,应将读写频繁的属性作为单独的字段。
例如,对于一个对于一个停车场表来说,它的属性,总车位数和当前可用车位数就不能放在同一个JSON中,如果使用JSON的话。因为前者通常是固定的,而后者是动态变化的。
3.为了提高查询速度而设置的冗余属性。这样的属性通常依赖于某些非主属性,放在JSON中则不符合提高性能的目的,而且不易修改。
例如,人员表tb_person中,非主属性有组织编码(organization_indexcode)和组织名称(organization_name)。组织名称依赖于组织编码,会随组织编码的变更而变更,不适合放置在JSON中。
4.本身定义不合理的,与其他属性重复的,或可能会被弃用的属性。从数据库的层面看,JSON是一个整体,单个属性的缺陷就是整个JSON的缺陷。而且修改或删除这样的有缺陷属性,代价会比修改或删除单独字段的代价高。因此,这一类属性,应当予以删除或改造。
1. 用户定义的属性,可以放在 JSON 中[4]。
2. 若属性是一个集合,则可以用JSON数组表示[2][4]。
3. 若表中有较大比例的行没有该属性,或该属性为空,则可以将该属性放在JSON字段中。
4. 对数据库透明,仅由上层程序处理的属性,可以放在JSON字段中。这样的JSON 字段一般很少在数据库中修改,而JSON 作为一个整体在程序中传输[3]。
如果因业务原因,需要将表中的几个字段合并为JSON,除了前几节的事项外,还需要注意以下几点:
1. JSON 结构尽量简单,理想的情况下,合并后的JSON 的键和值分别对应原有的字段和它的值。
2. 用来合成 JSON 的原字段最好是原子性的,或者对数据库是透明的。这样的字段便于合并,在 JSON 中也容易解析。
3. 最后,如果你需要用一些非原子性的字段构造一个复杂的 JSON,则应该详细地写出构造的方法步骤,再进行编码。
如果需要修改数据库中 JSON 字段的数据,除了上面的事项,还需要注意以下几点:
1. 增加,修改或删除JSON 字段中的键值对时,应该明确地给定表上的选择条件和JSON 键的路径,从而使我们能够直接通过给定条件获取表中需要更改的行,并能够根据 JSON 键的完整路径来添加,修改或删除键值对。如果新增的值,或者修改后的值依赖于 JSON 中某个键的值,则被依赖的键的路径也应该是明确的。
2. 修改JSON的结构应该慎重。JSON 字段的结构在确定之后,不应该发生较大变化。
[1] 介绍 JSON
[2] Colin M. Answer: Storing JSON in database vs. having a new column for each key?. 2017-09-15.
[3] Vishal Kumar. Answer: Is it okay to use JSON as a database. 2018-06-26.
[4] Arun B Chandrasekaran. Using JSON Datatype In Relational Database To Develop Flexible/Configurable Software. 2018-11-11
[5] Bill Karwin. Answer: Why not use relational databases to store JSON data (like a primary key field, and a BLOB field for JSON data in a MySQL table) instead of using NoSQL databases (MongoDB, etc.)?. 2019-04-14