text字段在join中导致性能下降,是因为优化器为每行预分配大量内存(如数mb),引发临时表写磁盘、using temporary和using filesort;延迟关联通过先用小字段(如id)完成轻量join、再按需补查大字段,显著提升性能。

Text大字段本身不慢,慢在它被提前加载进JOIN过程——只要不让它参与关联阶段,性能就能明显回升。
为什么JOIN时TEXT字段会让查询变卡
数据库在执行JOIN时,会把参与连接的表按行组装成中间结果集。如果右表含TEXT、JSON或超长VARCHAR,哪怕你SELECT里根本没选它,优化器仍可能为每行预分配数MB内存(尤其MySQL 5.7+),导致:
- 临时表撑爆
tmp_table_size,被迫写磁盘 -
Using temporary; Using filesort频繁出现 - 即使LEFT JOIN右表无匹配,也预留大字段空间
延迟关联:先拿ID,再查大字段
核心思路是把“关联逻辑”和“大字段读取”拆成两步:第一步用最小必要列(通常是主键或索引列)完成JOIN;第二步只对最终结果中的少量行,按需补查大字段。
示例场景:查用户订单列表,需显示order_id、user_name和订单备注remark(TEXT类型)
SELECT o.order_id, u.user_name, o.remark FROM `order` o JOIN users u ON o.user_id = u.user_id WHERE u.status = 'active';
→ 问题:remark随每行一起载入,IO暴增
改用延迟关联:
SELECT base.order_id, base.user_name, o2.remark FROM ( SELECT o.order_id, u.user_name, o.order_id AS oid FROM `order` o JOIN users u ON o.user_id = u.user_id WHERE u.status = 'active' ) AS base JOIN `order` o2 ON base.oid = o2.order_id;
关键点:
- 子查询
base只选order_id、user_name等小字段,JOIN轻量 - 外层再用
order_id单点关联补remark,避免批量加载 - 确保
order.order_id有主键或唯一索引,否则外层JOIN会退化
替代方案:EXISTS比LEFT JOIN更安全
当大字段仅用于过滤(比如“只查备注含‘紧急’的订单”),直接用EXISTS跳过字段搬运:
SELECT o.order_id, u.user_name
FROM `order` o
JOIN users u ON o.user_id = u.user_id
WHERE u.status = 'active'
AND EXISTS (
SELECT 1 FROM `order` o2
WHERE o2.order_id = o.order_id
AND o2.remark LIKE '%紧急%'
);
优势:
- 子查询不返回任何列,数据库无需物化
remark内容 - 可配合
remark上的全文索引(如MySQL的FULLTEXT)加速 - 比
LEFT JOIN ... WHERE remark IS NOT NULL少一次大字段扫描
容易被忽略的细节
延迟关联不是万能的,以下三点常被跳过:
- 子查询必须能走索引——如果
base里用了ORDER BY或LIMIT但没覆盖索引,反而触发Using filesort - PostgreSQL中
LATERAL子查询若带SELECT *,仍会物化大字段,必须显式限定列 - 应用层缓存时,别把延迟关联后的两步结果混在一起缓存——
remark更新频率可能远高于订单基本信息,缓存粒度要分开










