在 MySQL 中,使用 GENERATED COLUMNS
(也称为虚拟列或计算列)和索引来优化包含 JSON 数据的查询是一种有效的方法。GENERATED COLUMNS
允许你基于表中其他列(包括 JSON 列)的值来创建新列,并且这些列可以是虚拟的(不存储数据,只在查询时计算)或存储的(物理存储在表中)。对于需要频繁访问的 JSON 数据片段,将它们作为 GENERATED COLUMNS
存储并索引可以显著提高查询性能。
1、JSON 字段常见的增删改查操作
sql
mysql> CREATE table t_json(id bigint auto_increment primary key,col01 json);
Query OK, 0 rows affected (0.26 sec)
mysql> insert into t_json(col01) values('{ "name":"John", "age":30, "city":"New York"}');
Query OK, 1 row affected (0.03 sec)
mysql> insert into t_json(col01) values('{ "name":"supermao", "age":33, "city":"ShenZhen"}');
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_json;
+----+-----------------------------------------------------+
| id | col01 |
+----+-----------------------------------------------------+
| 3 | {"age": 30, "city": "New York", "name": "John"} |
| 4 | {"age": 33, "city": "ShenZhen", "name": "supermao"} |
+----+-----------------------------------------------------+
2 rows in set (0.00 sec)
mysql> select col01->"$.name" as nameinfo,col01->"$.age" as age from t_json;
+------------+------+
| nameinfo | age |
+------------+------+
| "John" | 30 |
| "supermao" | 33 |
+------------+------+
2 rows in set (0.00 sec)
以上是直接插入 JSON 格式的字符串,也可使用函数,常用的有 JSON_ARRAY() 和 JSON_OBJECT(),前者用于构造 JSON 数组,后者用于构造 JSON 对象
sql
mysql> insert into t_json(col01) select json_array(1, "abc", null, true,curtime());
Query OK, 1 row affected (0.06 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> insert into t_json(col01) select json_object('id', 87, 'name', 'carrot');
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> select col01->"$.name" as nameinfo,col01->"$.age" as age from t_json;
+------------+------+
| nameinfo | age |
+------------+------+
| "John" | 30 |
| "supermao" | 33 |
| NULL | NULL |
| "carrot" | NULL |
+------------+------+
4 rows in set (0.00 sec)
mysql> select * from t_json;
+----+-----------------------------------------------------+
| id | col01 |
+----+-----------------------------------------------------+
| 3 | {"age": 30, "city": "New York", "name": "John"} |
| 4 | {"age": 33, "city": "ShenZhen", "name": "supermao"} |
| 5 | [1, "abc", null, true, "08:43:52.000000"] |
| 6 | {"id": 87, "name": "carrot"} |
+----+-----------------------------------------------------+
4 rows in set (0.00 sec)
对于 JSON 文档,KEY 值不能重复。
- 如果插入的值中存在重复 KEY,在 MySQL 8.0.3 之前,遵循 first duplicate key wins 原则,会保留第一个 KEY,后面的将被丢弃掉。
- 从 MySQL 8.0.3 开始,遵循的是 last duplicate key wins 原则,只会保留最后一个 KEY。
sql
-- MySQL 5.7.36
mysql> select json_object('key1',10,'key2',20,'key1',30);
+--------------------------------------------+
| json_object('key1',10,'key2',20,'key1',30) |
+--------------------------------------------+
| {"key1": 10, "key2": 20} |
+--------------------------------------------+
1 row in set (0.02 sec)
sql
-- MySQL 8.0.28
mysql> select json_object('key1',10,'key2',20,'key1',30);
+--------------------------------------------+
| json_object('key1',10,'key2',20,'key1',30) |
+--------------------------------------------+
| {"key1": 30, "key2": 20} |
+--------------------------------------------+
1 row in set (0.00 sec)
2、使用GENERATED COLUMNS
2.1、创建 GENERATED COLUMNS
首先,你需要为 JSON 列中你经常需要访问的数据路径创建 GENERATED COLUMNS
。这些列应该是 STORED
的,以便它们可以被索引。
sql
-- 语法
ALTER TABLE tablename
ADD COLUMN json_field_name VARCHAR(255) AS (JSON_UNQUOTE(JSON_EXTRACT(json_column, '$.path.to.field'))) STORED;
-- 示例,向虚拟列中插入数据实际上是通过JSON字段插入,然后由虚拟列自动计算
(root@localhost)[superdb]> ALTER TABLE t_json ADD COLUMN nameinfo VARCHAR(64) AS (JSON_UNQUOTE(JSON_EXTRACT(col01,'$.name'))) STORED;
Query OK, 4 rows affected (0.40 sec)
Records: 4 Duplicates: 0 Warnings: 0
在这个例子中,json_column
是包含 JSON 数据的列名,'$.path.to.field'
是你想要提取的 JSON 字段的路径,json_field_name
是新创建的列名,用于存储提取的数据。
查看表结构及查询数据
sql
(root@localhost)[superdb]> desc t_json;
+----------+-------------+------+-----+---------+------------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+------------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| col01 | json | YES | | NULL | |
| nameinfo | varchar(64) | YES | | NULL | STORED GENERATED |
+----------+-------------+------+-----+---------+------------------+
3 rows in set (0.46 sec)
(root@localhost)[superdb]> select * from t_json;
+----+-----------------------------------------------------+----------+
| id | col01 | nameinfo |
+----+-----------------------------------------------------+----------+
| 1 | {"age": 30, "city": "New York", "name": "John"} | John |
| 2 | {"age": 33, "city": "ShenZhen", "name": "supermao"} | supermao |
| 3 | [1, "abc", null, true, "21:36:49.000000"] | NULL |
| 4 | {"id": 87, "name": "carrot"} | carrot |
+----+-----------------------------------------------------+----------+
4 rows in set (0.00 sec)
(root@localhost)[superdb]> explain select * from t_json where nameinfo='supermao';
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t_json | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.03 sec)
执行计划走全表type=ALL
2.2、为 GENERATED COLUMNS 创建索引
一旦你有了 GENERATED COLUMNS
,就可以为它们创建索引了。这将加速基于这些列值的查询。
sql
ALTER TABLE t_json ADD INDEX idx_t_json_nameinfo (nameinfo);
查询虚拟列的索引会被优化器识别和使用
sql
(root@localhost)[superdb]> ALTER TABLE t_json ADD INDEX idx_t_json_nameinfo (nameinfo);
Query OK, 0 rows affected (0.33 sec)
Records: 0 Duplicates: 0 Warnings: 0
(root@localhost)[superdb]> explain select * from t_json where nameinfo='supermao';
+----+-------------+--------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t_json | NULL | ref | idx_t_json_nameinfo | idx_t_json_nameinfo | 259 | const | 1 | 100.00 | NULL |
+----+-------------+--------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.02 sec)
2.3、 使用 GENERATED COLUMNS 进行查询
现在,你可以在查询中直接使用这些 GENERATED COLUMNS
,并且 MySQL 可以利用这些列上的索引来加速查询。
sql
(root@localhost)[superdb]> select * from t_json where nameinfo='supermao';
+----+-----------------------------------------------------+----------+
| id | col01 | nameinfo |
+----+-----------------------------------------------------+----------+
| 2 | {"age": 33, "city": "ShenZhen", "name": "supermao"} | supermao |
+----+-----------------------------------------------------+----------+
1 row in set (0.00 sec)
3、优点
- 性能提升:通过减少 JSON 数据的解析量和使用索引,查询性能可以显著提高。
- 简化查询:查询变得更简单,因为你不需要在 WHERE 子句中使用 JSON 函数来提取数据。
- 数据完整性 :如果 JSON 数据结构发生变化,
GENERATED COLUMNS
的定义也需要相应更新,这有助于保持数据的一致性。
4、注意事项
- 存储开销 :
GENERATED COLUMNS
(特别是 STORED 类型)会增加表的存储需求。 - 更新开销 :当基于它们计算的列(即 JSON 列或其他列)发生更改时,
GENERATED COLUMNS
的值也需要更新,这可能会增加写操作的开销。 - 维护 :如果 JSON 结构发生变化,你需要更新所有相关的
GENERATED COLUMNS
定义。
通过仔细规划和管理,使用 GENERATED COLUMNS
和索引可以是一种强大的策略,用于优化包含 JSON 数据的 MySQL 表的查询性能。