You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL含JSON字段的复合索引在范围/IN查询中未生效的咨询

关于MySQL JSON字段索引在范围/IN查询中不生效的问题

已创建的索引

create index main_cp_index on catalogue_product(
  product_class_id, is_public,
  (cast(coalesce(data->>'$."need_tags"', 0) as unsigned)) ASC);

等值查询可正常使用索引

当对need_tags进行等值查询时,索引main_cp_index能被正常使用:

mysql> explain SELECT count(*) FROM `catalogue_product` WHERE (product_class_id = 3 AND is_public = True AND cast(COALESCE(data->>'$."need_tags"', 0) as unsigned) = 1)\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: catalogue_product
   partitions: NULL
         type: ref
possible_keys: catalogue_product_product_class_id_0c6c5b54_fk_catalogue,catalogue_product_is_public_1cf798c5,main_cp_index
          key: main_cp_index
      key_len: 14
          ref: const,const,const
         rows: 2740
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

范围/IN/BETWEEN查询无法使用目标索引

IN查询的情况

mysql> explain SELECT count(*) FROM `catalogue_product` WHERE (product_class_id = 3 AND is_public = True AND cast(COALESCE(data->>'$."need_tags"', 0) as unsigned) in (0, 1))\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: catalogue_product
   partitions: NULL
         type: ref
possible_keys: catalogue_product_product_class_id_0c6c5b54_fk_catalogue,catalogue_product_is_public_1cf798c5,main_cp_index
          key: catalogue_product_product_class_id_0c6c5b54_fk_catalogue
      key_len: 5
          ref: const
         rows: 3471
     filtered: 20.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

比较运算符查询的情况

mysql> explain SELECT count(*) FROM `catalogue_product` WHERE (product_class_id = 3 AND is_public = True AND cast(COALESCE(data->>'$."need_tags"', 0) as unsigned) < 2)\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: catalogue_product
   partitions: NULL
         type: ref
possible_keys: catalogue_product_product_class_id_0c6c5b54_fk_catalogue,catalogue_product_is_public_1cf798c5,main_cp_index
          key: catalogue_product_product_class_id_0c6c5b54_fk_catalogue
      key_len: 5
          ref: const
         rows: 3471
     filtered: 33.33
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

BETWEEN查询的情况

mysql> explain SELECT count(*) FROM `catalogue_product` WHERE (product_class_id = 3 AND is_public = True AND cast(COALESCE(data->>'$."need_tags"', 0) as unsigned) between 0 and 1)\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: catalogue_product
   partitions: NULL
         type: ref
possible_keys: catalogue_product_product_class_id_0c6c5b54_fk_catalogue,catalogue_product_is_public_1cf798c5,main_cp_index
          key: catalogue_product_product_class_id_0c6c5b54_fk_catalogue
      key_len: 5
          ref: const
         rows: 3471
     filtered: 11.11
        Extra: Using where

表结构信息

mysql> show create table catalogue_product\G
*************************** 1. row ***************************
       Table: catalogue_product
Create Table: CREATE TABLE `catalogue_product` (
  `id` int NOT NULL AUTO_INCREMENT,
  `structure` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci NOT NULL,
  `upc` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci DEFAULT NULL,
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci NOT NULL,
  `slug` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci NOT NULL,
  `description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci NOT NULL,
  `rating` double DEFAULT NULL,
  `date_created` datetime(6) NOT NULL,
  `date_updated` datetime(6) NOT NULL,
  `is_discountable` tinyint(1) NOT NULL,
  `parent_id` int DEFAULT NULL,
  `product_class_id` int DEFAULT NULL,
  `is_public` tinyint(1) NOT NULL,
  `meta_description` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci,
  `meta_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci DEFAULT NULL,
  `data` json NOT NULL DEFAULT (_utf8mb4'{}'),
  `title_en` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci DEFAULT NULL,
  `title_sl` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci DEFAULT NULL,
  `nutrition` json NOT NULL DEFAULT (_utf8mb4'{}'),
  `brand_id` int DEFAULT NULL,
  `contents` json NOT NULL DEFAULT (_utf8mb4'{}'),
  `allergens` json NOT NULL DEFAULT (_utf8mb4'[]'),
  `description_en` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci,
  `description_sl` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci,
  `country` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_ci NOT NULL,
  `priority` smallint NOT NULL,
  `code` varchar(255) COLLATE utf8mb4_0900_as_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `upc` (`upc`),
  UNIQUE KEY `code` (`code`),
  KEY `catalogue_product_parent_id_9bfd2382_fk_catalogue_product_id` (`parent_id`),
  KEY `catalogue_product_product_class_id_0c6c5b54_fk_catalogue` (`product_class_id`),
  KEY `catalogue_product_slug_c8e2e2b9` (`slug`),
  KEY `catalogue_product_date_updated_d3a1785d` (`date_updated`),
  KEY `catalogue_product_date_created_d66f485a` (`date_created`),
  KEY `catalogue_product_is_public_1cf798c5` (`is_public`),
  KEY `catalogue_product_brand_id_74599134_fk_products_brand_id` (`brand_id`),
  KEY `catalogue_product_priority_983a8f56` (`priority`),
  KEY `need_tags_index` ((cast(json_extract(`data`,_utf8mb4'$."need_tags"') as char(5) charset utf8mb4))),
  KEY `start_index` ((cast(json_extract(`data`,_utf8mb4'$."start"') as char(10) charset utf8mb4))),
  KEY `touristic_index` ((cast(json_extract(`data`,_utf8mb4'$."touristic"') as char(2) charset utf8mb4))),
  KEY `main_cp_index` (`product_class_id`,`is_public`,(cast(coalesce(json_unquote(json_extract(`data`,_utf8mb4'$."need_tags"')),0) as unsigned))),
  CONSTRAINT `catalogue_product_brand_id_74599134_fk_products_brand_id` FOREIGN KEY (`brand_id`) REFERENCES `products_brand` (`id`),
  CONSTRAINT `catalogue_product_parent_id_9bfd2382_fk_catalogue_product_id` FOREIGN KEY (`parent_id`) REFERENCES `catalogue_product` (`id`),
  CONSTRAINT `catalogue_product_product_class_id_0c6c5b54_fk_catalogue` FOREIGN KEY (`product_class_id`) REFERENCES `catalogue_productclass` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18229 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_ci

问题

是否有办法让这类IN/范围查询也能使用main_cp_index?该查询是更大查询的一部分,需保留need_tags相关条件以保证索引前缀顺序。

解决方案

1. 强制指定索引

直接在查询中通过FORCE INDEX指定使用目标索引,绕过优化器的自动选择逻辑:

SELECT count(*) FROM `catalogue_product` 
FORCE INDEX(main_cp_index)
WHERE (product_class_id = 3 AND is_public = True AND cast(COALESCE(data->>'$."need_tags"', 0) as unsigned) in (0, 1));

这种方式简单直接,适合确认目标索引性能更优的场景。

2. 保持查询与索引表达式完全一致

虽然data->>'$."need_tags"'和json_unquote(json_extract(data,_utf8mb4'$."need_tags"'))功能等价,但优化器可能对表达式的文本匹配更敏感。尝试在查询中完全复用索引定义中的表达式:

SELECT count(*) FROM `catalogue_product` 
WHERE (product_class_id = 3 AND is_public = True AND cast(coalesce(json_unquote(json_extract(data,_utf8mb4'$."need_tags"')),0) as unsigned) in (0, 1));

3. 更新表统计信息

如果表的数据分布发生变化,MySQL的统计信息可能过时,导致优化器做出错误的索引选择。执行以下命令更新统计信息:

ANALYZE TABLE catalogue_product;

更新后重新执行EXPLAIN检查索引使用情况。

4. 使用生成列重构索引

将JSON字段中的need_tags提取为物理存储的生成列,再基于生成列创建索引,这种方式对各类查询的兼容性最好:
首先添加生成列:

ALTER TABLE catalogue_product 
ADD COLUMN need_tags_unsigned INT UNSIGNED 
GENERATED ALWAYS AS (cast(coalesce(data->>'$."need_tags"', 0) as unsigned)) STORED;

然后创建新的复合索引:

CREATE INDEX main_cp_index_v2 ON catalogue_product(product_class_id, is_public, need_tags_unsigned);

之后查询直接使用生成列:

SELECT count(*) FROM `catalogue_product` 
WHERE product_class_id = 3 AND is_public = True AND need_tags_unsigned IN (0,1);

生成列是物理存储的,优化器能更清晰地识别索引的适用场景,包括范围、IN、BETWEEN等查询。


内容的提问来源于stack exchange,提问作者aikipooh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 14:57:32