暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

搞懂Mysql的JSON数据类型

程序员恰恰 2024-04-15
16

JSON 数据类型是 MySQL 5.7.8 开始支持的。在此之前,只能通过字符类型(CHAR,VARCHAR 或 TEXT )来保存 JSON 文档。MySQL 8.0版本中增加了对JSON类型的索引支持。可以使用CREATE INDEX语句创建JSON类型的索引,提高JSON类型数据的查询效率。

存储JSON文档所需的空间与存储LONGBLOB或LONGTEXT所需的空间大致相同。在MySQL 8.0.13之前,JSON列不能有非空的默认值。JSON 类型比较适合存储一些列不固定、修改较少、相对静态的数据。MySQL支持JSON格式的数据之后,可以减少对非关系型数据库的依赖。

实战:假设有这样一个去求,某个图书管理系统的业务是存图书,根据图书的属性查询图书。图书的属性有很多,我们暂且假设有10个熟悉吧。包含标题、作者、流派、出版年份、出版商、ISBN、语言、页数、评分和价格等属性。

怎么存储数据呢?

方式1:反三范式,建立宽表,把所有的属性全部列出来。

这种设计方式的问题也很明显,作为一行记录的话,那么有的列是空的,有的列则是有值。在查询的时候,就要使用动态列判断的情况,此外一个图书属于多个行业,涉及到一对多的情况,多对多的情况,显然宽表设计不能满足需求。
方式2:使用json类型,设计表有三个属性,id,doc_name,extra,其中extra的数据类型是JSON。

模拟插入十行数据。

INSERT INTO `document` (`id``doc_name``extra`)
VALUES
 (1'了不起的盖茨比''{\"isbn\": \"9780743273565\", \"genre\": \"小说\", \"pages\": 180, \"price\": 10.99, \"title\": \"了不起的盖茨比\", \"author\": \"F·斯科特·菲茨杰拉德\", \"rating\": 4.2, \"language\": \"英语\", \"publisher\": \"斯克里布纳\", \"publication_year\": 1925}'),
 (2'杀死一只知更鸟''{\"isbn\": \"9780061120084\", \"genre\": \"小说\", \"pages\": 281, \"price\": 12.5, \"title\": \"杀死一只知更鸟\", \"author\": \"哈珀·李\", \"rating\": 4.5, \"language\": \"英语\", \"publisher\": \"利普丘克\", \"publication_year\": 1960}'),
 (3'1984年''{\"isbn\": \"9780451524935\", \"genre\": \"小说\", \"pages\": 328, \"price\": 9.99, \"title\": \"1984年\", \"author\": \"乔治·奥威尔\", \"rating\": 4.6, \"language\": \"英语\", \"publisher\": \"塞克与沃尔堡\", \"publication_year\": 1949}'),
 (4'麦田里的守望者''{\"isbn\": \"9780316769488\", \"genre\": \"小说\", \"pages\": 224, \"price\": 11.25, \"title\": \"麦田里的守望者\", \"author\": \"J.D.塞林格\", \"rating\": 4.0, \"language\": \"英语\", \"publisher\": \"小布朗\", \"publication_year\": 1951}'),
 (5'傲慢与偏见''{\"isbn\": \"9780486284736\", \"genre\": \"小说\", \"pages\": 279, \"price\": 8.75, \"title\": \"傲慢与偏见\", \"author\": \"简·奥斯汀\", \"rating\": 4.7, \"language\": \"英语\", \"publisher\": \"T.埃格顿\", \"publication_year\": 1813}'),
 (6'霍比特人''{\"isbn\": \"9780261103344\", \"genre\": \"奇幻\", \"pages\": 310, \"price\": 14.99, \"title\": \"霍比特人\", \"author\": \"J.R.R.托尔金\", \"rating\": 4.8, \"language\": \"英语\", \"publisher\": \"乔治·艾伦与温恩\", \"publication_year\": 1937}'),
 (7'哈利·波特与魔法石''{\"isbn\": \"9780747532743\", \"genre\": \"奇幻\", \"pages\": 309, \"price\": 13.5, \"title\": \"哈利·波特与魔法石\", \"author\": \"J.K.罗琳\", \"rating\": 4.9, \"language\": \"英语\", \"publisher\": \"布鲁姆斯伯里\", \"publication_year\": 1997}'),
 (8'达·芬奇密码''{\"isbn\": \"9780385504201\", \"genre\": \"悬疑\", \"pages\": 454, \"price\": 16.75, \"title\": \"达·芬奇密码\", \"author\": \"丹·布朗\", \"rating\": 4.3, \"language\": \"英语\", \"publisher\": \"道布尔戴\", \"publication_year\": 2003}'),
 (9'指环王''{\"isbn\": \"9780618640157\", \"genre\": \"奇幻\", \"pages\": 1178, \"price\": 24.99, \"title\": \"指环王\", \"author\": \"J.R.R.托尔金\", \"rating\": 4.7, \"language\": \"英语\", \"publisher\": \"乔治·艾伦与温恩\", \"publication_year\": 1954}'),
 (10'牧羊少年奇幻之旅''{\"isbn\": \"9780062315007\", \"genre\": \"小说\", \"pages\": 197, \"price\": 11.0, \"title\": \"牧羊少年奇幻之旅\", \"author\": \"保罗·柯艾略\", \"rating\": 4.6, \"language\": \"英语\", \"publisher\": \"哈珀柯林斯\", \"publication_year\": 1988}')

复制
用法:查询所有的图书编号。其中$代表这是一个json结构。$.后面跟的是json属性。
# 查询图书的编号信息
select extra -> '$.isbn' from document

复制

同时我们也能添加筛选条件。
# 查询售价是12.5的图书的编号信息
select document.*, extra -> '$.isbn' from document where extra ->'$.price' =12.5

复制

问题:上面的查询不走索引,我们通过explain可以看到该查询没有走索引,查询是全表扫描,假设有1000w数据,查询则会非常慢。如何优化呢?
利用:使用虚拟列
顾名思义:给表加一个虚拟的列,用法如下
ALTER TABLE document 
Add Column `v_request_id` varchar(32)
GENERATED ALWAYS AS (json_unquote(json_extract(`extra`,_utf8mb4'$.isbn'))) VIRTUAL NULL

复制

这条 SQL 语句:用于向名为 document 的表中添加一个名为 v_request_id 的虚拟列。我来详细解释一下:

  1. ALTER TABLE document: 这是告诉数据库你要修改名为 document 的表的结构。

  2. ADD COLUMN v_request_id varchar(32): 这部分是指定要添加的新列的名称和数据类型。在这里,我们添加了一个名为 v_request_id 的列,其数据类型为 varchar(32),即最大长度为 32 个字符的字符串。

  3. GENERATED ALWAYS AS (json_unquote(json_extract(extra,_utf8mb4'$.isbn'))) VIRTUAL NULL: 这是定义虚拟列的部分。让我分解一下:

    • GENERATED ALWAYS AS: 这表示我们要创建一个根据其他列计算得出的虚拟列,它的值不会存储在数据库中,而是根据特定的计算公式动态生成。

    • (json_unquote(json_extract(extra,_utf8mb4'$.isbn'))): 这是计算虚拟列值的表达式。在这里,我们使用了 JSON 函数 json_extract 来从名为 extra 的 JSON 列中提取 .isbn 键对应的值,并使用 json_unquote 函数来解析 JSON 字符串。换句话说,我们从 extra 列中提取出的 JSON 数据中找到 isbn 键,并将其作为虚拟列的值。

    • VIRTUAL NULL: 这表示虚拟列的存储类型是虚拟的,不会实际存储任何数据。因为虚拟列是根据其他列的值动态计算生成的,所以不需要为其分配存储空间。

json_unquote 具体作用?
json_unquote 是一个 MySQL 中的函数,用于解析 JSON 字符串,并返回其原始的未引用形式。具体来说,它的作用是去除 JSON 字符串中的引号,将其还原为未被引用的形式。
在 SQL 查询中,当你从 JSON 数据中提取某个字段时,该字段的值通常是被引号包围的 JSON 字符串。但有时你可能需要获取原始的未被引用的值,这时就可以使用 json_unquote 函数。
举个例子,假设你有一个 JSON 字符串 '"value"',其中值被双引号引用着。如果你想要获取其中的原始值 "value"(去除引号),就可以使用 json_unquote('"value"'),它会返回原始的未被引用的值 "value"
在前面提到的 SQL 语句中,json_unquote 函数的作用是去除从 JSON 数据中提取出的 isbn 字段的引号,以便得到原始的 ISBN 值。
此时我们再次查询这个表的数据。发现多了一个动态的虚拟列。
此时我们可以对这个虚拟列添加索引。发现该查询用上了索引。

同理我们可以添加更多的虚拟列,并设置索引,来提升查询效率。

---THE END---


文章转载自程序员恰恰,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论