Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > INNER JOIN、LEFT JOIN和RIGHT JOIN区别

MySQL数据库中INNER JOIN、LEFT JOIN和RIGHT JOIN的区别是什么详解

作者:程序员Benothing

在MySQL数据库操作中,JOIN操作是最常用且最重要的功能之一,它允许我们从多个表中获取相关联的数据,这篇文章主要介绍了MySQL数据库中INNER JOIN、LEFT JOIN和RIGHT JOIN的区别是什么的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

面试官考点分析:

一、标准回答

一句话总结:INNER JOIN 返回两表匹配的行,LEFT JOIN 返回左表全部行 + 右表匹配行,RIGHT JOIN 返回右表全部行 + 左表匹配行。

详细来说:

JOIN 类型结果集未匹配行处理等价关系
INNER JOIN两表交集丢弃
LEFT JOIN左表全集 + 右表匹配右表字段填 NULLRIGHT JOIN 交换表顺序
RIGHT JOIN右表全集 + 左表匹配左表字段填 NULLLEFT JOIN 交换表顺序

在实际开发中,大多数场景使用 LEFT JOIN 即可,RIGHT JOIN 可以通过交换表顺序用 LEFT JOIN 重写,代码可读性更好。

二、核心原理

MySQL 的 JOIN 查询底层使用嵌套循环算法,理解这一机制对写出高效 SQL 至关重要。

2.1 嵌套循环连接

MySQL 执行 JOIN 时,会先选定一张驱动表,然后逐行扫描驱动表,对每一行去被驱动表中查找匹配行。这个过程的伪代码如下:

-- 伪代码描述 JOIN 执行过程
for each row in 驱动表:
    for each row in 被驱动表:
        if 满足 ON 条件:
            输出该行组合

三种 JOIN 的执行差异在于:

2.2 索引对 JOIN 性能的影响

在嵌套循环算法中,被驱动表的连接字段上建立索引是性能优化的关键。原因如下:

使用 EXPLAIN 可以查看执行计划:

EXPLAIN SELECT * FROM orders o
LEFT JOIN users u ON o.user_id = u.id;

重点关注 type 列(ALL 表示全表扫描,ref 表示索引查找)和 rows 列(预估扫描行数)。

2.3 Block Nested-Loop 与 Hash Join

MySQL 8.0.18 开始引入 Hash Join,在被驱动表无索引时也能高效执行:

三、应用场景

理解三种 JOIN 的区别后,关键是要在实际业务中选择正确的 JOIN 类型。

3.1 INNER JOIN 的典型场景

3.2 LEFT JOIN 的典型场景

3.3 RIGHT JOIN 的典型场景

3.4 企业真实场景案例

四、使用方式

下面通过 Java 代码演示三种 JOIN 的实际使用,使用 JDBC 连接 MySQL 并执行查询。

4.1 准备测试数据

-- 用户表
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL
);
-- 订单表
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 插入测试数据
INSERT INTO users (id, name) VALUES
(1, '张三'), (2, '李四'), (3, '王五');
INSERT INTO orders (id, user_id, amount) VALUES
(1, 1, 100.00), (2, 1, 200.00), (3, 2, 150.00);

数据说明:张三有 2 笔订单,李四有 1 笔订单,王五没有订单。

4.2 Java 代码示例

import java.sql.*;
public class MySQLJoinDemo {
private static final String URL = "jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai";
private static final String USER = "root";
private static final String PASSWORD = "your_password";
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {
System.out.println("=== INNER JOIN 结果 ===");
executeJoinQuery(conn, "INNER JOIN");
    System.out.println("\n=== LEFT JOIN 结果 ===");
    executeJoinQuery(conn, "LEFT JOIN");
System.out.println("\n=== RIGHT JOIN 结果 ===");
executeJoinQuery(conn, "RIGHT JOIN");

System.out.println("\n=== 找出没有订单的用户(LEFT JOIN + IS NULL)===");
findUsersWithoutOrders(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void executeJoinQuery(Connection conn, String joinType) throws SQLException {
String sql = "SELECT u.id AS user_id, u.name, o.id AS order_id, o.amount "
"FROM users u " + joinType + " orders o ON u.id = o.user_id";
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
System.out.printf("%-8s %-8s %-8s %-10s%n", "user_id", "name", "order_id", "amount");
System.out.println("----------------------------------------");
while (rs.next()) {
int userId = rs.getInt("user_id");
String name = rs.getString("name");
int orderId = rs.getInt("order_id");
// 注意:order_id 可能为 0(JDBC 对 NULL 整数的默认值),amount 可能为 null
String orderIdStr = rs.wasNull() ? "NULL" : String.valueOf(orderId);
String amountStr = rs.getString("amount");
  System.out.printf("%-8d %-8s %-8s %-10s%n",
          userId, name, orderIdStr,
          amountStr == null ? "NULL" : amountStr);
}
}
}
private static void findUsersWithoutOrders(Connection conn) throws SQLException {
String sql = "SELECT u.id, u.name FROM users u "
"LEFT JOIN orders o ON u.id = o.user_id "
"WHERE o.id IS NULL";
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
System.out.println("没有订单的用户:");
while (rs.next()) {
System.out.println("ID: " + rs.getInt("id") + ", 姓名: " + rs.getString("name"));
}
}
}
}

4.3 执行结果与说明

INNER JOIN 结果:只返回 3 行,张三和李四的订单。王五没有订单,不出现。

LEFT JOIN 结果:返回 4 行,王五的 order_idamount 为 NULL。左表 users 的行全部保留。

RIGHT JOIN 结果:返回 3 行,结果与 INNER JOIN 相同,因为 orders 表中的每一行在 users 表中都有匹配。如果 orders 表中有 user_id 在 users 表中不存在的记录,RIGHT JOIN 会显示该订单行,用户字段为 NULL。

4.4 注意事项

五、扩展延伸

5.1 JOIN 与子查询的对比

维度JOIN子查询
性能通常更快,可利用索引相关子查询可能逐行执行,性能差
可读性多表关联时更清晰简单筛选场景更直观
返回多表字段可以直接返回需要多列子查询,较复杂
适用场景关联查询、多表展示EXISTS/NOT EXISTS、聚合后再关联

5.2 CROSS JOIN 与 FULL OUTER JOIN

-- MySQL 模拟 FULL OUTER JOIN
SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id
UNION
SELECT * FROM users u RIGHT JOIN orders o ON u.id = o.user_id;

5.3 实际开发注意事项

六、面试追问

追问 1:LEFT JOIN 后加 WHERE 条件过滤右表字段,为什么结果和 INNER JOIN 一样?

回答思路:LEFT JOIN 的结果集中,右表无匹配的行对应字段为 NULL。当 WHERE 条件为 right_table.column = '某值' 时,NULL 不满足任何等值条件,这些行被过滤掉,结果集等价于 INNER JOIN。

标准答案:因为 NULL 不能与任何值进行等值比较。如果确实需要过滤右表数据但保留左表所有行,应该把过滤条件写在 ON 子句中,例如:LEFT JOIN orders o ON u.id = o.user_id AND o.amount > 100

追问 2:三张表做 LEFT JOIN,执行顺序是怎样的?

回答思路:LEFT JOIN 是左结合,从左到右依次执行。优化器不能重新排列 LEFT JOIN 的表顺序。

标准答案:对于 A LEFT JOIN B ON ... LEFT JOIN C ON ...,先执行 A LEFT JOIN B 生成中间结果,再以中间结果作为左表与 C 做 LEFT JOIN。因为 LEFT JOIN 不满足 交换律和结合律,优化器必须严格按书写顺序执行。这可能导致性能问题,建议把数据量小的表放在前面。

追问 3:为什么阿里巴巴开发手册建议 JOIN 不超过 3 张表?

回答思路:从性能、可维护性和数据库负载三个角度回答。

标准答案:原因有三:第一,多表 JOIN 导致 MySQL 优化器选择执行计划的计算开销指数级增长,可能选错索引;第二,JOIN 产生的中间结果集可能远大于最终结果集,消耗大量内存和临时表空间;第三,多表 JOIN 的 SQL 可读性差,业务逻辑耦合严重,不利于后续维护和分库分表。建议在应用层拆分查询,用代码组装数据。

追问 4:Hash Join 和 Nested-Loop Join 的区别是什么?

回答思路:从算法原理、适用场景和 MySQL 版本演进角度回答。

标准答案:Nested-Loop Join 通过两层循环逐行匹配,依赖被驱动表的索引实现高效查找。Hash Join 先将被驱动表构建为内存哈希表,再对驱动表逐行探测,时间复杂度更低。MySQL 8.0.18 之前只支持 Nested-Loop,8.0.18 开始默认使用 Hash Join。当被驱动表没有合适索引时,Hash Join 性能远优于 Nested-Loop。但 Hash Join 需要额外内存,如果哈希表超过 join_buffer_size,会分批写入磁盘,性能下降。

总结

到此这篇关于MySQL数据库中INNER JOIN、LEFT JOIN和RIGHT JOIN的区别是什么的文章就介绍到这了,更多相关INNER JOIN、LEFT JOIN和RIGHT JOIN区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文