You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL无需子查询的多表JOIN实现方案及性能咨询

SQL问题答复

你编写的原始查询语句如下:

select * from notification where status = 0 and (
 notif_id in (select notif_id from notif_user where user_id = 1) OR 
 notif_id in (select notif_id from notif_group where group_id = 1))

一、原写法的性能问题说明

MySQL 8.0虽然对普通IN子查询做了半连接优化,但当前用OR连接两个跨表IN子查询的写法,存在明确的性能隐患:

  • 优化器无法将两个独立的IN子查询合并为高效的单路径执行计划,大表场景下容易出现子查询结果物化的临时表IO开销,扫描行数会远高于关联查询写法。
  • 执行计划稳定性差,当notif_user、notif_group表的数据分布变化时,很容易出现索引失效、全表扫描的问题,性能抖动明显。
  • 如果子查询返回的notif_id存在重复值,IN逻辑会触发额外的去重校验,带来不必要的CPU开销。

注:如果三张表数据量都在万级以下,且已经建好对应关联字段的索引,该写法不会有明显的性能问题,可以正常使用。

二、JOIN等价改写方案

可以通过JOIN方式改写为无嵌套子查询的形式,由于是OR匹配逻辑(通知要么关联指定用户、要么关联指定用户组),直接JOIN会产生重复行,需要搭配去重逻辑才能和原查询结果完全等价,共两种可用写法:

写法1:LEFT JOIN + DISTINCT(完全无嵌套子查询)

SELECT DISTINCT n.*
FROM notification n
LEFT JOIN notif_user nu
  ON n.notif_id = nu.notif_id
  AND nu.user_id = 1
LEFT JOIN notif_group ng
  ON n.notif_id = ng.notif_id
  AND ng.group_id = 1
WHERE n.status = 0
  AND (nu.notif_id IS NOT NULL OR ng.notif_id IS NOT NULL);

逻辑说明:通过左连接分别匹配用户关联、用户组关联的记录,过滤掉两边都没匹配上的行,最后用DISTINCT去掉同时满足两个条件产生的重复通知。

写法2:INNER JOIN + UNION(大表场景推荐,性能更高)

SELECT n.*
FROM notification n
INNER JOIN notif_user nu
  ON n.notif_id = nu.notif_id
WHERE n.status = 0
  AND nu.user_id = 1
UNION
SELECT n.*
FROM notification n
INNER JOIN notif_group ng
  ON n.notif_id = ng.notif_id
WHERE n.status = 0
  AND ng.group_id = 1;

逻辑说明:把OR条件拆成两个独立的内连接查询,分别查询匹配用户的通知、匹配用户组的通知,最后用UNION自带的去重能力合并结果,每个子查询都可以独立命中对应表的索引,大数据量下执行效率远高于原写法和写法1。

索引优化建议

不管使用哪种写法,都建议创建以下联合索引避免回表,最大化查询性能:

  • notification表:idx_status_notifid(status, notif_id)
  • notif_user表:idx_userid_notifid(user_id, notif_id)
  • notif_group表:idx_groupid_notifid(group_id, notif_id)

内容的提问来源于stack exchange,提问作者user1578872

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.10 16:15:45