MySQL最怕的IN大列表,被百度智能云GaiaDB治好了!查询速度提升60倍!

大家好,今天给大家分享一个百度智能云GaiaDB在MySQL内核优化上的黑科技------如何让包含数十万个值的IN查询,从原来的20秒降到0.3秒!

在生产环境中,通常很多业务场景会使用包含成千上万个取值的 IN 谓词进行数据过滤。然而当列表过大时,MySQL 的 range optimizer 容易因内存限制(由 range_optimizer_max_mem_size 控制)而失效,导致查询退化为全表扫描,严重影响性能。

百度智能云GaiaDB 从 3.2.3.1 版本开始,支持将大 IN 列表自动转换为 IN 子查询,从根本上解决了这一瓶颈。

传统 MySQL 的优化瓶颈

在处理形如:

sql 复制代码
column IN (item1, item2, ... )

这样的 IN 列表时,若 column 有索引,MySQL 会尝试使用 range optimizer 将其转换为多个 OR 条件,并进一步尝试使用索引范围扫描。然而该过程存在两个明显短板:

  • 内存消耗大:优化过程需占用大量内存,一旦超出 range_optimizer_max_mem_size 即退化为全表扫描;
  • 索引选择不准确:当列表长度超过 eq_range_index_dive_limit 时,优化器无法通过 index dive 获取精确的数据分布信息,只能依赖简单的索引统计量,容易导致执行计划劣化。

百度智能云GaiaDB 的解决方案:IN 列表转 IN 子查询

百度智能云GaiaDB 将 IN 列表改写为如下形式:

sql 复制代码
column IN (SELECT * FROM (VALUES ROW(item1), ROW(item2), ...) AS tvc)

该改写将 IN 列表转换为一个由表值构造器组成的非关联子查询,从而绕过 range optimizer 的内存限制,转而采用 semi-join 物化策略执行(由于临时表的数据量一般远小于外部表,因此会走 materialization-scan 模式),其流程如下:

  1. 构造并物化临时表:将 IN 列表中的值构建为临时表;
  2. 索引关联查询:从临时表中取一行数据,使用外表索引进行匹配;
  3. 高效匹配输出:遍历临时表完成全部匹配。

该方法不仅规避了 range optimizer 的内存瓶颈,还充分利用了索引,实现了与 range scan 同等级别甚至更优的查询效率。

适用条件与参数设置

百度智能云GaiaDB 在以下条件下自动启用 IN 谓词转 IN 子查询:

  • 版本要求:GaiaDB 3.2.3.1 及以上;
  • 列表长度:IN 列表中元素数量 ≥ gaia_in_predicate_conversion_threshold
  • 语法位置:IN 谓词位于 WHERE 或 ON 子句顶层,且仅通过 AND 连接;
  • 不支持场景:NOT IN 或无索引字段。

使用方法

通过 gaia_in_predicate_conversion_threshold 参数控制该功能开启。

参数名称

gaia_in_predicate_conversion_threshold

级别 Global, Session

描述 当IN列表中的值的数量达到该阈值时,将IN谓词转换为IN子查询,从而绕过range_optimizer_max_mem_size 对超大IN列表的优化分析限制。设置为 0 表示关闭该功能。取值范围:0~18446744073709551615。默认值为5000。

示例

go 复制代码
CREATE TABLE `lineitem` (
  `L_ORDERKEY` int NOT NULL,
  `L_PARTKEY` int NOT NULL,
  `L_SUPPKEY` int NOT NULL,
  `L_LINENUMBER` int NOT NULL,
  `L_QUANTITY` decimal(15,2) NOT NULL,
  `L_EXTENDEDPRICE` decimal(15,2) NOT NULL,
  `L_DISCOUNT` decimal(15,2) NOT NULL,
  `L_TAX` decimal(15,2) NOT NULL,
  `L_RETURNFLAG` char(1) NOT NULL,
  `L_LINESTATUS` char(1) NOT NULL,
  `L_SHIPDATE` date NOT NULL,
  `L_COMMITDATE` date NOT NULL,
  `L_RECEIPTDATE` date NOT NULL,
  `L_SHIPINSTRUCT` char(25) NOT NULL,
  `L_SHIPMODE` char(10) NOT NULL,
  `L_COMMENT` varchar(44) NOT NULL,
  PRIMARY KEY (`L_ORDERKEY`,`L_LINENUMBER`),
  KEY `LINEITEM_FK2` (`L_PARTKEY`,`L_SUPPKEY`),
  CONSTRAINT `lineitem_ibfk_1` FOREIGN KEY (`L_ORDERKEY`) REFERENCES `orders` (`O_ORDERKEY`),
  CONSTRAINT `lineitem_ibfk_2` FOREIGN KEY (`L_PARTKEY`, `L_SUPPKEY`) REFERENCES `partsupp` (`PS_PARTKEY`, `PS_SUPPKEY`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

SQL语句:

sql 复制代码
EXPLAIN SELECT sum(l_extendedprice) / 7.0 AS avg_yearly FROM lineitem WHERE l_partkey IN (207066596,1483256090,...); # 10w参数

关闭当前特性的执行计划:

sql 复制代码
+----+-------------+----------+------------+------+---------------+------+---------+------+----------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows     | filtered | Extra       |
+----+-------------+----------+------------+------+---------------+------+---------+------+----------+----------+-------------+
|  1 | SIMPLE      | lineitem | NULL       | ALL  | LINEITEM_FK2  | NULL | NULL    | NULL | 56764746 |    50.00 | Using where |
+----+-------------+----------+------------+------+---------------+------+---------+------+----------+----------+-------------+

打开当前特性的执行计划:

sql 复制代码
+----+--------------+-------------+------------+------+---------------+--------------+---------+----------------------+--------+----------+-----------------------+
| id | select_type  | table       | partitions | type | possible_keys | key          | key_len | ref                  | rows   | filtered | Extra                 |
+----+--------------+-------------+------------+------+---------------+--------------+---------+----------------------+--------+----------+-----------------------+
|  1 | PRIMARY      | <subquery2> | NULL       | ALL  | NULL          | NULL         | NULL    | NULL                 |   NULL |   100.00 | NULL                  |
|  1 | PRIMARY      | lineitem    | NULL       | ref  | LINEITEM_FK2  | LINEITEM_FK2 | 4       | <subquery2>.column_0 |     27 |   100.00 | Using index condition |
|  2 | MATERIALIZED | <derived3>  | NULL       | ALL  | NULL          | NULL         | NULL    | NULL                 | 100000 |   100.00 | NULL                  |
|  3 | DERIVED      | NULL        | NULL       | NULL | NULL          | NULL         | NULL    | NULL                 |   NULL |     NULL | No tables used        |
+----+--------------+-------------+------------+------+---------------+--------------+---------+----------------------+--------+----------+-----------------------+

性能测试

测试一:使用sysbench模型测试 1.准备5000w数据:

sql 复制代码
sysbench /usr/share/sysbench/oltp_read_only.lua --tables=1 --report-interval=10 --table-size=50000000  --mysql-user=root --mysql-password=123456 --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-db=sbtest --time=300 --max-requests=0  --threads=200   prepare

2.查询带10w个常量值的语句,k字段上有索引:

sql 复制代码
SELECT COUNT(*) FROM sbtest1 WHERE k IN (207066596,1483256090,...);

3.结果对比:

开启转换耗时 0.42s

关闭转换(不使用range optimizer)耗时 12.61s

性能对比 提升约30倍

测试二:使用TPC-H数据集测试 1.准备TPC-H 10GB标准测试数据集; 2.询带10w个常量值的语句,l_partkey 字段上有索引:

sql 复制代码
SELECT sum(l_extendedprice) / 7.0 AS avg_yearly FROM lineitem WHERE l_partkey IN (207066596,1483256090,...);

3.结果对比: 开启转换耗时 0.29s

关闭转换(不使用range optimizer)耗时 20.01s

性能对比 提升约60倍

结论

百度智能云GaiaDB 通过将大 IN 列表智能转换为 IN 子查询,结合 semi-join 物化策略,有效克服了传统 MySQL 在处理大列表时的内存与优化限制。在实际测试中,查询性能提升显著,尤其适用于高并发、大数据量的在线业务场景。该功能无需业务改造,仅通过参数即可控制,是 百度智能云GaiaDB 在查询优化方面的重要增强。

相关推荐
信奥卷王4 小时前
[GESP202506 五级] 奖品兑换
数据结构·算法
奶茶树4 小时前
【数据结构】二叉搜索树
数据结构·算法
晨曦(zxr_0102)4 小时前
CSP-X 2024 复赛编程题全解(B4104+B4105+B4106+B4107)
数据结构·c++·算法
ai安歌4 小时前
【Rust编程:从新手到大师】 Rust 控制流深度详解
开发语言·算法·rust
Shinom1ya_4 小时前
算法 day 36
算法
·白小白4 小时前
力扣(LeetCode) ——15.三数之和(C++)
c++·算法·leetcode
海琴烟Sunshine4 小时前
leetcode 268. 丢失的数字 python
python·算法·leetcode
CL.LIANG4 小时前
视觉SLAM前置知识:相机模型
数码相机·算法
无限进步_5 小时前
深入理解C语言scanf函数:从基础到高级用法完全指南
c语言·开发语言·c++·后端·算法·visual studio