MySQL隐蔽BUG:组合条件查询无故返回空集的排查与规避方案

 更新时间:2026年01月05日 09:05:32   作者:数据库干货铺  
在数据库日常运维中,查询结果不符合预期 是高频问题,但多数情况可归因于 SQL 语法、数据异常或索引设计,而本次遇到的案例,却源于 MySQL 的底层 BUG明明数据存在,单一条件查询正常,叠加一个过滤条件后竟返回空集,所以本文为大家介绍了排查与规避方案

引言

在数据库日常运维中,“查询结果不符合预期” 是高频问题,但多数情况可归因于 SQL 语法、数据异常或索引设计。而本次遇到的案例,却源于 MySQL 的底层 BUG—— 明明数据存在,单一条件查询正常,叠加一个过滤条件后竟返回空集,着实令人费解。本文将完整还原问题场景、排查过程,以及最终的解决方案。

1.  问题背景

数据库版本:MySQL8.0.40

假设我们创建了一个名为 product_info 的表,用于存储产品的相关信息。该表包含三个字段:product_id(产品编号)、category_id(类别编号)和 brand_id(品牌编号)。其中,product_id 被设置为主键,并且采用降序排列。

CREATE TABLE product_info(    product_id VARCHAR(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT '产品编号',     category_id  VARCHAR(32)  COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '类别编号',    brand_id  VARCHAR(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '品牌编号',    PRIMARY KEY(`product_id` DESC),    KEY `idx_brand_id`(`brand_id`),    KEY idx_category_id(category_id))DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

以下是创建表的 SQL 语句:随后,我们向表中插入了一些数据:

INSERT INTO product_info VALUES('P001','C01','B02'),('P002','C02','B01'),('P003','C02','B01'),('P004','C01','B02'),('P005','C03','B01'),('P006','C03','B01');

数据插入完成后,我们进行了两次查询操作。第一次查询是筛选出 category_id 为 C02 的记录:

SELECT * FROM product_info WHERE category_id='C02';

这次查询正常返回了两条记录,结果如下:

+------------+-------------+----------+| product_id | category_id | brand_id |+------------+-------------+----------+| P003       | C02         | B01      || P002       | C02         | B01      |+------------+-------------+----------+

然而,当我们进行第二次查询,增加了 brand_id 为 B01 的条件时:

mysql> SELECT * FROM product_info WHERE category_id='C02' AND brand_id='B01';Empty set (0.00 sec)

本应返回上述两条记录,但实际结果却为空集,这显然与预期不符。

2.  问题分析及排查

2.1 字符集和校对规则方面

表和字段都采用了 utf8mb4_general_ci 字符集和校对规则。通常情况下,对于数字和字母组成的字符串比较,这种校对规则不会出现问题。但我们不能排除隐式类型转换或者存在不可见字符的可能性。为了验证这一点,我们可以使用 HEX 函数查看 brand_id 的实际存储值:

SELECT product_id, category_id, brand_id, HEX(brand_id) FROM product_info WHERE category_id='C02';

如果 brand_id 的值确实是 B01,那么 HEX 函数的结果应该是 423031。若结果中出现其他字符,比如尾随空格,可能会导致比较时出现不匹配的情况。但是此案例明显不是。

2.2 索引相关问题

索引选择问题

当执行组合条件查询时,优化器可能会选择不合适的索引。对于 SELECT * FROM product_info WHERE category_id='C02' AND brand_id='B01' 这个查询,优化器可能只选择了 idx_category_id 或 idx_brand_id 其中一个索引,从而无法有效地结合两个条件进行查询。

mysql> SELECT * FROM product_info FORCE INDEX (idx_category_id) WHERE category_id='C02' AND brand_id='B01';+------------+-------------+----------+| product_id | category_id | brand_id |+------------+-------------+----------+| P003       | C02         | B01      || P002       | C02         | B01      |+------------+-------------+----------+2 rows in set (0.00 sec)

mysql> SELECT * FROM product_info FORCE INDEX (idx_brand_id) WHERE category_id='C02' AND brand_id='B01';+------------+-------------+----------+| product_id | category_id | brand_id |+------------+-------------+----------+| P003       | C02         | B01      || P002       | C02         | B01      |+------------+-------------+----------+

可见强制走其中一个索引都能正常

索引合并问题

以上可以看出优化器选择使用索引合并(如 index merge intersect),将 idx_category_id 和 idx_brand_id 的结果合并,但由于主键降序排列等因素,可能会导致两个索引的结果无法正确交集,进而出现查询结果为空的情况。因此我们关闭index_merge_intersection或者index_merge测试一下:

mysql> SET optimizer_switch='index_merge_intersection=off';Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM product_info FORCE INDEX (idx_brand_id) WHERE category_id='C02' AND brand_id='B01';+------------+-------------+----------+| product_id | category_id | brand_id |+------------+-------------+----------+| P003       | C02         | B01      || P002       | C02         | B01      |+------------+-------------+----------+2 rows in set (0.00 sec)

关闭后确实可以了。另外关闭

2.3 主键降序排列的影响

二级索引结构

主键采用降序排列可能会对二级索引的存储结构和扫描方向产生影响。在查询时,可能会因为这种影响导致索引无法正常工作,从而无法正确检索到符合条件的记录。

我们建一张product_info2表,再导入原样的数据,再查询一遍

mysql> CREATE TABLE product_info2(    ->     product_id VARCHAR(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT '产品编号',     ->     category_id  VARCHAR(32)  COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '类别编号',    ->     brand_id  VARCHAR(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '品牌编号',    ->     PRIMARY KEY(`product_id` ),    ->     KEY `idx_brand_id`(`brand_id`),    ->     KEY idx_category_id(category_id)    -> )    -> DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;Query OK, 0 rows affected (0.01 sec)
mysql> insert into product_info2 select * from product_info;Query OK, 6 rows affected (0.01 sec)Records: 6  Duplicates: 0  Warnings: 0
mysql> SET optimizer_switch='index_merge_intersection=off';Query OK, 0 rows affected (0.00 sec)
mysql> SET optimizer_switch='index_merge_intersection=on';Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM product_info WHERE category_id='C02' AND brand_id='B01';Empty set (0.00 sec)
mysql> SELECT * FROM product_info2 WHERE category_id='C02' AND brand_id='B01';+------------+-------------+----------+| product_id | category_id | brand_id |+------------+-------------+----------+| P002       | C02         | B01      || P003       | C02         | B01      |+------------+-------------+----------+2 rows in set (0.00 sec)

通过对比可以发现,修改为非降序索引后确实也正常了。

2.4 MySQL 版本兼容性

不同的 MySQL 版本对降序索引的支持和处理方式可能存在差异。某些旧版本可能存在与降序索引相关的 bug,导致在使用降序主键和二级索引进行查询时出现问题。出现问题的版本是MySQL8.0.40,我们用MySQL8.0.41再看一下,发现新版本已经解决

3.  小结

本次问题的本质是 MySQL 8.0.40 版本中,降序主键与索引合并交集模式的底层逻辑冲突—— 二级索引的存储结构受降序主键影响,导致索引合并时无法正确计算结果交集,最终查询 “丢失” 数据。通过逐层排查,我们定位了核心诱因,并提供了紧急规避与长期优化方案,即:

  • 尽量不要使用降序主键,如需使用降序特性,建议创建二级索引解决
  • 如非必要不要开启index_merge或index_merge_intersection,以免导致性能问题或检索错误问题,如果需要,可以考虑先建组合索引解决
  • 以上案例和数据自身也有关系,只是部分数据会出现此情况,大家如需复现可以用我案例中的数据进行测试

因此,在平时数据库运维中,看似 “匪夷所思” 的异常,往往与版本 BUG、索引策略或表结构设计相关。遇到类似问题时,可按 “验证数据→排查索引→测试版本兼容性” 的思路定位,同时优先选择经过实践验证的表结构与索引设计方案,降低踩坑概率。

以上就是MySQL隐蔽BUG:组合条件查询无故返回空集的排查与规避方案的详细内容,更多关于MySQL BUG组合条件查询无故返回空集的资料请关注脚本之家其它相关文章!

相关文章

  • MySQL中order by排序语句的原理解析

    MySQL中order by排序语句的原理解析

    这篇文章主要介绍了MySQL中order by排序语句的原理,本文结合示例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-12-12
  • SQL ALTER TABLE语句灵活修改表结构和数据类型

    SQL ALTER TABLE语句灵活修改表结构和数据类型

    这篇文章主要介绍了SQL ALTER TABLE语句灵活修改表结构和数据类型,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • 优化MySQL数据库中的查询语句详解

    优化MySQL数据库中的查询语句详解

    这篇文章主要介绍了优化MySQL数据库中的查询语句,非常实用的经验总结,需要的朋友可以参考下
    2014-07-07
  • mysql判断字符串是否存在几种常见方式

    mysql判断字符串是否存在几种常见方式

    写SQL语句我们经常需要判断一个字符串中是否包含另一个字符串,下面这篇文章主要给大家介绍了关于mysql判断字符串是否存在的几种常见方式,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-05-05
  • MySQL统计今日生成create_time的数据量的方法小结

    MySQL统计今日生成create_time的数据量的方法小结

    create_time通常是一个用于表示某个实体或事件创建时间的字段,在数据库设计、日志记录或许多软件系统中常见,它存储的是一个日期或时间戳,记录了数据首次被创建的具体时刻,本文介绍了MySQL统计今日生成create_time的数据量的方法,需要的朋友可以参考下
    2024-08-08
  • 日常收集整理常见的mysql sql技巧

    日常收集整理常见的mysql sql技巧

    本篇内容是小编日常收集整理常见的mysql sql技巧,对大家学习mysql sql技巧相关内容有所帮助,感兴趣的朋友一起学习吧
    2015-12-12
  • Windows server 2008 r2下MySQL5.7.17 winx64安装版配置方法图文教程

    Windows server 2008 r2下MySQL5.7.17 winx64安装版配置方法图文教程

    这篇文章主要为大家详细介绍了Windows server 2008 r2下MySQL5.7.17 winx64安装版配置方法图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • MySQL创建索引/判断索引是否生效的问题

    MySQL创建索引/判断索引是否生效的问题

    这篇文章主要介绍了MySQL创建索引/判断索引是否生效的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • Linux自动备份MySQL数据库脚本代码

    Linux自动备份MySQL数据库脚本代码

    下面这段Linux的Shell脚本用于每日自动备份MySQL数据库,可通过Linux的crontab每天定时执行
    2013-11-11
  • MySQL 去重实例操作详解

    MySQL 去重实例操作详解

    这篇文章主要介绍了MySQL 去重实例操作详情,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-07-07

最新评论