当前位置:   article > 正文

MySQL JSON数据类型_mysql json类型

mysql json类型

一. JSON数据类型

JSON(JavaScript Object Notation)主要用于互联网应用服务之间的数据交换。MySQL 支持JSON 对象JSON 数组两种类型,JSON 类型是从 MySQL 5.7 版本开始支持的功能(强烈建议MySQL8下使用JSON,性能更佳),MySQL中使用JSON有以下好处:

  • 无须预定义字段:字段可以无限拓展,避免了ALTER ADD COLUMN的操作,使用更加灵活。

  • 处理稀疏字段:避免了稀疏字段的NULL值,避免冗余存储。

  • 支持索引:相比于字符串格式的JSON,JSON类型支持索引做特定的查询优化。

总体而言,JSON 类型比较适合存储一些列不固定、修改较少、相对静态的数据,比如用户画像、商品标签、接口数据等,原先大家可能会用Mongo来存储此类数据,现在MySQL也支持使用了。

  1. /*建表SQL*/
  2. CREATE TABLE student (
  3. id BIGINT AUTO_INCREMENT PRIMARY KEY,
  4. info JSON DEFAULT NULL
  5. );
  1. /*插入json对象,包含多组键值对*/
  2. mysql> INSERT student (info) VALUES ('{"sex": "F", "age": 13, "city": "beijing"}');
  3. mysql> INSERT student (info) VALUES ('{"sex": "M", "age": 14, "city": "suzhou"}');
  4. mysql> INSERT student (info) VALUES ('{"sex": "F", "age": 23, "city": "shenzhen"}');
  5. mysql> select * from student;
  6. +----+---------------------------------------------+
  7. | id | info |
  8. +----+---------------------------------------------+
  9. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  10. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  11. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  12. +----+---------------------------------------------+
  1. /*插入JSON数组,一般有如下2种形式,也可以为更加复杂的嵌套JSON*/
  2. mysql> INSERT student (info) VALUES ('[1,2,3,4]');
  3. mysql> INSERT student (info) VALUES ('[{"sex": "M"},{"sex":"F", "city":"nanjing"}]');
  4. mysql> select * from student where id in (4,5);
  5. +----+-------------------------------------------------+
  6. | id | info |
  7. +----+-------------------------------------------------+
  8. | 4 | [1, 2, 3, 4] |
  9. | 5 | [{"sex": "M"}, {"sex": "F", "city": "nanjing"}] |
  10. +----+-------------------------------------------------+

二. JSON数据查询

因为支持了新的JSON类型,MySQL 配套提供了丰富的 JSON 字段处理函数,用于方便地操作 JSON 数据,最常见的就是函数 JSON_EXTRACT,它用来从 JSON 数据中提取所需要的字段内容。

2.1. 提取JSON对象

主要是JSON_EXTRACT(根据键提取值)JSON_UNQUOTE(去除最外侧的双引号),或者-> 和 ->> 表达式。

  1. /*JSON_EXTRACT适用于json对象提取*/
  2. mysql> select * from student;
  3. +----+-------------------------------------------------+
  4. | id | info |
  5. +----+-------------------------------------------------+
  6. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  7. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  8. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  9. | 4 | [1, 2, 3, 4] |
  10. | 5 | [{"sex": "M"}, {"sex": "F", "city": "nanjing"}] |
  11. +----+-------------------------------------------------+
  1. /*JSON数组无法直接用JSON_EXTRACT提取数据*/
  2. mysql> select id,
  3. JSON_UNQUOTE(JSON_EXTRACT(info,'$.sex')) ex,
  4. JSON_UNQUOTE(JSON_EXTRACT(info,'$.age')) age,
  5. JSON_UNQUOTE(JSON_EXTRACT(info,'$.city')) city
  6. from student;
  7. +----+------+------+----------+
  8. | id | ex | age | city |
  9. +----+------+------+----------+
  10. | 1 | F | 13 | beijing |
  11. | 2 | M | 14 | suzhou |
  12. | 3 | F | 23 | shenzhen |
  13. | 4 | NULL | NULL | NULL |
  14. | 5 | NULL | NULL | NULL |
  15. +----+------+------+----------+
  1. /*JSON_UNQUOTE不加则不会取出最外侧的双引号*/
  2. mysql> select id,JSON_EXTRACT(info,'$.sex') sex from student;
  3. +----+------+
  4. | id | sex |
  5. +----+------+
  6. | 1 | "F" |
  7. | 2 | "M" |
  8. | 3 | "F" |
  9. | 4 | NULL |
  10. | 5 | NULL |
  11. +----+------+

MySQL 还提供了-> 和 ->> 表达式,和上述 SQL 效果一样,其中->能得到提取结果但是不去除外面的双引号,与JSON_EXTRACT对应, ->>能得到提取结果且会去除外面的符号,与 JSON_UNQUOTE(JSON_EXTRACT)对应。

  • -> = JSON_EXTRACT

  • ->> = JSON_UNQUOTE(JSON_EXTRACT)

  1. mysql> select * from student;
  2. +----+-------------------------------------------------+
  3. | id | info |
  4. +----+-------------------------------------------------+
  5. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  6. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  7. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  8. | 4 | [1, 2, 3, 4] |
  9. | 5 | [{"sex": "M"}, {"sex": "F", "city": "nanjing"}] |
  10. +----+-------------------------------------------------+
  1. /*->>去除最外侧双引号*/
  2. mysql> select id,info->>'$.sex' sex,info->>'$.age' age,info->>'$.city' city from student;
  3. +----+------+------+----------+
  4. | id | sex | age | city |
  5. +----+------+------+----------+
  6. | 1 | F | 13 | beijing |
  7. | 2 | M | 14 | suzhou |
  8. | 3 | F | 23 | shenzhen |
  9. | 4 | NULL | NULL | NULL |
  10. | 5 | NULL | NULL | NULL |
  11. +----+------+------+----------+
  1. /*->不去除最外层双引号*/
  2. mysql> select id,info->'$.sex' sex,info->'$.age' age,info->'$.city' city from student;
  3. +----+------+------+------------+
  4. | id | sex | age | city |
  5. +----+------+------+------------+
  6. | 1 | "F" | 13 | "beijing" |
  7. | 2 | "M" | 14 | "suzhou" |
  8. | 3 | "F" | 23 | "shenzhen" |
  9. | 4 | NULL | NULL | NULL |
  10. | 5 | NULL | NULL | NULL |
  11. +----+------+------+------------+

2.2. 提取JSON数组

  1. mysql> select * from student;
  2. +----+-------------------------------------------------+
  3. | id | info |
  4. +----+-------------------------------------------------+
  5. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  6. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  7. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  8. | 4 | [1, 2, 3, 4] |
  9. | 5 | [{"sex": "M"}, {"sex": "F", "city": "nanjing"}] |
  10. +----+-------------------------------------------------+
  1. /*使用JSON_EXTRACT提取数组*/
  2. mysql> select id,JSON_EXTRACT(info, '$[0]') first from student;
  3. +----+---------------------------------------------+
  4. | id | first |
  5. +----+---------------------------------------------+
  6. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  7. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  8. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  9. | 4 | 1 |
  10. | 5 | {"sex": "M"} |
  11. +----+---------------------------------------------+
  1. /*使用->>符号提取数组,如果索引位置不存在则返回NULl*/
  2. mysql> select id,info->>'$[0]' first,info->>'$[1]' second from student;
  3. +----+---------------------------------------------+---------------------------------+
  4. | id | first | second |
  5. +----+---------------------------------------------+---------------------------------+
  6. | 1 | {"age": 13, "sex": "F", "city": "beijing"} | NULL |
  7. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} | NULL |
  8. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} | NULL |
  9. | 4 | 1 | 2 |
  10. | 5 | {"sex": "M"} | {"sex": "F", "city": "nanjing"} |
  11. +----+---------------------------------------------+---------------------------------+
  1. /*使用->>符号提取数组中的值*/
  2. mysql> select id,info->>'$[0].sex' first from student;
  3. +----+-------+
  4. | id | first |
  5. +----+-------+
  6. | 1 | F |
  7. | 2 | M |
  8. | 3 | F |
  9. | 4 | NULL |
  10. | 5 | M |
  11. +----+-------+

三. JSON数据过滤

提取JSON后不能用新命名的字段做筛选过滤,需要调用把JSON函数或者符号再写一遍。

3.1. JSON对象过滤

这种方式与原先的非JSON类型条件过滤类似,写法比较简单明了,但是只能用于过滤JSON对象,无法过滤JSON数组。

  1. mysql> select * from student;
  2. +----+-------------------------------------------------+
  3. | id | info |
  4. +----+-------------------------------------------------+
  5. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  6. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  7. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  8. | 4 | [1, 2, 3, 4] |
  9. | 5 | [{"sex": "M"}, {"sex": "F", "city": "nanjing"}] |
  10. +----+-------------------------------------------------+
  1. /*筛选sex是F,age大于14的*/
  2. mysql> select id,info from student WHERE info->>'$.age' > 14 and info->>'$.sex' = 'F';
  3. +----+---------------------------------------------+
  4. | id | info |
  5. +----+---------------------------------------------+
  6. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  7. +----+---------------------------------------------+

3.2. JSON函数过滤(高级)

MySQL8中针对JOSN类型,新增了部分JSON函数用于数据过滤(包括JSON对象和JSON数组):

  • MEMBER OF:匹配某个元素是否存在,返回1表示元素存在,返回0表示元素不存在。

  • JSON_CONTAINS:对JSON数组检查一个元素或者多个元素是否存在,对于JSON对象检查指定KEY是否有某个值。

  • JSON_OVERLAP:比较两个JSON数组是否至少有一个元素一致,如果是返回1,否则返回0,如果是JSON对象,判断是否是有一对key value一致。

以上函数可以在前面加上NOT关键字就可以取反

MEMBER OF

  1. /*MEMBER OF对JSON数组使用*/
  2. mysql> select * from student;
  3. +----+----------------+
  4. | id | info |
  5. +----+----------------+
  6. | 1 | [1, 2, [3, 4]] |
  7. | 2 | [2, 5, 6] |
  8. | 3 | [1, 3] |
  9. +----+----------------+
  1. /*数组中包含3,对嵌套数组不生效*/
  2. mysql> SELECT * FROM student WHERE 3 MEMBER OF(info);
  3. +----+--------+
  4. | id | info |
  5. +----+--------+
  6. | 3 | [1, 3] |
  7. +----+--------+
  1. /*NOT取反,数组中不包含3,对嵌套数组不生效*/
  2. mysql> SELECT * FROM student WHERE not 3 MEMBER OF(info);
  3. +----+----------------+
  4. | id | info |
  5. +----+----------------+
  6. | 1 | [1, 2, [3, 4]] |
  7. | 2 | [2, 5, 6] |
  8. +----+----------------+
  1. /*过滤包含[3,4]子数组,JSON_ARRAY(3,4)会生成一个JSON数组[3,4]*/
  2. mysql> SELECT * FROM student WHERE JSON_ARRAY(3, 4) MEMBER OF(info);
  3. +----+----------------+
  4. | id | info |
  5. +----+----------------+
  6. | 1 | [1, 2, [3, 4]] |
  7. +----+----------------+
  1. /*MEMBER OF对JSON对象使用*/
  2. mysql> SELECT * FROM student;
  3. +----+---------------------------------------------+
  4. | id | info |
  5. +----+---------------------------------------------+
  6. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  7. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  8. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  9. +----+---------------------------------------------+
  1. /*过滤出age包含13的数据,对于JSON对象来说,value相当于只有一个元素的数组,因此相当于是个等值匹配*/
  2. mysql> SELECT * FROM student2 WHERE 13 MEMBER OF(info->'$.age');
  3. +----+--------------------------------------------+
  4. | id | info |
  5. +----+--------------------------------------------+
  6. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  7. +----+--------------------------------------------+

JSON_CONTAINS

  1. mysql> select * from student;
  2. +----+----------------+
  3. | id | info |
  4. +----+----------------+
  5. | 1 | [1, 2, [3, 4]] |
  6. | 2 | [2, 5, 6] |
  7. | 3 | [1, 3] |
  8. +----+----------------+
  1. /*JSON_CONTAINS过滤JSON数组,同时包含2和6*/
  2. mysql> SELECT * FROM student WHERE JSON_CONTAINS(info, '[2, 6]');
  3. +----+-----------+
  4. | id | info |
  5. +----+-----------+
  6. | 2 | [2, 5, 6] |
  7. +----+-----------+
  1. mysql> select * from student;
  2. +----+---------------------------------------------+
  3. | id | info |
  4. +----+---------------------------------------------+
  5. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  6. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  7. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  8. +----+---------------------------------------------+
  1. /*JSON_CONTAINS过滤JSON对象,字符串要在内加一层双引号,数值不需要*/
  2. mysql> SELECT * FROM student WHERE JSON_CONTAINS(info, '"F"', '$.sex');
  3. +----+---------------------------------------------+
  4. | id | info |
  5. +----+---------------------------------------------+
  6. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  7. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  8. +----+---------------------------------------------+
  1. mysql> SELECT * FROM student WHERE JSON_CONTAINS(info, '13', '$.age');
  2. +----+--------------------------------------------+
  3. | id | info |
  4. +----+--------------------------------------------+
  5. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  6. +----+--------------------------------------------+

JSON_OVERLAP
​​​​​​​

  1. mysql> select * from student;
  2. +----+----------------+
  3. | id | info |
  4. +----+----------------+
  5. | 1 | [1, 2, [3, 4]] |
  6. | 2 | [2, 5, 6] |
  7. | 3 | [1, 3] |
  8. +----+----------------+
  1. /*JSON数组中包含3或5元素*/
  2. mysql> SELECT * FROM student WHERE JSON_OVERLAPS(info, '[3, 5]');
  3. +----+-----------+
  4. | id | info |
  5. +----+-----------+
  6. | 2 | [2, 5, 6] |
  7. | 3 | [1, 3] |
  8. +----+-----------+
  1. mysql> select * from student;
  2. +----+---------------------------------------------+
  3. | id | info |
  4. +----+---------------------------------------------+
  5. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  6. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  7. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  8. +----+---------------------------------------------+
  1. /*JSON对象中包含age=13或sex='F'的键值对*/
  2. mysql> select * from student where JSON_OVERLAPS(info,'{"age": 13,"sex": "F"}');
  3. +----+---------------------------------------------+
  4. | id | info |
  5. +----+---------------------------------------------+
  6. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  7. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  8. +----+---------------------------------------------+

四. JSON数据修改

修改数据主要是JSON_SETJSON_INSERTJSON_REPLACE三个方法,同时也支持完整的JSON列更新,但是不建议,因为需要把整个JSON拼成一个字符串,相对复杂,而前三种JSON方法可以只针对某个key做更新,相对简单。

  • JSON_SET:替换现有key的值,插入不存在的key的值。

  • JSON_INSERT:插入不存在的key的值,已经存在的不修改。

  • JSON_REPLACE:只替换已存在的key的值,不存在的不做插入。

删除使用JSON_REMOVE,在JSON对象中指定key删除,或在JSON数组中指定下标删除。

  1. mysql> select * from student where id = 1;
  2. +----+--------------------------------------------+
  3. | id | info |
  4. +----+--------------------------------------------+
  5. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  6. +----+--------------------------------------------+
  7. /*完整的列更新*/
  8. mysql> update student set info='{"age": 14, "sex": "F", "city": "beijing"}' where id = 1;
  9. mysql> select * from student where id = 1;
  10. +----+--------------------------------------------+------+
  11. | id | info | age |
  12. +----+--------------------------------------------+------+
  13. | 1 | {"age": 14, "sex": "F", "city": "beijing"} | 14 |
  14. +----+--------------------------------------------+------+
  15. /*JSON_SET,不存在则插入,有则替换*/
  16. mysql> UPDATE student SET info = JSON_SET(info, "$.city", 'wuxi', "$.height", 123) WHERE id = 1;
  17. mysql> select * from student where id = 1;
  18. +----+--------------------------------------------------------+
  19. | id | info |
  20. +----+--------------------------------------------------------+
  21. | 1 | {"age": 14, "sex": "F", "city": "wuxi", "height": 123} |
  22. +----+--------------------------------------------------------+
  23. /*JSON_INSERT,只会插入不存在的值*/
  24. mysql> select * from student where id = 2;
  25. +----+-------------------------------------------+
  26. | id | info |
  27. +----+-------------------------------------------+
  28. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  29. +----+-------------------------------------------+
  30. mysql> UPDATE student SET info = JSON_INSERT(info, '$.city', 'wuxi', '$.height', 123) WHERE id = 2;
  31. mysql> select * from student where id = 2;
  32. +----+----------------------------------------------------------+
  33. | id | info |
  34. +----+----------------------------------------------------------+
  35. | 2 | {"age": 14, "sex": "M", "city": "suzhou", "height": 123} |
  36. +----+----------------------------------------------------------+
  37. /*JSON_REPLACE,只会替换已有值*/
  38. mysql> select * from student where id = 3;
  39. +----+---------------------------------------------+
  40. | id | info |
  41. +----+---------------------------------------------+
  42. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  43. +----+---------------------------------------------+
  44. mysql> UPDATE student SET info = JSON_REPLACE(info, '$.city', 'wuxi', '$.height', 123) WHERE id = 3;
  45. mysql> select * from student where id = 3;
  46. +----+-----------------------------------------+
  47. | id | info |
  48. +----+-----------------------------------------+
  49. | 3 | {"age": 23, "sex": "F", "city": "wuxi"} |
  50. +----+-----------------------------------------+
  51. /*在JSON对象中指定key删除*/
  52. mysql> select * from student where id = 3;
  53. +----+-----------------------------------------+
  54. | id | info |
  55. +----+-----------------------------------------+
  56. | 3 | {"age": 23, "sex": "F", "city": "wuxi"} |
  57. +----+-----------------------------------------+
  58. mysql> UPDATE student SET info = JSON_REMOVE(info, '$.age') WHERE id = 3;
  59. mysql> select * from student where id = 3;
  60. +----+------------------------------+
  61. | id | info |
  62. +----+------------------------------+
  63. | 3 | {"sex": "F", "city": "wuxi"} |
  64. +----+------------------------------+
  65. /*在JSON数组中指定下标删除*/
  66. mysql> select * from student where id in (4,5);
  67. +----+-------------------------------------------------+
  68. | id | info |
  69. +----+-------------------------------------------------+
  70. | 4 | [1, 2, 3, 4] |
  71. | 5 | [{"sex": "M"}, {"sex": "F", "city": "nanjing"}] |
  72. +----+-------------------------------------------------+
  73. mysql> UPDATE student SET info = JSON_REMOVE(info, '$[1]') WHERE id in (4,5);
  74. mysql> select * from student where id in (4,5);
  75. +----+----------------+
  76. | id | info |
  77. +----+----------------+
  78. | 4 | [1, 3, 4] |
  79. | 5 | [{"sex": "M"}] |
  80. +----+----------------+

五. JSON索引使用

5.1. 虚拟列索引

当 JSON 数据量非常大,用户希望对 JSON 数据进行有效检索时,可以利用 MySQL 的函数索引功能对 JSON 中的某个字段进行索引,具体方式是先创建一个虚拟列,再对虚拟列创建索引

  1. /*原本执行计划走的全表*/
  2. mysql> explain select * from student where info->>"$.age" = 13 ;
  3. +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
  4. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  5. +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
  6. | 1 | SIMPLE | student | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | Using where |
  7. +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
  8. /*创建age虚拟列*/
  9. mysql> ALTER TABLE student ADD COLUMN age INT as (info->>"$.age");
  10. /*创建age索引*/
  11. mysql> create index idx_age on student(age);
  12. /*执行计划走新加的索引*/
  13. mysql> explain select * from student where info->>"$.age" = 13 ;
  14. +----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+-------+
  15. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  16. +----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+-------+
  17. | 1 | SIMPLE | student | NULL | ref | idx_age | idx_age | 5 | const | 1 | 100.00 | NULL |
  18. +----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+-------+

5.2. Multi-Valued Index

从MySQL8.0.17开始,InnoDB支持多值索引(Multi-Valued Index)。多值索引是在存储JSON数组的列上定义的辅助索引,对于MEMBER OF,JSON_CONTAINS,JSON_OVERLAPS 等函数可以利用多值索引进行性能优化。

JSON对象的使用(多值索引是官方针对JSON数组的辅助索引,但是根据实践也可以针对JSON对象使用,但是只适用于member of`函数)

  1. mysql> select * from student;
  2. +----+---------------------------------------------+
  3. | id | info |
  4. +----+---------------------------------------------+
  5. | 1 | {"age": 13, "sex": "F", "city": "beijing"} |
  6. | 2 | {"age": 14, "sex": "M", "city": "suzhou"} |
  7. | 3 | {"age": 23, "sex": "F", "city": "shenzhen"} |
  8. +----+---------------------------------------------+
  9. /*没创建多值索引前走的是全表扫描*/
  10. mysql> explain select * from student where 13 member of(info->'$.age');
  11. +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
  12. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  13. +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
  14. | 1 | SIMPLE | student2 | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using where |
  15. +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
  16. /*创建info->'$.age'的多值索引*/
  17. mysql> alter table student add index idx_age((cast(info->'$.age' as unsigned array)));
  18. /*memberof函数可以走多值索引*/
  19. mysql> explain select * from student where 13 member of(info->'$.age');
  20. +----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
  21. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  22. +----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
  23. | 1 | SIMPLE | student2 | NULL | ref | idx_age | idx_age | 9 | const | 1 | 100.00 | Using where |
  24. +----+-------------+----------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
  25. /*指定JSON对象过滤无法利用多值索引*/
  26. mysql> explain select * from student where info->'$.age' = 13;
  27. +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
  28. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  29. +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
  30. | 1 | SIMPLE | student2 | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using where |
  31. +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
  32. /*JSON_CONTAINS函数无法利用多值索引过滤JSON对象*/
  33. mysql> explain SELECT * FROM student2 where JSON_CONTAINS(info, '13', '$.age');
  34. +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
  35. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  36. +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
  37. | 1 | SIMPLE | student2 | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using where |
  38. +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
  39. /*JSON_OVERLAPS函数也无法利用多值索引过滤JSON对象*/
  40. mysql> explain select * from student where JSON_OVERLAPS(info,'{"age": 13}');
  41. +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
  42. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  43. +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
  44. | 1 | SIMPLE | student | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | Using where |
  45. +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------

JSON数组的使用:三种JSON函数都可以利用多值索引。

  1. mysql> select * from student;
  2. +----+--------------+
  3. | id | info |
  4. +----+--------------+
  5. | 1 | [1, 2, 5, 9] |
  6. | 2 | [2, 5, 6, 8] |
  7. | 3 | [5, 3, 8, 9] |
  8. | 4 | [1, 2, 7, 8] |
  9. +----+--------------+
  10. /*没创建多值索引前走的是全表扫描*/
  11. mysql> EXPLAIN SELECT * FROM student WHERE JSON_CONTAINS(info, '[3]');
  12. +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
  13. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  14. +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
  15. | 1 | SIMPLE | student | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | Using where |
  16. +----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
  17. /*创建info列的多值索引,注意如果这个地方改为idx_info((cast((info->"$") as unsigned array))),则后续所有的函数都要是info->"$",否则走不了索引*/
  18. mysql> ALTER TABLE student ADD INDEX idx_info((cast(info as unsigned array)));
  19. /*JSON_CONTAINS函数可以走新建的多值索引*/
  20. mysql> EXPLAIN SELECT * FROM student WHERE JSON_CONTAINS(info, '[3]');
  21. +----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
  22. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  23. +----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
  24. | 1 | SIMPLE | student | NULL | range | idx_info | idx_info | 9 | NULL | 4 | 100.00 | Using where |
  25. +----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
  26. /*JSON_CONTAINS函数可以走新建的多值索引*/
  27. mysql> explain SELECT * FROM student WHERE JSON_CONTAINS(info, '[3]');
  28. +----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
  29. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  30. +----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
  31. | 1 | SIMPLE | student | NULL | range | idx_info | idx_info | 9 | NULL | 1 | 100.00 | Using where |
  32. +----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
  33. /*MEMBER OF函数可以走新建的多值索引*/
  34. mysql> explain SELECT * FROM student WHERE 3 MEMBER OF(info);
  35. +----+-------------+---------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
  36. | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
  37. +----+-------------+---------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
  38. | 1 | SIMPLE | student | NULL | ref | idx_info | idx_info | 9 | const | 1 | 100.00 | Using where |
  39. +----+-------------+---------+------------+------+---------------+----------+---------+-------+------+----------+-------------+

六. JSON应用场景

这里列举一个MySQL JSON最常见的应用场景,即用户画像(也就是给用户打标签),大家可以作为参考,看下实际业务场景中是否有此类需求。

错误的设计:用逗号作为分隔符存储用户标签。

正确的设计:用JSON存储用户标签。

利用函数MEMBER OF、JSON_CONTAINS、JSON_OVERLAP进行用户画像的搜索。

  1. --查询80后且爱看电影的用户
  2. select * from usertag where json_contains(userTags,'[2,10]');
  1. --查询画像为80后、90后或常看电影的用户
  2. select * from usertag where json_overlaps(userTags,'[2,3,10]');

七. JSON性能测试

7.1. sysbench

我们这里用sysbench压测工具,对原表和JSON表(原表的k,c,pad三个列用一个json格式的info列替代,另外添加k的虚拟列索引)分别进行压测,看下JSON的性能表现。

--服务器:8核64G SSD

--压测工具:sysbench(10张表,单表10w数据,8线程,4个压测场景 )

--原表

  1. CREATE TABLE `sbtest1` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `k` int NOT NULL DEFAULT '0',
  4. `c` char(120) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  5. `pad` char(60) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  6. PRIMARY KEY (`id`),
  7. KEY `k_1` (`k`)
  8. ) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

--json表

  1. CREATE TABLE `sbtest1` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `info` json DEFAULT NULL,
  4. `k` int GENERATED ALWAYS AS (json_unquote(json_extract(`info`,_utf8mb4'$.k'))) VIRTUAL,
  5. PRIMARY KEY (`id`),
  6. KEY `idx_k` (`k`)
  7. ) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

--场景一:point-select 根据主键索引查询JSON

  1. SELECT c FROM sbtest1 WHERE id=?
  2. SELECT info->'$.c' FROM sbtest1_json WHERE id=?

--场景二:select_random_points 根据虚拟列索引查询(json类型用prepare会导致索引失效,所以这里用虚拟列替代)

  1. SELECT id, k, c, pad FROM sbtest1 WHERE k in(?)
  2. SELECT id, info FROM sbtest1_json WHERE k in(?)

--场景三:oltp_update_index 根据主键更新JSON ​​​​​​​

  1. UPDATE sbtest1 SET k=k+1 WHERE id=?
  2. UPDATE sbtest1_json SET info = JSON_SET(info, "$.k", convert(info->'$.k',signed) + 1) WHERE id=?

--场景四:oltp_insert json数据写入

  1. INSERT INTO sbtest1 (id, k, c, pad) VALUES (?,?,?,?)
  2. INSERT INTO sbtest1_json (id, info) VALUES (?,'{"k":?,"c":?,"pad":?}')
场景原表(qps)JSON表(qps)
point-select10097392521
select_random_points7792277331
oltp_update_index13321301
oltp_insert14201400

总结:整体性能测试下来,JSON性能与原表相比无特大出入。

7.2. 大量KV测试

JSON列中包含大量KV的读写性能测试:

--表结构

  1. CREATE TABLE `sbtest1` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `info` json DEFAULT NULL,
  4. `col1` int GENERATED ALWAYS AS (json_unquote(json_extract(`info`,_utf8mb4'$.col1'))) VIRTUAL,
  5. PRIMARY KEY (`id`),
  6. KEY `idx_col1` (`col1`)
  7. ) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

--场景二:select_random_points 根据虚拟列索引查询

select id,info->'$.col1',info->'$.col2',info->'$.col3',info->'$.col4',info->'$.col5' FROM sbtest1 WHERE col1 in (?)

--场景三:oltp_update_index 根据主键更新JSON

UPDATE sbtest1 SET info = JSON_SET(info, "$.col1", convert(info->'$.col1',signed) + 1) WHERE id=?
场景100列200列300列
select_random_points764947551974766
oltp_update_index1013927869

总结:JSON读写性能会随着KV数增加而略微下降。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/158055?site
推荐阅读
相关标签
  

闽ICP备14008679号