MySQL 5.7 - JSON索引 - 生成的具有非标量值的列

时间:2017-07-12 14:23:46

标签: mysql json mysql-5.7

我一直在使用MySQL 5.7中的JSON支持。我有一些关于生成列的问题,以便进行索引。 https://dev.mysql.com/doc/refman/5.7/en/create-table.html#create-table-secondary-indexes-virtual-columns

具体来说,请参阅以下一行:

  

无法对JSON列编制索引。您可以通过在生成的列上创建索引来解决此限制,该列从JSON列中提取 标量 值。

这对我来说似乎是一个很大的限制。我看到的每个地方,人们建议使用生成的列。但是这种解决方法适用于非常有限的一组用例。或者,我理解错误。

设置阶段

让我解释一下我的用例。假设您有一个名为standards的表。它具有以下结构:

CREATE TABLE `standards` (
  `id` int(11) NOT NULL,
  `name` varchar(100) NOT NULL,
  `sections` json DEFAULT NULL,
  `subjects` json DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

sections列包含一个JS对象数组:

[
  {
    "id": 90491,
    "name": "A",
  },
  {
    "id": 90494,
    "name": "B",
  }
]

subjects列包含嵌套的JS对象:

{
  "576845": {
    "id": 576845,
    "name": "Computer Education"
  },
  "576848": {
    "id": 576848,
    "name": "English Language"
  },
  "576854": {
    "id": 576854,
    "name": "Environmental Science"
  },
  "576860": {
    "id": 576860,
    "name": "Mathematics"
  }
}

示例查询

查询1

要查找Standard section ID 90494的{​​{1}}记录,查询将为:

SELECT * from standards WHERE JSON_CONTAINS( sections->>'$[*].id', '90494' );

查询2

要查找Standard subject ID 576854的{​​{1}}条记录,查询将为:

SELECT * from standards WHERE JSON_CONTAINS_PATH( subjects, 'one', '$."576854"');

OR

SELECT * from standards WHERE JSON_CONTAINS( subjects->>'$.*.id', '576854' );

问题

现在,以上所有工作。问题是查询执行全表扫描。

考虑上面的查询1,如何使用包含所有section IDs标量数据生成虚拟列?

每条Standard条记录都有多个sections,其中包含多个ID。所以,我不能只创建一个整数虚拟列来存储单个值。它必须是一个部分ID数组,我们需要通过它来搜索。

所以,我生成的列将如下所示:

ALTER TABLE standards
ADD section_ids json GENERATED ALWAYS AS (sections->>'$[*].id') VIRTUAL NOT NULL;

生成的列现在只存储部分ID数组。但我无法在生成的列上添加索引,因为它又是一个JSON列。

问题 - 如何利用索引?

因此,问题归结为此 - 对于上面显示的查询,如何避免全表扫描?

任何建议都将不胜感激。

2 个答案:

答案 0 :(得分:2)

我不会说MySQL 5.7是不可能的,因为它存在笨拙的变通办法和局限性,但我不会介绍该版本,因为它要困难得多,而且局限性在很多情况下,如果可以将大量项目添加到数组中,则可以达到目标。

但是,从MySQL 8.0.17开始,它现在支持multi-valued indexes

ALTER TABLE standards
  ADD INDEX section_ids ( (CAST(sections->'$[*].id' AS UNSIGNED ARRAY)) ),
  ADD INDEX subject_ids ( (CAST(subjects->'$.*.id'AS UNSIGNED ARRAY)) );

**请注意,$.*将具有所有对象属性,并返回每个格式化为数组的查询值(.id)。

EXPLAIN SELECT * from standards WHERE JSON_CONTAINS( sections->'$[*].id', '90494' );
EXPLAIN SELECT * from standards WHERE JSON_CONTAINS( subjects->'$.*.id', 576854 );

您将看到索引用于那些查询。

答案 1 :(得分:0)

在较旧的版本中,我将通过手动创建一个单独的索引表并使用触发器使其保持最新状态来解决此问题。