LEFT JOIN 的语言条件必须写在 ON 子句而非 WHERE,否则 NULL 行被过滤导致退化为 INNER JOIN;多语言多时宜用 GROUP BY + CASE 聚合替代多次 JOIN;触发器同步应优先用 BEFORE INSERT 或直接使用 NEW.id;务必建立 (product_id, lang_code) 联合索引,禁用 JSON 存储多语言。

直接用 LEFT JOIN 多次关联翻译表是可行的,但必须把语言条件写在 ON 子句里,而不是 WHERE;否则缺翻译的记录会整行丢失。
为什么 LEFT JOIN 的语言条件不能放 WHERE
放在 WHERE 会把 LEFT JOIN 退化成 INNER JOIN:数据库先完成连接,再过滤,此时右表字段为 NULL 的行被 WHERE en.lang_code = 'en' 直接踢掉。
- 正确写法:
LEFT JOIN product_translations en ON p.id = en.product_id AND en.lang_code = 'en' - 错误写法:
LEFT JOIN product_translations en ON p.id = en.product_id WHERE en.lang_code = 'en' - 后果:只要某产品缺英文翻译,该产品整条记录就不出现在结果里
多语言字段太多时,用 GROUP BY + CASE 替代堆 JOIN
当支持的语言超过 5 种,或语言列表动态变化(比如后台可配置),硬写一堆 LEFT JOIN 会导致视图难维护、执行计划臃肿。
- 改用聚合方式:
SELECT p.id, MAX(CASE WHEN t.lang_code = 'en' THEN t.name END) AS name_en, MAX(CASE WHEN t.lang_code = 'zh' THEN t.name END) AS name_zh FROM products p LEFT JOIN product_translations t ON p.id = t.product_id GROUP BY p.id - 优势:新增语言只需加一列
CASE,不用动 JOIN 结构 - 注意:必须确保
(product_id, lang_code)有联合索引,否则GROUP BY性能极差 - 缺点:无法在
WHERE中直接过滤某语言的name,得用子查询或HAVING
触发器同步默认语言时,别依赖 AFTER INSERT 的 SELECT
想在插入主表后自动补一条中文翻译?AFTER INSERT 触发器里直接 SELECT id FROM products WHERE ... 很可能查不到刚插的行——事务还没提交,隔离级别会挡住它。
- 推荐方案:用
BEFORE INSERT,直接用NEW.id构造翻译行,INSERT 到翻译表 - 如果必须用
AFTER,就别查主表,直接写死值:INSERT INTO product_translations (product_id, lang_code, name) VALUES (NEW.id, 'zh', NEW.default_name) - 关键约束:
product_translations必须有UNIQUE KEY (product_id, lang_code),否则ON DUPLICATE KEY UPDATE不生效 - 别用 JSON 字段存多语言:索引失效,
WHERE json_value(data, '$.zh.name') = 'xxx'无法走索引
最常被忽略的是联合索引的顺序:(product_id, lang_code) 有效,反过来就几乎没用——因为查询总是先按 product_id 定位,再筛语言。

















