INNER JOIN 是首选,因其仅返回两表匹配记录,语义精准、性能最优且避免 NULL 值干扰;LEFT/RIGHT JOIN 易引入冗余数据或逻辑错误,且开销更大。

用 INNER JOIN 一次性查出主数据和关联数据,就能彻底避开 N+1 查询。这不是“推荐做法”,而是绝大多数场景下唯一合理的解法——只要业务语义允许丢弃无匹配的记录。
为什么 INNER JOIN 是首选而非 LEFT/RIGHT JOIN
INNER JOIN 的语义最贴近“我只关心有关注关系的动态”这类真实需求。LEFT JOIN 会保留 story 表所有记录,哪怕用户根本没关注发布者,结果里就会混入 NULL 值,后续还得在代码里过滤;RIGHT JOIN 更容易写反逻辑(比如误把 follow 当驱动表),且 MySQL 优化器对 RIGHT JOIN 的处理不如 INNER JOIN 稳定。
- INNER JOIN 能让数据库提前剪枝:WHERE 条件(如
f.followerID = ?)可直接作用于连接后的中间结果,减少数据搬运量 - LEFT JOIN 在无匹配时仍要构造空行,增加网络传输和内存开销
- 若业务真需要“未关注用户的动态也显示”,应明确拆成两个查询,或用 UNION + CASE,而不是靠 RIGHT JOIN 强撑
JOIN 查询必须配索引,否则性能可能更差
没有索引的 JOIN 就是全表扫描嵌套,10 万行 × 10 万行 = 100 亿次比对。关键索引不是“随便建一个”,得看执行路径:
- 连接字段必须有索引:
story.userID和follow.followedID都要单独建索引,或合建复合索引INDEX(followedID, followerID) - WHERE 条件字段也要覆盖:
follow.followerID是高频过滤条件,单列索引INDEX(followerID)比联合索引更直接有效 - 用
EXPLAIN验证:看到type: ref或range,且key显示用了哪个索引,才算到位
参数化是硬性前提,别让 JOIN 变成 SQL 注入温床
所有用户输入必须走参数绑定,尤其是 f.followerID = ? 这类 WHERE 条件。手拼字符串(如 "f.followerID = " . $userId)会让整个 JOIN 查询失去缓存能力,还打开注入大门。
- PHP PDO 示例中必须用
$stmt->execute([$userId]),不能用execute(["$userId"])字符串拼接 - MySQL 中数字型参数不加引号,但 PDO 会自动处理类型,开发者只需确保传入的是整型变量
- 如果业务需要动态切换表名或字段名(极少见),必须白名单校验,绝不用用户输入直插 SQL 字符串
最容易被忽略的是 JOIN 后字段重复问题:当 story 和 follow 都有 id 字段时,SELECT * 会导致 PHP fetch() 只拿到最后一个 id 值。必须显式列出所需字段并加别名,比如 s.storyID, s.userID, f.followedID——这既是健壮性要求,也避免传输冗余数据。


















