MySQL 中 IN 到底走不走索引?

MySQL 中 IN 到底走不走索引?

一、问题起源

这几乎是每个 MySQL 开发者都会遇到的问题:

-- 这个查询会走索引吗?
SELECT * FROM users WHERE id IN (1, 2, 3);
-- 和这个比呢?
SELECT * FROM users WHERE id = 1 OR id = 2 OR id = 3;
-- IN 列表多大时会失效?
SELECT * FROM users WHERE id IN (1, 2, 3, ..., 1000);

网上流传的说法:

二、核心结论(先看答案) IN 查询走索引的条件

因素

影响

说明

索引类型

主键索引 > 唯一索引 > 普通索引

选择性

IN 列表占全表比例越小,越可能走索引

IN 列表大小

不是绝对值,而是相对于表大小的比例

数据分布

数据是否均匀分布

MySQL 版本

优化器算法在不断改进

统计信息

准确的统计信息至关重要

经验阈值(MySQL 8.0)

注意:这些不是硬性规则mysql fetch into,优化器会综合判断!

三、实测验证(真实数据)3.1 测试环境准备

-- 创建测试表
CREATE TABLE test_in_index (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    status TINYINT DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_id (user_id),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入 100 万条测试数据
DELIMITER $
CREATE PROCEDURE insert_test_data()
BEGIN
    DECLARE i INT DEFAULT 1;
    WHILE i <= 1000000 DO
        INSERT INTO test_in_index (user_id, status) 
        VALUES (FLOOR(1 + RAND() * 10000), FLOOR(1 + RAND() * 5));
        SET i = i + 1;
    END WHILE;
END$
DELIMITER ;
CALL insert_test_data();
-- 更新统计信息
ANALYZE TABLE test_in_index;

表统计信息:

SELECT 
    TABLE_ROWS,
    AVG_ROW_LENGTH,
    DATA_LENGTH,
    INDEX_LENGTH
FROM information_schema.TABLES 
WHERE TABLE_NAME = 'test_in_index';

结果:

+------------+----------------+-------------+--------------+
| TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH | INDEX_LENGTH |
+------------+----------------+-------------+--------------+
| 1000000    | 45             | 45088768    | 32768000     |
+------------+----------------+-------------+--------------+

3.2 测试场景 1:主键索引 + 少量 IN 值

EXPLAIN SELECT * FROM test_in_index WHERE id IN (1, 2, 3);

执行计划:

+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table         | type  | possible_keys | key     | key_len| ref  | rows | Extra |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | test_in_index | range | PRIMARY       | PRIMARY | 4      | NULL |    3 | Using where |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+

分析:

结论:主键索引 + 少量 IN 值 → 走索引

3.3 测试场景 2:主键索引 + 大量 IN 值

-- 生成 1000 个 ID 的 IN 列表
SET @ids = (SELECT GROUP_CONCAT(id) FROM (
    SELECT id FROM test_in_index ORDER BY RAND() LIMIT 1000
) t);
SET @sql = CONCAT('EXPLAIN SELECT * FROM test_in_index WHERE id IN (', @ids, ')');
PREPARE stmt FROM @sql;
EXECUTE stmt;

执行计划:

+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table         | type  | possible_keys | key     | key_len| ref  | rows | Extra |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | test_in_index | range | PRIMARY       | PRIMARY | 4      | NULL | 1000 | Using where |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+

分析:

继续测试 10000 个 IN 值(占表 1%):

-- 10000 个 ID(占 1%)
SET @ids = (SELECT GROUP_CONCAT(id) FROM (
    SELECT id FROM test_in_index ORDER BY RAND() LIMIT 10000
) t);
SET @sql = CONCAT('EXPLAIN SELECT * FROM test_in_index WHERE id IN (', @ids, ')');
PREPARE stmt FROM @sql;
EXECUTE stmt;

执行计划:

+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table         | type  | possible_keys | key     | key_len| ref  | rows | Extra |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | test_in_index | range | PRIMARY       | PRIMARY | 4      | NULL | 10000| Using where |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+

分析:

测试 30 万个 IN 值(占表 30%):

-- 300000 个 ID(占 30%)
SET @ids = (SELECT GROUP_CONCAT(id) FROM (
    SELECT id FROM test_in_index ORDER BY RAND() LIMIT 300000
) t);
SET @sql = CONCAT('EXPLAIN SELECT * FROM test_in_index WHERE id IN (', @ids, ')');
PREPARE stmt FROM @sql;
EXECUTE stmt;

执行计划:

+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table         | type  | possible_keys | key     | key_len| ref  | rows | Extra |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | test_in_index | ALL   | PRIMARY       | NULL    | NULL  | NULL | 1000000 | Using where |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+

分析:

mysql fetch into

结论:主键索引 + IN 列表超过 30% → 可能全表扫描 ️

3.4 测试场景 3:普通索引 + IN 查询

-- 测试 user_id 索引(10000 个不同值)
EXPLAIN SELECT * FROM test_in_index WHERE user_id IN (100, 200, 300);

执行计划:

+----+-------------+---------------+-------+---------------+------------+---------+------+------+-------+
| id | select_type | table         | type  | possible_keys | key        | key_len | ref  | rows | Extra |
+----+-------------+---------------+-------+---------------+------------+---------+------+------+-------+
|  1 | SIMPLE      | test_in_index | range | idx_user_id   | idx_user_id| 4       | NULL |  300 | Using where |
+----+-------------+---------------+-------+---------------+------------+---------+------+------+-------+

分析:

测试 100 个 IN 值(占 总数 1%):

-- 100 个 user_id(占 1%)
SET @user_ids = (SELECT GROUP_CONCAT(user_id) FROM (
    SELECT DISTINCT user_id FROM test_in_index ORDER BY RAND() LIMIT 100
) t);
SET @sql = CONCAT('EXPLAIN SELECT * FROM test_in_index WHERE user_id IN (', @user_ids, ')');
PREPARE stmt FROM @sql;
EXECUTE stmt;

执行计划:

+----+-------------+---------------+-------+---------------+------------+---------+------+------+-------+
| id | select_type | table         | type  | possible_keys | key        | key_len | ref  | rows | Extra |
+----+-------------+---------------+-------+---------------+------------+---------+------+------+-------+
|  1 | SIMPLE      | test_in_index | range | idx_user_id   | idx_user_id| 4       | NULL | 10000| Using where |
+----+-------------+---------------+-------+---------------+------------+---------+------+------+-------+

分析:

测试 2000 个 IN 值(占 总数 20%):

-- 2000 个 user_id(占 20%)
SET @user_ids = (SELECT GROUP_CONCAT(user_id) FROM (
    SELECT DISTINCT user_id FROM test_in_index ORDER BY RAND() LIMIT 2000
) t);
SET @sql = CONCAT('EXPLAIN SELECT * FROM test_in_index WHERE user_id IN (', @user_ids, ')');
PREPARE stmt FROM @sql;
EXECUTE stmt;

执行计划:

+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table         | type  | possible_keys | key     | key_len | ref  | rows | Extra |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | test_in_index | ALL   | idx_user_id   | NULL    | NULL  | NULL | 1000000 | Using where |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+

分析:

结论:普通索引 + IN 列表超过 20% → 可能全表扫描 ️

3.5 测试场景 4:低选择性索引

-- status 字段只有 5 个不同值(选择性很低)
EXPLAIN SELECT * FROM test_in_index WHERE status IN (1, 2);

执行计划:

+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table         | type  | possible_keys | key     | key_len | ref  | rows | Extra |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+
|  1 | SIMPLE      | test_in_index | ALL   | idx_status    | NULL    | NULL  | NULL | 1000000 | Using where |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+-------+

分析:

结论:低选择性索引 → 即使 IN 值很少也可能不走索引

3.6 性能对比测试

测试 1:IN vs OR

-- IN 查询
SELECT * FROM test_in_index WHERE id IN (1, 2, 3, 4, 5);
-- OR 查询
SELECT * FROM test_in_index WHERE id = 1 OR id = 2 OR id = 3 OR id = 4 OR id = 5;

执行时间对比(5 次平均):

结论:IN 和 OR 性能基本相当mysql fetch intoMySQL 中 IN 到底走不走索引?,优化器会做相同优化

测试 2:IN vs

-- IN 查询
SELECT * FROM test_in_index 
WHERE user_id IN (SELECT user_id FROM users WHERE status = 1);
-- EXISTS 查询
SELECT * FROM test_in_index t
WHERE EXISTS (SELECT 1 FROM users u WHERE u.user_id = t.user_id AND u.status = 1);

执行时间对比(子查询返回 1000 个 ):

结论: 略快,但差异不大(MySQL 8.0 优化器很智能)

测试 3:不同 IN 列表大小性能对比

结论:IN 列表越大,性能越差,但不是线性关系

四、源码级分析:优化器如何决策?4.1 成本模型(Cost Model)

MySQL 优化器基于成本模型选择执行计划:

总成本 = IO 成本 + CPU 成本
IO 成本:
- 读取数据页的成本
- 随机 IO vs 顺序 IO
CPU 成本:
- 比较行的成本
- 排序成本
- 临时表成本

IN 查询的成本估算:

-- 优化器估算公式(简化版)
成本 (索引扫描) = N * (IO_cost + CPU_cost)
成本 (全表扫描) = TABLE_ROWS * (IO_cost + CPU_cost)
-- 当 IN 列表占表比例超过阈值时:
-- 成本 (索引扫描) > 成本 (全表扫描)
-- 优化器选择全表扫描

4.2 关键阈值

MySQL 源码中的关键参数(mysql/):

// 范围扫描的最大行数比例
const double MAX_RANGE_ROWS_FACTOR = 0.3;  // 30%
// 索引扫描 vs 全表扫描的阈值
if (range_rows / table_rows > MAX_RANGE_ROWS_FACTOR) {
    // 选择全表扫描
} else {
    // 选择索引扫描
}

但这不是硬性规则! 优化器还会考虑:

覆盖索引:如果索引包含所有需要的列MySQL 中 IN 到底走不走索引?,更倾向于走索引聚簇索引:主键索引的阈值更高数据分布:数据是否均匀分布统计信息准确性: TABLE 更新统计信息五、实战建议 适合使用 IN 的场景主键/唯一索引查询– 推荐

* FROM users WHERE id IN (1, 2, 3);IN 列表较小(< 100 个值)– 推荐

* FROM WHERE IN (100, 200, 300);高选择性字段– 推荐( 有 10000 个不同值)

* FROM WHERE IN (100, 200, 300);

— 不推荐( 只有 5 个不同值)

* FROM WHERE IN (1, 2);子查询结果集较小– 推荐(子查询返回 < 1000 行)

* FROM

mysql fetch into

WHERE IN ( id FROM users WHERE > '2026-01-01');️ 需要谨慎的场景IN 列表过大– 考虑分批查询

* FROM WHERE id IN (1, 2, 3, …, 10000);

— 改为分批

FOR i IN 1..10:

* FROM WHERE id IN (…1000 个…);低选择性字段– 不走索引

* FROM users WHERE IN ('M', 'F');

— 考虑其他优化

* FROM users WHERE = 'M' AND > '2026-01-01';IN + OR 混合– 复杂条件可能不走索引

* FROM

WHERE ( IN (1, 2, 3) OR = 1) AND > 100;

— 简化条件

* FROM

WHERE IN (1, 2, 3) AND > 100

UNION ALL

* FROM

WHERE = 1 AND > 100; 优化技巧

技巧 1:使用临时表

--  IN 列表过大
SELECT * FROM orders 
WHERE user_id IN (1, 2, 3, ..., 10000);
--  使用临时表
CREATE TEMPORARY TABLE tmp_user_ids (user_id INT PRIMARY KEY);
INSERT INTO tmp_user_ids VALUES (1), (2), (3), ...;
SELECT o.* FROM orders o
INNER JOIN tmp_user_ids t ON o.user_id = t.user_id;

技巧 2:使用 替代

--  子查询返回大量数据
SELECT * FROM orders 
WHERE user_id IN (SELECT id FROM users WHERE status = 1);
--  使用 EXISTS
SELECT * FROM orders o
WHERE EXISTS (SELECT 1 FROM users u WHERE u.id = o.user_id AND u.status = 1);

技巧 3:分批查询

# Python 示例
user_ids = [1, 2, 3, ..., 10000]  # 10000 个 ID
batch_size = 1000
for i in range(0, len(user_ids), batch_size):
    batch = user_ids[i:i+batch_size]
    placeholders = ','.join(['%s'] * len(batch))
    sql = f"SELECT * FROM orders WHERE user_id IN ({placeholders})"
    cursor.execute(sql, batch)

技巧 4:强制使用索引(谨慎使用)

-- 强制使用索引
SELECT * FROM orders USE INDEX (idx_user_id)
WHERE user_id IN (1, 2, 3, ..., 500);
-- 注意:只在确认索引更优时使用

六、常见误区 误区 1:”IN 超过 3 个值就不走索引”

真相:没有这个限制!我们测试了 10000 个 IN 值仍然走索引(只要占表比例小)。

误区 2:”IN 永远比 OR 快”

真相:MySQL 8.0 中,IN 和 OR 会被优化成相同的执行计划,性能基本相当。

误区 3:”IN 列表越大越好”

真相:IN 列表过大会导致:

误区 4:”低选择性字段用 IN 没问题”

真相:低选择性字段(如性别、状态)即使 IN 值很少,优化器也可能选择全表扫描。

七、总结 核心结论

问题

答案

IN 走不走索引?

看情况

mysql fetch into,取决于多个因素

IN 超过多少个值不走索引?

没有固定值

,取决于占表比例

IN 和 OR 哪个快?

基本相当

,优化器会做相同优化

主键索引 vs 普通索引?

主键索引阈值更高

(30% vs 20%)

如何优化大 IN 列表?

分批查询、临时表、

最佳实践小 IN 列表直接用( 1000 个值)低选择性字段慎用定期更新统计信息( TABLE)用 验证,不要凭感觉

公告:
1. 本站所有资源来源于用户上传和网络,如有侵权请联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系站长处理!
6. 本站不售卖代码,资源标价只是站长收集整理的辛苦费!如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。
7. 站长QQ号码 2205675299

资源库 - 资源分享下载网 » MySQL 中 IN 到底走不走索引?

常见问题FAQ

关于资源售价和售后服务的说明?
本站所有资源的标价均为本站收集资源的辛苦费,不代表资源本身的价值。软件是高智慧高价值的商品,不可能是白菜价。本站资源标价只是赞助费用,收取的赞助费仅用来维持本站的日常运营!毕竟收集整理资料需要投入云计算资源和站长大量的精力。
代码有没有售后服务和技术支持?
由于代码的运行具有不可预见性,本站不保证代码完整可运行,不提供技术支持和售后服务。 本站原创代码都是站长自己开发的,可以有偿提供技术支持服务。 网站里标明【亲测】的代码都是站长亲测过的,其他的代码由于精力有限,没有一一测试,不能保证代码就一定能够使用,更没有技术支持服务,下载前请自行斟酌。
有没有搭建服务?
由于搭建服务比较费时费力,所以本站除了原创代码外均不提供搭建服务。本站分享代码纯属兴趣爱好,不以盈利为目的,请勿咨询有没有搭建服务,谢谢理解。
链接地址失效了怎么办?
请带上资源链接地址联系客服,工作时间内我们看到后将第一时间回复。
关于解压密码
本站资源一般都没有加密,如果发现需要解压密码的,那么就输入 hao.35dc.com 试试。

发表评论

资源库,由老程序员细心甄别、精心筛选,只为提供优质的源码资源

关于我们 联系我们