JSON 介绍

  • JSON(JavaScript Object Notation)是一种轻量级的数据交换语言,并且是独立于语言的文本格式。
  • 一些 NoSQL 数据库选择 JSON 作为其数据存储格式,比如:MongoDB、CouchDB 等。
  • MySQL 5.7.x 开始支持 JSON 数据类型。

官方文档(JSON 类型):https://dev.mysql.com/doc/refman/8.0/en/json.html

JSON 类型适合用来处理一些经常需要变动表结构的情况。应该结构化数据是需要预先定义好表结构的,如果后面业务有变动,需要通过 ALTER TABLE 命令来改变表结构,而每次 ALTER TABLE 的代价很大,如果表的数据量很大,需要花费很长时间,这种情况下,可以尝试使用 MySQL 的 JSON 类型。

结构化 & 非结构化

结构化数据:通常我们说的关系型数据就是结构化数据,二维表结构(行和列),使用 SQL 语句进行操作。

非结构化数据:比如像 MongoDB 就是使用 JSON 作为其数据存储格式,无结构定义(Schema Free)。

  1. --
  2. -- SQL创建User
  3. --
  4. create table user (
  5. id bigint not null auto_increment,
  6. user_name varchar(10),
  7. age int,
  8. primary key(id)
  9. );
  10. #
  11. # JSON定义的User表
  12. #
  13. db.user.insert({
  14. user_name:"tom",
  15. age:30
  16. })
  17. db.createCollection("user")

JSON VS BLOB

JSON

  • JSON 数据可以做有效性检查。
  • JSON 使得查询性能提升。
  • JSON 支持部分属性索引,通过虚拟列的功能可以对 JSON 中的部分数据进行索引。

BLOB

  • BLOB 类型无法在数据库层做约束性检查。
  • BLOB 进行查询,需要遍历所有字符串。
  • BLOB 做只能做指定长度的索引。

5.7之前,只能把 JSON 当作 BLOB 进行存储。数据库层面无法对 JSON 数据做一些操作,只能由应用程序处理。

JSON 操作示例

JSON入门

  1. --
  2. -- 创建带json字段的表
  3. --
  4. mysql> create table user (
  5. -> uid int auto_increment,
  6. -> data json,
  7. -> primary key(uid)
  8. -> );
  9. Query OK, 0 rows affected (0.11 sec)
  10. --
  11. -- 插入json数据
  12. --
  13. mysql> insert into user values (
  14. -> null, -- 自增长数据,可以插入null
  15. -> '{
  16. '> "name":"tom",
  17. '> "age":18,
  18. '> "address":"SZ"
  19. '> }'
  20. -> );
  21. Query OK, 1 row affected (0.03 sec)
  22. mysql> insert into user values (
  23. -> null,
  24. -> '{
  25. '> "name":"jim",
  26. '> "age":28,
  27. '> "mail":"jim@163.com"
  28. '> }'
  29. -> );
  30. Query OK, 1 row affected (0.02 sec)
  31. mysql> insert into user values ( null, "can you insert it?"); -- 无法插入,因为是JSON类型
  32. ERROR 3140 (22032): Invalid JSON text: "Invalid value." at position 0 in value (or column) can you insert it?. -- 这短话有单引号,但是渲染有问题,所以这里去掉了
  33. mysql> select * from user;
  34. +-----+---------------------------------------------------+
  35. | uid | data |
  36. +-----+---------------------------------------------------+
  37. | 1 | {"age": 18, "name": "tom", "address": "SZ"} | -- 这个json中有address字段
  38. | 2 | {"age": 28, "mail": "jim@163.com", "name": "jim"} | -- 这个json中有mail字段
  39. +-----+---------------------------------------------------+
  40. 2 rows in set (0.00 sec)

JSON 常用函数介绍

官方文档(JSON 函数):https://dev.mysql.com/doc/refman/8.0/en/json-function-reference.html

  1. --
  2. -- 使用json_extract提取数据
  3. -- 原型 : JSON_EXTRACT(json_doc, path[, path] ...)
  4. --
  5. mysql> select json_extract('[10, 20, [30, 40]]', '$[1]');
  6. +--------------------------------------------+
  7. | json_extract('[10, 20, [30, 40]]', '$[1]') |
  8. +--------------------------------------------+
  9. | 20 | -- list中抽取 下标 1的元素(下标从0开始)
  10. +--------------------------------------------+
  11. 1 row in set (0.00 sec)
  12. mysql> select
  13. -> json_extract(data, '$.name'), -- 提起name字段的数据
  14. -> json_extract(data, '$.address') -- 提取address字段的数据
  15. -> from user;
  16. +------------------------------+---------------------------------+
  17. | json_extract(data, '$.name') | json_extract(data, '$.address') |
  18. +------------------------------+---------------------------------+
  19. | "tom" | "SZ" |
  20. | "jim" | NULL | -- jim 没有address字段,填充了NULL
  21. +------------------------------+---------------------------------+
  22. 2 rows in set (0.00 sec)
  23. --
  24. -- json_object list(K-V对)封装成json格式
  25. -- 原型 : JSON_OBJECT([key, val[, key, val] ...])
  26. --
  27. mysql> select json_object("name", "jery", "email", "jery@163.com", "age",33);
  28. +----------------------------------------------------------------+
  29. | json_object("name", "jery", "email", "jery@163.com", "age",33) |
  30. +----------------------------------------------------------------+
  31. | {"age": 33, "name": "jery", "email": "jery@163.com"} | -- 封装成了K-V
  32. +----------------------------------------------------------------+
  33. 1 row in set (0.00 sec)
  34. mysql> insert into user values (
  35. -> null,
  36. -> json_object("name", "jery", "email", "jery@163.com", "age",33) -- 进行封装
  37. -> );
  38. Query OK, 1 row affected (0.03 sec)
  39. mysql> select * from user;
  40. +-----+------------------------------------------------------+
  41. | uid | data |
  42. +-----+------------------------------------------------------+
  43. | 1 | {"age": 18, "name": "tom", "address": "SZ"} |
  44. | 2 | {"age": 28, "mail": "jim@163.com", "name": "jim"} |
  45. | 4 | {"age": 33, "name": "jery", "email": "jery@163.com"} |
  46. +-----+------------------------------------------------------+
  47. 3 rows in set (0.00 sec)
  48. --
  49. -- json_insert 插入数据
  50. -- 原型 : JSON_INSERT(json_doc, path, val[, path, val] ...)
  51. --
  52. mysql> set @j = '{ "a": 1, "b": [2, 3]}';
  53. Query OK, 0 rows affected (0.00 sec)
  54. mysql> select json_insert(@j, '$.a', 10, '$.c', '[true, false]');
  55. +----------------------------------------------------+
  56. | json_insert(@j, '$.a', 10, '$.c', '[true, false]') |
  57. +----------------------------------------------------+
  58. | {"a": 1, "b": [2, 3], "c": "[true, false]"} | -- a还是=1,存在的被忽略,不受影响
  59. +----------------------------------------------------+ -- c之前不存在,则插入
  60. 1 row in set (0.00 sec)
  61. mysql> update user set data = json_insert(data, "$.address_2", "BJ") where uid = 1; -- 插入 addres_2
  62. Query OK, 1 row affected (0.03 sec)
  63. Rows matched: 1 Changed: 1 Warnings: 0
  64. mysql> select * from user;
  65. +-----+----------------------------------------------------------------+
  66. | uid | data |
  67. +-----+----------------------------------------------------------------+
  68. | 1 | {"age": 18, "name": "tom", "address": "SZ", "address_2": "BJ"} | -- 增加了addres_2 : "BJ"
  69. | 2 | {"age": 28, "mail": "jim@163.com", "name": "jim"} |
  70. | 4 | {"age": 33, "name": "jery", "email": "jery@163.com"} |
  71. +-----+----------------------------------------------------------------+
  72. 3 rows in set (0.00 sec)
  73. --
  74. -- json_merge 合并数据并返回。注意:原数据不受影响
  75. -- 原型 : JSON_MERGE(json_doc, json_doc[, json_doc] ...)
  76. --
  77. mysql> select json_merge('{"name": "x"}', '{"id": 47}'); -- 原来有两个JSON
  78. +-------------------------------------------+
  79. | json_merge('{"name": "x"}', '{"id": 47}') |
  80. +-------------------------------------------+
  81. | {"id": 47, "name": "x"} | -- 合并多个JSON
  82. +-------------------------------------------+
  83. 1 row in set (0.00 sec)
  84. mysql> select
  85. -> json_merge(
  86. -> json_extract(data, '$.address'), -- json 1
  87. -> json_extract(data, '$.address_2')) -- jons 2
  88. -> from user where uid = 1;
  89. +---------------------------------------------------------------------------------+
  90. | json_merge( json_extract(data, '$.address'), json_extract(data, '$.address_2')) |
  91. +---------------------------------------------------------------------------------+
  92. | ["SZ", "BJ"] | -- 合并成一个json
  93. +---------------------------------------------------------------------------------+
  94. 1 row in set (0.00 sec)
  95. --
  96. -- json_array_append 追加数据
  97. -- 原型 : JSON_ARRAY_APPEND(json_doc, path, val[, path, val] ...)
  98. -- json_append 5.7.9 中重命名为 json_array_append
  99. --
  100. mysql> set @j = '["a", ["b", "c"], "d"]'; -- 下标为1的元素中只有["b", "c"]
  101. Query OK, 0 rows affected (0.00 sec)
  102. mysql> select json_array_append(@j, '$[1]', 1);
  103. +----------------------------------+
  104. | json_array_append(@j, '$[1]', 1) |
  105. +----------------------------------+
  106. | ["a", ["b", "c", 1], "d"] | -- 现在插入了 数字 1
  107. +----------------------------------+
  108. 1 row in set (0.00 sec)
  109. mysql> update user set data = json_array_append(
  110. -> data,
  111. -> '$.address',
  112. -> json_extract(data, '$.address_2'))
  113. -> where uid = 1;
  114. Query OK, 1 row affected (0.02 sec)
  115. Rows matched: 1 Changed: 1 Warnings: 0
  116. mysql> select * from user;
  117. +-----+------------------------------------------------------------------------+
  118. | uid | data |
  119. +-----+------------------------------------------------------------------------+
  120. | 1 | {"age": 18, "name": "tom", "address": ["SZ", "BJ"], "address_2": "BJ"} | --address_2追加到address
  121. | 2 | {"age": 28, "mail": "jim@163.com", "name": "jim"} |
  122. | 4 | {"age": 33, "name": "jery", "email": "jery@163.com"} |
  123. +-----+------------------------------------------------------------------------+
  124. 3 rows in set (0.00 sec)
  125. --
  126. -- json_remove json记录中删除数据
  127. -- 原型 : JSON_REMOVE(json_doc, path[, path] ...)
  128. --
  129. mysql> set @j = '["a", ["b", "c"], "d"]';
  130. Query OK, 0 rows affected (0.00 sec)
  131. mysql> select json_remove(@j, '$[1]');
  132. +-------------------------+
  133. | json_remove(@j, '$[1]') |
  134. +-------------------------+
  135. | ["a", "d"] | -- 删除了下标为1的元素["b", "c"]
  136. +-------------------------+
  137. 1 row in set (0.00 sec)
  138. mysql> update user set data = json_remove(data, "$.address_2") where uid = 1;
  139. Query OK, 1 row affected (0.03 sec)
  140. Rows matched: 1 Changed: 1 Warnings: 0
  141. mysql> select * from user;
  142. +-----+------------------------------------------------------+
  143. | uid | data |
  144. +-----+------------------------------------------------------+
  145. | 1 | {"age": 18, "name": "tom", "address": ["SZ", "BJ"]} | -- address_2 的字段删除了
  146. | 2 | {"age": 28, "mail": "jim@163.com", "name": "jim"} |
  147. | 4 | {"age": 33, "name": "jery", "email": "jery@163.com"} |
  148. +-----+------------------------------------------------------+
  149. 3 rows in set (0.00 sec)

JSON 创建索引

JSON 类型数据本身无法直接创建索引,变相的通过将需要索引的 JSON 数据重新生成虚拟列(Virtual Columns)之后,对该列进行索引。

官方文档(JSON 创建索引):https://dev.mysql.com/doc/refman/8.0/en/create-table.html#create-table-secondary-indexes-virtual-columns

新建表时创建 JSON 索引

  1. mysql> create table test_inex_1(
  2. -> data json,
  3. -> gen_col varchar(10) generated always as (json_extract(data, '$.name')), -- 抽取data中的name 生成新的一列,名字为gen_col
  4. -> index idx (gen_col) -- gen_col 作为索引
  5. -> );
  6. Query OK, 0 rows affected (0.13 sec)
  7. mysql> show create table test_index_1
  8. -- -----省略表格线-----
  9. | test_index_1 | CREATE TABLE `test_index_1` (
  10. `data` json DEFAULT NULL,
  11. `gen_col` varchar(10) GENERATED ALWAYS AS (json_extract(data, '$.name')) VIRTUAL,
  12. KEY `idx` (`gen_col`)
  13. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
  14. -- -----省略表格线-----
  15. 1 row in set (0.00 sec)
  16. mysql> insert into test_index_1(data) values ('{"name":"tom", "age":18, "address":"SH"}');
  17. Query OK, 1 row affected (0.04 sec)
  18. mysql> insert into test_index_1(data) values ('{"name":"jim", "age":28, "address":"SZ"}');
  19. Query OK, 1 row affected (0.03 sec)
  20. mysql> select * from test_index_1;
  21. +---------------------------------------------+---------+
  22. | data | gen_col |
  23. +---------------------------------------------+---------+
  24. | {"age": 18, "name": "tom", "address": "SH"} | "tom" |
  25. | {"age": 28, "name": "jim", "address": "SZ"} | "jim" |
  26. +---------------------------------------------+---------+
  27. 2 rows in set (0.00 sec)
  28. mysql> select json_extract(data,"$.name") as username from test_index_1 where gen_col="tom"; -- 如果这样做,为空,原因如下
  29. Empty set (0.00 sec)
  30. mysql> select hex('"');
  31. +----------+
  32. | hex('"') |
  33. +----------+
  34. | 22 | -- 双引号的 16进制
  35. +----------+
  36. 1 row in set (0.00 sec)
  37. mysql> select hex(gen_col) from test_index_1;
  38. +--------------+
  39. | hex(gen_col) |
  40. +--------------+
  41. | 226A696D22 | -- 双引号本身也作为了存储内容
  42. | 22746F6D22 |
  43. +--------------+
  44. 2 rows in set (0.00 sec)
  45. mysql> select json_extract(data,"$.name") as username from test_index_1 where gen_col='"tom"'; -- 使用'"tome"',用单引号括起来
  46. +----------+
  47. | username |
  48. +----------+
  49. | "tom" | -- 找到了对应的数据
  50. +----------+
  51. 1 row in set (0.00 sec)
  52. mysql> explain select json_extract(data,"$.name") as username from test_index_1 where gen_col='"tom"'\G
  53. *************************** 1. row ***************************
  54. id: 1
  55. select_type: SIMPLE
  56. table: test_index_1
  57. partitions: NULL
  58. type: ref
  59. possible_keys: idx -- 使用了 key idx
  60. key: idx
  61. key_len: 43
  62. ref: const
  63. rows: 1
  64. filtered: 100.00
  65. Extra: NULL
  66. 1 row in set, 1 warning (0.00 sec)
  67. ---
  68. --- 建立表的时候去掉双引用
  69. ---
  70. mysql> create table test_index_2 (
  71. -> data json,
  72. -> gen_col varchar(10) generated always as (
  73. -> json_unquote( -- 使用json_unquote函数进行去掉双引号
  74. -> json_extract(data, "$.name")
  75. -> )),
  76. -> key idx(gen_col)
  77. -> );
  78. Query OK, 0 rows affected (0.13 sec)
  79. mysql> show create table test_index_2;
  80. -- -----省略表格线-----
  81. | test_index_2 | CREATE TABLE `test_index_2` (
  82. `data` json DEFAULT NULL,
  83. `gen_col` varchar(10) GENERATED ALWAYS AS (json_unquote(
  84. json_extract(data, "$.name")
  85. )) VIRTUAL,
  86. KEY `idx` (`gen_col`)
  87. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
  88. -- -----省略表格线-----
  89. 1 row in set (0.00 sec)
  90. mysql> insert into test_index_2(data) values ('{"name":"tom", "age":18, "address":"SH"}');
  91. Query OK, 1 row affected (0.03 sec)
  92. mysql> insert into test_index_2(data) values ('{"name":"jim", "age":28, "address":"SZ"}');
  93. Query OK, 1 row affected (0.02 sec)
  94. mysql> select json_extract(data,"$.name") as username from test_index_2 where gen_col="tom"; -- 未加单引号
  95. +----------+
  96. | username |
  97. +----------+
  98. | "tom" | -- 可以找到数据
  99. +----------+
  100. 1 row in set (0.00 sec)
  101. mysql> explain select json_extract(data,"$.name") as username from test_index_2 where gen_col="tom"\G
  102. *************************** 1. row ***************************
  103. id: 1
  104. select_type: SIMPLE
  105. table: test_index_2
  106. partitions: NULL
  107. type: ref
  108. possible_keys: idx -- 使用了 key idx
  109. key: idx
  110. key_len: 43
  111. ref: const
  112. rows: 1
  113. filtered: 100.00
  114. Extra: NULL
  115. 1 row in set, 1 warning (0.00 sec)

修改已存在的表创建 JSON 索引

  1. --
  2. -- 使用之前的user表操作
  3. --
  4. mysql> show create table user;
  5. -- -----省略表格线-----
  6. | user | CREATE TABLE `user` (
  7. `uid` int(11) NOT NULL AUTO_INCREMENT,
  8. `data` json DEFAULT NULL,
  9. PRIMARY KEY (`uid`)
  10. ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 |
  11. -- -----省略表格线-----
  12. 1 row in set (0.00 sec)
  13. mysql> select * from user;
  14. +-----+------------------------------------------------------+
  15. | uid | data |
  16. +-----+------------------------------------------------------+
  17. | 1 | {"age": 18, "name": "tom", "address": ["SZ", "BJ"]} |
  18. | 2 | {"age": 28, "mail": "jim@163.com", "name": "jim"} |
  19. | 4 | {"age": 33, "name": "jery", "email": "jery@163.com"} |
  20. +-----+------------------------------------------------------+
  21. mysql> alter table user
  22. -> add user_name varchar(32)
  23. -> generated always as (json_extract(data,"$.name")) virtual;
  24. Query OK, 0 rows affected (0.05 sec)
  25. Records: 0 Duplicates: 0 Warnings: 0
  26. -- virtual 关键字是不将该列的字段值存储,对应的是stored
  27. mysql> select user_name from user;
  28. +-----------+
  29. | user_name |
  30. +-----------+
  31. | "tom" |
  32. | "jim" |
  33. | "jery" |
  34. +-----------+
  35. 3 rows in set (0.00 sec)
  36. mysql> alter table user add index idx(user_name);
  37. Query OK, 0 rows affected (0.13 sec)
  38. Records: 0 Duplicates: 0 Warnings: 0
  39. mysql> select * from user where user_name='"tom"'; -- 加单引号
  40. +-----+-----------------------------------------------------+-----------+
  41. | uid | data | user_name |
  42. +-----+-----------------------------------------------------+-----------+
  43. | 1 | {"age": 18, "name": "tom", "address": ["SZ", "BJ"]} | "tom" |
  44. +-----+-----------------------------------------------------+-----------+
  45. 1 row in set (0.00 sec)
  46. mysql> explain select * from user where user_name='"tom"'\G
  47. *************************** 1. row ***************************
  48. id: 1
  49. select_type: SIMPLE
  50. table: user
  51. partitions: NULL
  52. type: ref
  53. possible_keys: idx -- 使用了 key idx
  54. key: idx
  55. key_len: 131
  56. ref: const
  57. rows: 1
  58. filtered: 100.00
  59. Extra: NULL
  60. 1 row in set, 1 warning (0.00 sec)
  61. mysql> show create table user;
  62. -- -----省略表格线-----
  63. | user | CREATE TABLE `user` (
  64. `uid` int(11) NOT NULL AUTO_INCREMENT,
  65. `data` json DEFAULT NULL,
  66. `user_name` varchar(32) GENERATED ALWAYS AS (json_extract(data,"$.name")) VIRTUAL,
  67. `user_name2` varchar(32) GENERATED ALWAYS AS (json_extract(data,"$.name")) VIRTUAL,
  68. PRIMARY KEY (`uid`),
  69. KEY `idx` (`user_name`)
  70. ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 |
  71. -- -----省略表格线-----
  72. 1 row in set (0.00 sec)

附录

  1. --
  2. -- 老师演示JSONSQL
  3. --
  4. drop table if exists User;
  5. CREATE TABLE User (
  6. uid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  7. name VARCHAR(32) NOT NULL,
  8. email VARCHAR(256) NOT NULL,
  9. address VARCHAR(512) NOT NULL,
  10. UNIQUE KEY (name),
  11. UNIQUE KEY (email)
  12. );
  13. INSERT INTO User VALUES (NULL,'David','david@gmail','Shanghai ...');
  14. INSERT INTO User VALUES (NULL,'Amy','amy@gmail','Beijing ...');
  15. INSERT INTO User VALUES (NULL,'Tom','tom@gmail','Guangzhou ...');
  16. SELECT * FROM User;
  17. ALTER TABLE User ADD COLUMN address2 VARCHAR(512) NOT NULL;
  18. ALTER TABLE User ADD COLUMN passport VARCHAR(64) NOT NULL;
  19. DROP TABLE IF EXISTS UserJson;
  20. CREATE TABLE UserJson(
  21. uid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  22. data JSON
  23. );
  24. truncate table UserJson;
  25. insert into UserJson
  26. SELECT
  27. uid,JSON_OBJECT('name',name,'email',email,'address',address) AS data
  28. FROM
  29. User;
  30. SELECT * FROM UserJson;
  31. SELECT uid,JSON_EXTRACT(data,'$.address2') from UserJson;
  32. UPDATE UserJson
  33. set data = json_insert(data,"$.address2","HangZhou ...")
  34. where uid = 1;
  35. SELECT JSON_EXTRACT(data,'$.address[1]') from UserJson;
  36. select json_merge(JSON_EXTRACT(data,'$.address') ,JSON_EXTRACT(data,'$.address2'))
  37. from UserJson;
  38. begin;
  39. UPDATE UserJson
  40. set data = json_array_append(data,"$.address",JSON_EXTRACT(data,'$.address2'))
  41. where JSON_EXTRACT(data,'$.address2') IS NOT NULL AND uid >0;
  42. select JSON_EXTRACT(data,'$.address') from UserJson;
  43. UPDATE UserJson
  44. set data = JSON_REMOVE(data,'$.address2')
  45. where uid>0;
  46. commit;

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/whwpuf 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。