在MySQL(版本8.0.35)中,我经常使用JSON列.通常,我会在JSON属性上创建函数索引以加快查询速度.根据MySQL docs,Functional indexes are implemented as hidden virtual generated columns.但是,它们的行为似乎与联接中生成的列上的索引不同.我将用一个不知何故构建的例子来解释我的观点.

示例:给出了两个表productpurchase.purchase具有引用product的JSON属性$.productUuid.

CREATE TABLE IF NOT EXISTS product (
    id         BINARY(16) NOT NULL,
    payload    JSON       NOT NULL,
    
    CONSTRAINT pk_product PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE IF NOT EXISTS purchase (
    id         BINARY(16) NOT NULL,
    payload    JSON       NOT NULL,
    
    CONSTRAINT pk_product PRIMARY KEY (id),
    INDEX `i_purchase_product` (
        ( CAST(payload->>'$.productUuid' AS CHAR(36)) COLLATE utf8mb4_bin )
    )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO product (id, payload)
VALUES
  ( UUID_TO_BIN(UUID(), TRUE), '{ "name": "random drink" }' ),
  ( UUID_TO_BIN(UUID(), TRUE), '{ "name": "random dish" }' ),
  ( UUID_TO_BIN(UUID(), TRUE), '{ "name": "random tool" }' )
;

INSERT INTO purchase (id, payload)
SELECT
  UUID_TO_BIN(UUID(), TRUE), JSON_SET(payload, '$.productUuid', BIN_TO_UUID(id))
FROM product
;

对于以下查询

SELECT * FROM product a INNER JOIN purchase b ON BIN_TO_UUID(a.id) = b.payload->>'$.productUuid';

MySQL生成以下计划:

+----+-------------+-------+---------------+------+--------------------------------------------+
| id | select_type | table | possible_keys | key  | Extra                                      |
+----+-------------+-------+---------------+------+--------------------------------------------+
|  1 | SIMPLE      | a     | NULL          | NULL | NULL                                       |
|  1 | SIMPLE      | b     | NULL          | NULL | Using where; Using join buffer (hash join) |
+----+-------------+-------+---------------+------+--------------------------------------------+

该计划显示,甚至没有考虑功能指数.如果我创建的表包含一个生成的列和一个常规索引,则情况会有所不同.

CREATE TABLE IF NOT EXISTS purchase (
    id           BINARY(16)  NOT NULL,
    payload      JSON        NOT NULL,
    product_uuid VARCHAR(36) GENERATED ALWAYS AS (payload->>'$.productUuid') STORED NOT NULL,
    
    CONSTRAINT pk_purchase PRIMARY KEY (id),
    INDEX `i_purchase_product` (product_uuid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

现在,MySQL生成的计划显示该索引已被考虑:

+----+-------------+-------+--------------------+--------------------+-----------------------+
| id | select_type | table | possible_keys      | key                | Extra                 |
+----+-------------+-------+--------------------+--------------------+-----------------------+
|  1 | SIMPLE      | a     | NULL               | NULL               | NULL                  |
|  1 | SIMPLE      | b     | i_purchase_product | i_purchase_product | Using index condition |
+----+-------------+-------+--------------------+--------------------+-----------------------+

对于这种行为,有没有什么解释,最好是有记录的?

推荐答案

这是一个已知的错误:https://bugs.mysql.com/bug.php?id=98937

它甚至不是关于JSON的使用.任何函数索引都不能在JOIN中使用,即使相同的索引可以在另一个条件中使用.

我将您的示例修改为使用任意字符串函数,而不是使用JSON函数:

CREATE TABLE IF NOT EXISTS product (
    id         BINARY(16) NOT NULL,
    abc CHAR(3) NOT NULL,
    CONSTRAINT pk_product PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE IF NOT EXISTS purchase (
    id         BINARY(16) NOT NULL,
    abc CHAR(3) NOT NULL, 
    CONSTRAINT pk_product PRIMARY KEY (id),
    INDEX `i_abc` ((REVERSE(abc)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO product (id, abc)
VALUES
  ( UUID_TO_BIN(UUID(), TRUE), 'abc' ),
  ( UUID_TO_BIN(UUID(), TRUE), 'abc' ),
  ( UUID_TO_BIN(UUID(), TRUE), 'abc' );

INSERT INTO purchase (id, abc)
SELECT
  UUID_TO_BIN(UUID(), TRUE),  REVERSE(abc)
FROM product;

使用函数索引的表达式进行搜索按预期工作,它利用索引:

EXPLAIN 
SELECT * FROM purchase b WHERE REVERSE(b.abc) = 'abc';

+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+

| id | select_type | table | partitions | type | possible_keys | key   | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | b     | NULL       | ref  | i_abc         | i_abc | 15      | const |    3 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+

但是使用函数索引的表达式连接并不使用索引:

EXPLAIN
SELECT * FROM product a
INNER JOIN purchase b
ON REVERSE(b.abc) = a.abc;

+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | NULL                                       |
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------------------+

该漏洞于2020年3月报告(在我们 compose 本文时已经超过三年).唯一的解决办法是他们同意"提交一个文档错误来澄清当前的限制."但后来他们链接到一个内部错误(我看不到).

到目前为止,我还没有发现他们记录了这种限制.

Mysql相关问答推荐

SQL - Select 复合主键,条件为其中一个主键

无法删除或更新父行:外键约束无法删除表

MySQL:统计单词在单元格中出现的次数,并将数字放在bra中单词的旁边

为什么MySQL不考虑在联接中使用(JSON)函数索引,而考虑在生成的列上使用索引?

约会时的意外行为

在MySQL和JavaFX中保存和检索图像文件

如何使用GROUP BY调优简单SQL查询

在MySQL CLI中,是否有自动完成过程的方法?

如何合并有多行的json列

在两个日期之间生成每个月的1行数据.

MySQL 关于 JSON 数组和子查询的问题

使用mysql查询获取不关注user1的user3

仅计算 DATEDIFF (MySQL) 中的工作日

如何在 MySQL 中查找重复值和更新值

MySQL - 如何查询涉及前面计算值的表结果

具有别名主机的 ssh 反向 mysql tunel

如何过滤表格,以便它显示最新的数据?

SQL 中的双杠 (||) 是什么意思?

mysql 按日期 Select 总和组

字符ي和ی以及波斯语的区别 - Mysql