【问题标题】:Why don't MEMBER OF() or JSON_CONTAINS() use the multi-value index?为什么 MEMBER OF() 或 JSON_CONTAINS() 不使用多值索引?
【发布时间】:2020-04-20 02:31:56
【问题描述】:

我有下表,在 JSON 整数数组上设置了多值索引:

CREATE TABLE test (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  catIds JSON NOT NULL,
  PRIMARY KEY (id),
  KEY test_categories ((CAST(catIds AS UNSIGNED ARRAY)))
);

我已插入 200,000 条记录,例如:

INSERT INTO test (catIds) VALUES('[123, 456]');
...

问题是,在有或没有索引的catIds 字段上查询此表不会改变执行速度。我尝试使用MEMBER OF()JSON_CONTAINS() 进行查询,有无索引;速度是一样的。

确实,EXPLAIN 表明这些查询不使用索引:

mysql> EXPLAIN SELECT count(*) FROM test WHERE 51 MEMBER OF (catIds);
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | test  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 201416 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+-------+------+----------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                              |
+-------+------+----------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select count(0) AS `count(*)` from `test`.`test` where <cache>(51) member of (`test`.`test`.`catIds`) |
+-------+------+----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> EXPLAIN SELECT count(*) FROM test WHERE JSON_CONTAINS(catIds, '51');
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | test  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 201416 |   100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
+-------+------+---------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                   |
+-------+------+---------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select count(0) AS `count(*)` from `test`.`test` where json_contains(`test`.`test`.`catIds`,<cache>('51')) |
+-------+------+---------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

为什么catIds 上的索引不用于这些查询?我错过了什么?

【问题讨论】:

    标签: mysql json mysql-8.0 mysql-json


    【解决方案1】:

    您必须为索引定义和查询中的谓词使用 JSON 路径。

    https://dev.mysql.com/doc/refman/8.0/en/create-index.html#create-index-multi-valued 说:

    多值键部分中唯一允许的表达式类型是 JSON 路径。路径不必指向插入索引列的 JSON 文档中的现有元素,但本身必须在语法上有效。

    我对此进行了测试:

    mysql> alter table test add key bk1 ((cast(catIds->'$[*]' as unsigned array)));
    Query OK, 0 rows affected (0.07 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> explain SELECT count(*) FROM test WHERE 903 MEMBER OF (catIds->'$[*]');
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+
    |  1 | SIMPLE      | test  | NULL       | ref  | bk1           | bk1  | 9       | const |    8 |   100.00 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------------+
    

    我毫不怀疑使用此功能会增加WTFs per minute during code reviews

    另外请记住,如果优化器认为索引没有帮助,MySQL 将跳过使用索引。就像表只有几行一样,或者您要搜索的值出现在大多数行中。这并不特定于多值索引,多年来它一直是 MySQL 优化器行为的一部分,具有正常索引。

    这是一个示例:我的表中有 4096 行,但它们都相同。即使我搜索表中出现的值,MySQL 也会检测到它将匹配大多数行(在本例中为所有行)并避免索引。

    mysql> select distinct catIds from test;
    +--------------+
    | catIds       |
    +--------------+
    | [258.0, 7.0] |
    +--------------+
    1 row in set (0.00 sec)
    
    mysql> select count(*) from test;
    +----------+
    | count(*) |
    +----------+
    |     4096 |
    +----------+
    1 row in set (0.01 sec)
    
    mysql> explain SELECT count(*) FROM test WHERE 258 MEMBER OF (catIds);
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | test  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4096 |   100.00 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    

    【讨论】:

    • 谢谢。我可以看到现在使用的索引。索引使查询在查询WHERE 51 MEMBER OF (catIds-&gt;'$[*]') 时返回更快一点(0.19 秒与没有索引的 0.25 秒),但令我惊讶的是查询WHERE 51 MEMBER OF (catIds),虽然仍然不使用索引,但在 0.10 秒内返回,所以两次一样快?在这种情况下,索引如何没有帮助?同样令人惊讶的是,在这种特殊情况下,这个没有索引的查询实际上比规范化数据并为catIdsJOIN 使用单独的表更快(与 JSON 字段上的数组索引大致相当) .
    • 我不知道。 MEMBER OF 是一个全新的功能,所以它可能需要一些成熟。我建议您在 bugs.mysql.com 上提交错误并让他们发表评论。我还建议没有在WHERE 子句中使用 JSON 的好方法。
    • 谢谢。当然,我并不是说这种设计比使用规范化数据库更好,但是引入 JSON 是有原因的;我觉得它可以简化一些额外的表可能看起来过大的设计,和/或模式需要更多的灵活性。无论如何,玩这个闪亮的新功能很令人满意!
    • 好吧,但如果你只是在玩,那就别指望它有效率了。许多游戏形式故意低效
    • 我的意思是玩,我的意思是在一个小项目的某个地方的生产中使用它,看看它是如何进行的。所以性能总是受到追捧!
    【解决方案2】:

    MySQL 中的多值索引如此缓慢的原因有几个: *) 这些总是二级索引,因此与 InnoDB 中的主索引相比会受到惩罚 *) 与常规索引相同,它指向记录,然后包含必须解包才能处理的 JSON。 *) 与常规索引不同,多值索引不能被覆盖,它们总是必须从表中获取行

    所有这些都缩小了多值索引有益的条件。他们的最佳条件是: *) 指标选择性高,越高越好 *) 表中有很多行 *) big json docs - 这不会使索引扫描更快,但由于 innodb 中的行 blob 存储不足,普通扫描会很慢,因此可以使索引扫描更加闪亮

    【讨论】:

      猜你喜欢
      • 2020-04-19
      • 2019-02-11
      • 2014-02-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多