【问题标题】:MySQL: index json arrays of variable length?MySQL:索引可变长度的json数组?
【发布时间】:2016-05-13 21:28:42
【问题描述】:

我想创建一个json 类型的tags 列:

例如,

id  |  tags
=========================================
1   |  '["tag1", "tag2", "tag3"]'
2   |  '["tag1", "tag3", "tag5", "tag7"]'
3   |  '["tag2", "tag5"]'

我想索引数组中的每个tag,而不知道数组的长度(可变长度)。

那么,如果我查询包含 tag2 的行,它应该返回第 1、3 行。

https://dev.mysql.com/doc/refman/5.7/en/json.html

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

通过“提取标量值”,这是否意味着我必须单独提取和索引数组中的每个项目(意味着我必须知道数组的最大长度才能全部索引)?如何索引可变长度数组?

【问题讨论】:

  • 不,标量只是一个简单的值(相对于复杂的数据结构)。
  • 如果你想对mysql中的数据做一些事情,那么不要将它存储为json。将其存储为规范化数据,并在需要时将其作为 json 输出。
  • Strawberry 的评论突出了一个重点。比如'["tag2", "tag5"]''["tag5", "tag2"]'是不是同一条数据?
  • 假设我查询tag2,那么它应该匹配任何包含tag2 的数组,无论它在数组中的什么位置。

标签: mysql json indexing


【解决方案1】:

在 MySQL 中用 JSON 索引数组是不切实际的。

您可以使用生成的列将数组的每个元素提取到单独的标量列中,并对每个生成的列进行索引。但是您需要多少这些列?您如何知道哪一列包含您要搜索的值?

您可以使用生成的列,如@bostaf 的回答所示,提取多个数组值并制作一个逗号分隔的字符串。您不能使用普通索引在此字符串中搜索可能出现在中间的单词。也许您可以使用全文索引,但这仅适用于数组元素都是单个单词的情况。

在 2018 年 4 月,我做了一个关于在 MySQL 中使用 JSON 的这种弱点的演讲:How to Use JSON in MySQL Wrong

多值属性的更好解决方案是以数据库规范化所禁止的方式将它们存储在依赖表中。然后这些值会出现在多行中的单列中,您可以以更直接的方式对其进行索引。


你的评论:

我想出了一个在 JSON 数组上强制唯一性的解决方案,但这取决于数组中的元素保持排序顺序。

mysql> create table mytags ( tags json );
mysql> insert into mytags values ('["tag1", "tag3", "tag5", "tag7"]');

JSON_UNQUOTE() 函数将 JSON 作为字符串返回。

mysql> select json_unquote(tags) from mytags;
+----------------------------------+
| json_unquote(tags)               |
+----------------------------------+
| ["tag1", "tag3", "tag5", "tag7"] |
+----------------------------------+

现在我们知道如何根据它生成一个生成列,然后在生成列上创建一个 UNIQUE KEY 索引。这适用于 MySQL 5.7 及更高版本。

mysql> alter table mytags 
  add column j varchar(768) as (json_unquote(tags)), 
  add unique index (j);

现在尝试在 JSON 列中插入相同的值数组失败:

mysql> insert into mytags (tags) values ('["tag1", "tag3", "tag5", "tag7"]');
ERROR 1062 (23000): Duplicate entry '["tag1", "tag3", "tag5", "tag7"]' for key 'j'

不幸的是,没有好的方法可以确保 JSON 数组已排序。请参阅Sorted json array field 因此,您可以自行设计应用程序代码,使其始终在插入或更新之前对 JSON 数组中的值进行预排序。

【讨论】:

  • 对于答案的最后一段,我们如何在各个行(存储属性)中应用唯一约束。例如:有一个词表(猫、狗、老鼠)。并且有一个短语表存储这些单词的唯一组合,例如:(cat dog, mouse dog)。为了确保一个短语(单词的组合)是唯一的,我们可以将这个 FK(单词)的组合存储在一个 json 中,但是我们不能索引这个 json,以防我们想将短语连接到关键字。我们有解决这种问题的方法吗?
【解决方案2】:

现在可以使用 MySQL 8.0.17+

类似的东西(未测试)

CREATE TABLE posts (
    id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    tags JSON,
    INDEX tags( (CAST(tags AS VARCHAR(32) ARRAY)) )
    );

这样使用:

SELECT * FROM posts 
         WHERE JSON_CONTAINS(tags, CAST('[tag1, tag2]' AS JSON));

更多细节和示例在这里: https://dev.mysql.com/doc/refman/8.0/en/json.html

【讨论】:

  • 这太棒了!请注意,这仅适用于 MySQL 8.0.17+(不幸的是,我们使用 AWS RDS,当然目前支持 8.0.16)。 “从 MySQL 8.0.17 开始,key_part 规范的 expr 可以采用 (CAST json_path AS type ARRAY) 形式在 JSON 列上创建多值索引。”来自:dev.mysql.com/doc/refman/8.0/en/create-index.html
  • 嗨,是否可以在字符串数组上创建多值索引。我尝试了类似: CREATE INDEX products ON components ( (CAST(data->'$.products' AS VARCHAR(255) ARRAY)) ) 但它给了我错误。有没有办法做到这一点?
  • 是的,也可以为这类字段创建索引。在这里查看dev.mysql.com/doc/refman/8.0/en/…
  • 你能分享一些例子吗?我有带有“产品”字段的json,其中包含可变长度的字符串数组:[“aa”,“bbb”,“cccc”...]
  • 这应该有效:在 YOURTABLE ((CAST(YOURJSONFIELD->'$.products' AS UNSIGNED ARRAY)) 上创建索引产品;
【解决方案3】:

通过“提取标量值”,这是否意味着我必须分别提取和索引数组中的每个项目 [...]?

您可以提取任意数量的项目。它们将存储为标量(例如字符串),而不是复合值(JSON 就是这样)。

CREATE TABLE mytags (
    id INT NOT NULL AUTO_INCREMENT,
    tags JSON,
    PRIMARY KEY (id)
);

INSERT INTO mytags (tags) VALUES
    ('["tag1", "tag2", "tag3"]'),
    ('["tag1", "tag3", "tag5", "tag7"]'),
    ('["tag2", "tag5"]');

SELECT * FROM mytags;

+----+----------------------------------+
| id | tags                             |
+----+----------------------------------+
|  1 | ["tag1", "tag2", "tag3"]         |
|  2 | ["tag1", "tag3", "tag5", "tag7"] |
|  3 | ["tag2", "tag5"]                 |
+----+----------------------------------+

让我们创建一个仅包含一项的索引(来自 JSON 对象的第一个值):

ALTER TABLE mytags
    ADD COLUMN tags_scalar VARCHAR(255) GENERATED ALWAYS AS (json_extract(tags, '$[0]')),
    ADD INDEX tags_index (tags_scalar);

SELECT * FROM mytags;

+----+----------------------------------+-------------+
| id | tags                             | tags_scalar |
+----+----------------------------------+-------------+
|  1 | ["tag1", "tag2", "tag3"]         | "tag1"      |
|  2 | ["tag1", "tag3", "tag5", "tag7"] | "tag1"      |
|  3 | ["tag2", "tag5"]                 | "tag2"      |
+----+----------------------------------+-------------+

现在您在 VARCHAR 列 tags_scalar 上有一个索引。该值包含引号,也可以跳过:

ALTER TABLE mytags DROP COLUMN tags_scalar, DROP INDEX tags_index;

ALTER TABLE mytags
    ADD COLUMN tags_scalar VARCHAR(255) GENERATED ALWAYS AS (json_unquote(json_extract(tags, '$[0]'))),
    ADD INDEX tags_index (tags_scalar);

SELECT * FROM mytags;

+----+----------------------------------+-------------+
| id | tags                             | tags_scalar |
+----+----------------------------------+-------------+
|  1 | ["tag1", "tag2", "tag3"]         | tag1        |
|  2 | ["tag1", "tag3", "tag5", "tag7"] | tag1        |
|  3 | ["tag2", "tag5"]                 | tag2        |
+----+----------------------------------+-------------+

正如您已经想象的那样,生成的列可以包含更多来自 JSON 的项目:

ALTER TABLE mytags DROP COLUMN tags_scalar, DROP INDEX tags_index;

ALTER TABLE mytags
    ADD COLUMN tags_scalar VARCHAR(255) GENERATED ALWAYS AS (json_extract(tags, '$[0]', '$[1]', '$[2]')),
    ADD INDEX tags_index (tags_scalar);

SELECT * from mytags;

+----+----------------------------------+--------------------------+
| id | tags                             | tags_scalar              |
+----+----------------------------------+--------------------------+
|  1 | ["tag1", "tag2", "tag3"]         | ["tag1", "tag2", "tag3"] |
|  2 | ["tag1", "tag3", "tag5", "tag7"] | ["tag1", "tag3", "tag5"] |
|  3 | ["tag2", "tag5"]                 | ["tag2", "tag5"]         |
+----+----------------------------------+--------------------------+

或使用any other valid expression从JSON结构中自动生成一个字符串,以获得可以轻松索引和搜索的内容,例如“tag1tag3tag5tag7”。

[...](意思是我必须知道数组的最大长度才能全部索引)?

如上所述,您无需知道 - 可以使用任何有效的表达式跳过 NULL 值。但当然知道总是更好。
现在是架构决策:JSON 数据类型最适合实现目标吗?解决这个特殊问题? JSON在这里是正确的工具吗?它会加快搜索速度吗?

如何索引可变长度数组?

如果你坚持,请转换字符串:

ALTER TABLE mytags DROP COLUMN tags_scalar, DROP INDEX tags_index;

ALTER TABLE mytags
    ADD COLUMN tags_scalar VARCHAR(255) GENERATED ALWAYS AS (replace(replace(replace(cast(tags as char), '"', ''), '[', ''), ']', '')),
    ADD INDEX tags_index (tags_scalar);

SELECT * from mytags;

+----+----------------------------------+------------------------+
| id | tags                             | tags_scalar            |
+----+----------------------------------+------------------------+
|  1 | ["tag1", "tag2", "tag3"]         | tag1, tag2, tag3       |
|  2 | ["tag1", "tag3", "tag5", "tag7"] | tag1, tag3, tag5, tag7 |
|  3 | ["tag2", "tag5"]                 | tag2, tag5             |
+----+----------------------------------+------------------------+

这样或那样你最终会得到一个 VARCHAR 或 TEXT 列,在其中应用最适用的索引结构 (some options)。

进一步阅读:

【讨论】:

    猜你喜欢
    • 2020-07-26
    • 2013-02-15
    • 2020-10-26
    • 2020-11-20
    • 2020-11-28
    • 2014-07-27
    • 2011-07-22
    • 2014-03-22
    相关资源
    最近更新 更多