作者介绍:吴双桥 腾讯云工程师
本文主要介绍在MySQL 5.7.7开始引入的非结构化数据类型JSON的特性以及具体的实现方式(包括存储方式)。
1 . 文档合法性
在MySQL5.7.7对JSON提供原生类型的支持之前,用户可以用TEXT或者BLOB类型来存储JSON文档。但对于MySQL来说,用户插入的数据只是序列化后的一个普通的字符串,不会对JSON文档本身的语法合法性做检查,文档的合法性需要用户自己保证。在引入新的JSON类型之后,插入语法错误的JSON文档,MySQL会提示错误,并在插入之后做归一化处理,保证每一个键对应一个值。
2. 更有效的访问
MySQL 5.7.7+本身提供了很多原生的函数以及路径表达式来方便用户访问JSON数据。例如对于下面的JSON文档:
{ "a": [ [ 3, 2 ], [ { "c" : "d" }, 1 ] ], "b": { "c" : 6 }, "one potato": 7, "b.c" : 8 }
用户可以使用
$.a[1][0]
获取{ "c" : "d" }
$.a[1]
获取[ { "c" : "d" }, 1 ]
还可以使用通配符 *
和 **
来进行模糊匹配,详见下一段。
3 . 性能优化
在MySQL提供JSON原生支持之前,如果用户需要获取或者修改某个JSON文档的键值,需要把TEXT或者BLOB整个字符串读出来反序列化成JSON对象,然后通过各种库函数访问JSON数据。显然这样是非常没有效率的,特别是对较大的文档。而原生JSON的性能,特别是读性能非常好。根据Oracle公司针对200K+数据文档做的性能测试表明,同样的数据用TEXT和JSON类型的查询性能差异达到两个数量级以上,而且用户还可以对经常访问的JSON键值做索引,进一步提升性能。JSON数据操作性能的提升是基于JSON数据本身的存储结构的,下文会进一步介绍。
1. JSON的操作接口
根据MySQL官方文档的介绍,服务器端JSON函数的实现需要满足以下条件:
Requirements:
Non-requirements:
提供的函数列表具体为:
JSON_APPEND() JSON_ARRAY_INSERT() JSON_UNQUOTE() JSON_ARRAY()
JSON_REPLACE() JSON_CONTAINS() JSON_DEPTH() JSON_EXTRACT()
JSON_INSERT() JSON_KEYS() JSON_LENGTH() JSON_VALID()
JSON_MERGE() JSON_OBJECT() JSON_QUOTE() JSON_REMOVE()
JSON_CONTAINS_PATH() JSON_SEARCH() JSON_SET() JSON_TYPE()
以上函数的调用规则大多形如:
JSON_APPEND(json_doc, path, val[, path, val] ...)
第一个参数json_doc
为JSON文档,或者是表里面的某一列,也可以是JSON文档里面的嵌套子文档变量;
第二个参数path
为路径表达式,用来定位要访问的键,path
(即路径表达式)下面紧接着会介绍;
第三个参数val
有的函数可能没有,若有表示键对应的操作数值。
2. JSON路径表达式
为了更方便快速的访问JSON的键值,MySQL 5.7.7+提供了新的路径表达式语法支持。前文提到的$.a[1][0]
就是路径表达式的一个具体的示例。完整的路径表达式语法为:
pathExpression> ::= scope [ ( pathLeg )* ]
scope ::= [ columnReference ] dollarSign
columnReference ::= [ [ databaseIdentifier period ] tableIdentifier period ] columnIdentifier
databaseIdentifier ::= sqlIdentifier
tableIdentifier ::= sqlIdentifier
columnIdentifier ::= sqlIdentifier
pathLeg ::= member | arrayLocation | doubleAsterisk
member ::= period ( keyName | asterisk )
arrayLocation ::= leftBracket ( non-negative-integer | asterisk ) rightBracket
keyName ::= ECMAScript-identifier | double-quoted-string-literal
doubleAsterisk ::= **
还是以
{ "a": [ [ 3, 2 ], [ { "c" : "d" }, 1 ] ], "b": { "c" : 6 }, "one potato": 7, "b.c" : 8 }
为例,再举几个例子说明:
$.a[1]
获取的值为 [ { "c" : "d" }, 1 ]
$.b.c
获取的值为 6
$."b.c"
获取的值为 8
对比上面最后两个例子,可以看到用引号包围的表达式会被当作一个字符串键值。
关于通配符*
和**
来进行模糊匹配需要做进一步的说明。
两个连着星号**
不能作为表达式的结尾,不能出现连续的三个星号***
单个星号*
表示匹配某个JSON对象中所有的成员
[*]
表示匹配某个JSON数组中的所有元素
prefix**suffix
表示所有以prefix开始,以suffix结尾的路径
举个具体的例子,直接在MySQL命令行里面输入:
select json_extract('{ "a": [ [ 3, 2 ], [ { "c" : "d" }, 1 ] ], "b": { "c" : 6 }, "one potato": 7, "b.c" : 8 }','$**.c');
得到显示结果:["d", 6]
。
在处理JSON时,MySQL使用的utf8mb4字符集,utf8mb4是utf8和ascii的超集。由于历史原因,这里utf8并非是我们常说的UTF-8 Unicode变长编码方案,而是MySQL自身定义的utf8编码方案,最长为三个字节。具体区别非本文重点,请大家自行Google了解。
MySQL在内存中是以DOM的形式表示JSON文档,而且在MySQL解析某个具体的路径表达式时,只需要反序列化和解析路径上的对象,而且速度极快。要弄清楚MySQL是如何做到这些的,我们就需要了解JSON在硬盘上的存储结构。有个有趣的点是,JSON对象是BLOB的子类,在其基础上做了特化。
根据MySQL官方文档的表述:
On a high level, we will store the contents of the JSON document in three sections:
我们来使用示意图更清晰的展示它的结构:
JSON文档本身是层次化的结构,因而MySQL对JSON存储也是层次化的。对于每一级对象,存储的最前面为存放当前对象的元素个数,以及整体占的大小。需要注意的是:
现在MySQL不支持对JSON列进行索引,官网文档的说明是:
JSON columns cannot be indexed. You can work around this restriction by creating an index on a generated column that extracts a scalar value from the JSON column.
虽然不支持直接在JSON列上建索引,但MySQL规定,可以首先使用路径表达式对JSON文档中的标量值建立虚拟列,然后在虚拟列上建立索引。这样用户可以使用表达式对自己感兴趣的键值建立索引。举个具体的例子来说明:
CREATE TABLE features (
id INT NOT NULL AUTO_INCREMENT,
feature JSON NOT NULL,
PRIMARY KEY (id)
);
插入它的JSON数据的格式为:
{
"type":"Feature",
"properties":{
"TO_ST":"0",
"BLKLOT":"0001001",
"STREET":"UNKNOWN",
"FROM_ST":"0",
"LOT_NUM":"001",
"ST_TYPE":null,
"ODD_EVEN":"E",
"BLOCK_NUM":"0001",
"MAPBLKLOT":"0001001"
}
}
使用:
ALTER TABLE features ADD feature_street VARCHAR(30) AS (JSON_UNQUOTE(feature->"$.properties.STREET"));
ALTER TABLE features ADD INDEX (feature_street);
两个步骤,可以对feature列中properties键值下的STREET键(feature->"$.properties.STREET"
)创建索引。
其中,feature_street
列就是新添加的虚拟列。之所以取名虚拟列,是因为与它对应的还有一个存储列(stored column)。它们最大的区别为虚拟列只修改数据库的metadata,并不会存储真实的数据在硬盘上,读取过程也是实时计算的方式;而存储列会把表达式的列存储在硬盘上。两者使用的场景不一样,默认情况下通过表达式生成的列为虚拟列。
这样虚拟列的添加和删除都会非常快,而在虚拟列上建立索引跟传统的建立索引的方式并没有区别,会提高虚拟列读取的性能,减慢整体插入的性能。虚拟列的特性结合JSON的路径表达式,可以方便的为用户提供高效的键值索引功能。
JSON值可以使用=, <, <=, >, >=, <>, !=, <=>等操作符,BETWEEN
, IN
,GREATEST
, LEAST
等操作符现在还不支持。JSON值使用的两级排序规则,第一级基于JSON的类型,类型不同的使用每个类型特有的排序规则。
JSON类型按照优先级从高到低为
BLOB
BIT
OPAQUE
DATETIME
TIME
DATE
BOOLEAN
ARRAY
OBJECT
STRING
INTEGER, DOUBLE
NULL
优先级高的类型大,不用再进行其他的比较操作;如果类型相同,每个类型按自己的规则排序。具体的规则如下:
任何JSON值与SQL的NULL常量比较,得到的结果是UNKNOWN。对于JSON值和非JSON值的比较,按照一定的规则将非JSON值转化为JSON值,然后按照以上的规则进行比较。
本文主要介绍了MySQL在5.7.7之后引入的原生JSON支持的特性,说明了引入JSON类型的好处,并结合具体的示例介绍了MySQL在JSON类型上对外的接口以及引入的新语法规则。此外,还重点介绍了JSON在硬盘上的存储结构,简要分析了这种存储结构的优势和不足。最后还介绍了JSON的索引原理,以及比较和排序规则。相信理解了本文介绍的内容,关于JSON文中没有提到的部分内容也较容易理解。
更多数据库文章,请关注腾讯云数据库公众号: QcloudCDB
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有