日常开发和慢SQL优化中,JOIN、IN、EXISTS是高频使用的关联查询方式。很多同学写SQL时全凭习惯,要么无脑用JOIN,要么一律IN/EXISTS,并不清楚三者的等价场景、底层执行差异,经常出现小数据量没问题、数据量上来直接慢查询爆表的情况。

我在线上SQL巡检、性能调优过程中,见过大量因选错关联语法导致的全表扫描、临时表、文件排序问题。今天我们研究一下三者的等价写法、核心区别、性能优劣以及适用场景。

一、准备测试表与模拟数据

为了保证所有示例结果一致、可落地,我们创建两张业务常用的关联表:用户表 user_info、用户订单表 user_order,一对多关系,一个用户可对应多条订单,无订单的用户为纯用户数据。

下面是完整建表语句、索引语句、批量模拟数据,适配MySQL5.7/8.0所有版本。

-- 创建用户表
CREATE TABLE IF NOT EXISTS user_info (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
    username VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用户名',
    phone VARCHAR(11) DEFAULT NULL COMMENT '手机号',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';

-- 创建订单表
CREATE TABLE IF NOT EXISTS user_order (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
    user_id INT NOT NULL COMMENT '关联用户ID',
    order_no VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单编号',
    order_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '订单金额',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
    KEY idx_user_id (user_id) -- 关联字段建索引,贴合线上真实场景
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户订单表';

-- 插入测试用户数据(10条有效用户)
INSERT INTO user_info (username, phone) VALUES
('张三','13800138000'),
('李四','13800138001'),
('王五','13800138002'),
('赵六','13800138003'),
('孙七','13800138004'),
('周八','13800138005'),
('吴九','13800138006'),
('郑十','13800138007'),
('钱十一','13800138008'),
('冯十二','13800138009');

-- 插入订单数据(部分用户有订单,部分无)
INSERT INTO user_order (user_id, order_no, order_amount) VALUES
(1,'ORD20260713001',99.00),
(1,'ORD20260713002',199.00),
(2,'ORD20260713003',299.00),
(3,'ORD20260713004',59.00),
(5,'ORD20260713005',299.00),
(7,'ORD20260713006',399.00),
(9,'ORD20260713007',129.00);

数据说明:用户1、2、3、5、7、9有订单,其余用户无订单。后续我们统一查询有订单的用户信息,用三种语法实现完全一致的查询结果。

二、JOIN / IN / EXISTS 等价查询实现(结果完全一致)

核心需求:查询所有存在订单记录的用户基础信息。下面分别用内连接JOIN、IN、EXISTS三种方式实现,查询结果完全相同。

1、JOIN 写法(INNER JOIN)

内连接会自动过滤无匹配数据的记录,刚好满足「查询有订单的用户」需求,也是关联查询最常用的写法。

-- JOIN 关联查询有订单的用户(去重,避免多条订单重复返回用户数据)
SELECT DISTINCT u.id, u.username, u.phone
FROM user_info u
INNER JOIN user_order o ON u.id = o.user_id;

2、IN 写法

先通过子查询查出所有存在订单的用户ID,再外层匹配用户信息,适合固定ID集合匹配场景。

-- IN 子查询实现等价效果
SELECT id, username, phone
FROM user_info
WHERE id IN (
    SELECT user_id FROM user_order
);

3、EXISTS 写法

遍历外层用户表,通过子查询判断当前用户是否存在订单记录,存在则保留数据。

-- EXISTS 实现等价效果
SELECT id, username, phone
FROM user_info u
WHERE EXISTS (
    SELECT 1 FROM user_order o WHERE o.user_id = u.id
);

以上三条SQL执行后,返回的数据行数、字段内容完全一致,这也是很多开发者疑惑的点:既然结果一样,为什么线上要区分使用?核心差异不在结果,而在执行逻辑、扫描行数、索引利用率、资源消耗

三、JOIN、IN、EXISTS 核心区别与执行原理

抛开结果看底层,三者的执行顺序、遍历方式、优化器逻辑完全不同,这是性能差异的根本原因。

1、IN 执行原理与特点

IN的执行逻辑是:先执行内层子查询,得到一个临时ID集合,再外层表遍历匹配集合数据

MySQL优化器会对IN子查询做自动优化,将部分IN语句转换成SEMI JOIN(半连接),但依然存在短板:

  • 内层子查询结果集过大时,临时集合会占用内存,匹配效率骤降;

  • 字段无索引时,极易触发全表扫描;

  • 不支持NULL匹配,子查询结果含NULL时,IN查询会直接返回空数据,极易踩坑。

适用场景:子查询结果集小、固定枚举值、单表简单匹配

2、EXISTS 执行原理与特点

EXISTS是外层驱动、内层匹配的迭代逻辑:先遍历外层主表,每一条数据去内层子查询做存在性判断,匹配到第一条数据立即终止当前判断(短路特性),不会遍历全量表。

核心优势:自带短路查询,不需要匹配所有数据,只判断存在性;核心短板:外层表数据量大、内层无索引时,性能极差,相当于循环嵌套查询。

适用场景:主表数据量小、子表数据量大,关联字段有索引

3、JOIN 执行原理与特点

JOIN是MySQL优化器优化最成熟的关联方式,支持嵌套循环连接、哈希连接、块嵌套连接多种算法,会自动根据两张表的数据量,选择小表做驱动表、大表做被驱动表,最大化减少扫描行数。

INNER JOIN 天然过滤不匹配数据,LEFT JOIN 会保留主表所有数据,需要注意去重和空值判断。JOIN的最大优势是索引利用率最高、优化器适配性最强、大数据量下稳定性最好

适用场景:两张及以上表关联查询、数据量大、需要多字段关联、线上核心业务查询

四、三者性能对比

结合线上千万级数据表调优经验,直接给大家可落地的性能结论,不用记复杂原理,日常开发直接套用即可:

1、小数据量场景(双表均10w以内)

三者性能几乎无差异,MySQL优化器会自动抹平差距,随便写都不会出现慢查询,优先选代码简洁的IN或EXISTS即可。

2、大数据量场景(核心重点)

  • 子表大、主表小:EXISTS 性能最优,优于 JOIN、IN;短路特性可以避免大量无效匹配

  • 主表大、子表小:IN 性能最优,优于 EXISTS;先锁定小结果集再匹配,减少主表扫描次数

  • 双表均大、常规关联查询:JOIN 性能最稳定,远超 IN/EXISTS,优化器会自动选最优驱动方式,避免嵌套循环低效问题

3、通用避坑结论

  • 禁止大结果集使用IN子查询:子查询返回上万条数据后,IN的临时集合匹配效率会断崖式下跌;

  • 无索引禁止用EXISTS做大表遍历:会触发双重全表扫描,直接变成慢SQL;

  • 业务关联查询优先JOIN:绝大多数线上多表关联场景,INNER JOIN是最优解,可读性、性能、稳定性最高;

  • EXISTS只做存在性判断,不要用来批量查字段数据;IN只适合小集合匹配。

五、日常开发落地规范

1. 简单存在性判断、主小表、子大表:用 EXISTS

2. 固定枚举值、小结果集匹配:用 IN

3. 所有多表关联查询、大数据量业务查询、需要返回多表字段:统一用 JOIN

4. 所有关联字段(join字段、in匹配字段、exists判断字段)必须建立索引,这是性能最优的前提,语法选择只是锦上添花

5. 杜绝嵌套多层 IN/EXISTS,多层子查询无法被优化器优化,极易产生慢查询

六、写在最后

很多新人开发者觉得「三种写法结果一样,随便写就行」,但线上性能问题往往就是这些细节积累的。小数据量掩盖了所有问题,一旦数据量增长到百万、千万级,语法选择的差异会被无限放大。

掌握三者的等价写法、底层逻辑、适用场景,不是为了炫技,而是为了写出可复用、高性能、无隐患的业务SQL,从源头减少慢SQL,降低后续调优成本。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐