复合主键join必须用and连接各字段等值条件,不可用(a,b)=(c,d);索引字段顺序须与on中字段最左匹配顺序一致,且tenant_id等租户字段必须显式参与join以避免数据错乱。

复合主键 JOIN 必须用 AND 连等值条件
不能写成 (a, b) = (c, d) 或用逗号分隔,标准 SQL 不认这种语法。数据库只接受显式的列对列比较,且必须全部用 = 连接。
比如订单明细表 order_items 和订单备注表 order_item_notes 共享 (order_id, product_id) 复合主键,正确写法是:
SELECT oi.*, oin.note FROM order_items oi JOIN order_item_notes oin ON oi.order_id = oin.order_id AND oi.product_id = oin.product_id
-
ON后面只能跟布尔表达式,AND是唯一合法连接多个等值条件的方式 - 顺序无关(
oi.order_id = oin.order_id AND oi.product_id = oin.product_id和反过来效果一样),但索引顺序会影响性能——见下一条 - 如果其中任一字段为
NULL,整行匹配失败(=对NULL返回UNKNOWN),需确认业务是否允许空值
联合索引必须匹配 ON 字段顺序
建了 INDEX idx_on_order_product ON order_items(order_id, product_id),但 ON 里写成 oi.product_id = oin.product_id AND oi.order_id = oin.order_id,MySQL 的 EXPLAIN 里 key_len 可能只显示第一个字段长度——说明只用了索引前缀,第二字段退化为扫描过滤。
- 索引字段顺序要和
ON中**最左匹配字段顺序一致**,不是“哪个先出现就建哪个”,而是看查询中哪列选择性更高、更常用于范围过滤(如tenant_id比user_id值更少重复,就该放前面) - 单列索引
(order_id)+(product_id)无法支撑复合 JOIN,优化器通常只选其一 - PostgreSQL 要看
Index Cond是否覆盖全部 JOIN 列,而不是只看Filter
LEFT JOIN 多字段关联后行数异常膨胀
结果行数远超左表记录数?大概率不是 JOIN 写错了,而是右表在复合键上存在重复组合值,或者 ON 条件混入了非等值逻辑。
- 先查右表是否有重复:
SELECT order_id, product_id, COUNT(*) FROM order_item_notes GROUP BY order_id, product_id HAVING COUNT(*) > 1 -
LEFT JOIN ... ON a = b AND c IN ('x', 'y')会把一条左表记录匹配多条右表记录,这不是 bug,是语义本身如此 - 若只想保留最多一条备注,得加子查询或
ROW_NUMBER()窗口函数限定,不能靠改ON条件硬压
跨库或分库场景下 tenant_id 必须显式对齐
分库后 orders 和 users 表都按 tenant_id 拆分,JOIN 时漏掉 tenant_id 就等于在错误租户里找用户——数据错乱但 SQL 不报错。
- 即使两个表都在同一数据库,只要业务逻辑要求租户隔离,
tenant_id就是 JOIN 的必要条件,不是可选修饰 - 别依赖“反正 tenant_id 总是一样的”这种假设,生产环境总有例外(比如迁移未完成、测试数据混入)
- 建议把
tenant_id放ON最前面,既符合索引顺序习惯,也强化语义优先级










