在数据库的日常使用中,我们常常会遇到一些看似匪夷所思的查询问。最近就看到一个因为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 主键降序排列的影响
- 二级索引结构
主键采用降序排列可能会对二级索引的存储结构和扫描方向产生影响。在查询时,可能会因为这种影响导致索引无法正常工作,从而无法正确检索到符合条件的记录。
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)
复制


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

2. mysql8.0新增用户及加密规则修改的那些事
3. 比hive快10倍的大数据查询利器-- presto
5. PostgreSQL主从复制--物理复制
6. MySQL传统点位复制在线转为GTID模式复制




文章转载自数据库干货铺,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。
评论
相关阅读
【专家观点】罗敏:从理论到真实SQL,感受DeepSeek如何做性能优化
墨天轮编辑部
1240次阅读
2025-03-06 16:45:38
【专家有话说第五期】在不同年龄段,DBA应该怎样规划自己的职业发展?
墨天轮编辑部
1220次阅读
2025-03-13 11:40:53
MySQL8.0统计信息总结
闫建(Rock Yan)
455次阅读
2025-03-17 16:04:03
2月“墨力原创作者计划”获奖名单公布
墨天轮编辑部
440次阅读
2025-03-13 14:38:19
Ogg23ai高手必看-MySQL Innodb Cluster跟oracle的亲密接触
曹海峰
437次阅读
2025-03-04 21:56:13
SQL优化 - explain查看SQL执行计划(一)
金同学
378次阅读
2025-03-13 16:04:22
SQLE 4.0 正式版发布,新增 SQL 重写、SQL 性能追踪、语法知识图谱等功能
爱可生开源社区
345次阅读
2025-03-07 10:30:00
MySQL突然崩溃?教你用gdb解剖core文件,快速锁定“元凶”!
szrsu
323次阅读
2025-03-13 00:29:43
MySQL生产实战优化(利用Index skip scan优化性能提升257倍)
chengang
310次阅读
2025-03-17 10:36:40
[MYSQL] xtrabackup备份报错Unable to obtain lock分析
大大刺猬
232次阅读
2025-02-28 16:43:00