大字段(TEXT、BLOB、JSON)必须与主表物理分离,否则即使只查1行也会触发磁盘临时表、全表扫描和IO翻倍;应通过垂直分表拆至扩展表,列表页显式指定非大字段,详情页单独查询,外键须加索引且大字段默认为NULL。

大字段(TEXT、BLOB、JSON)必须和主表分离,否则一查就拖垮整条 SQL。 不是加索引能解决的,是数据物理布局和 MySQL 执行机制决定的——只要 SELECT 里包含 TEXT 字段,哪怕你只取 1 行,MySQL 就可能放弃内存临时表,转而写磁盘临时表,IO 直接翻倍。
TEXT 字段为什么会让查询变慢
MySQL 对大字段的处理很“实在”:它不会像普通字段那样走内存缓存,而是按需从磁盘读取完整内容。一旦 SQL 中出现 SELECT * 或没限制字段的 select(),哪怕你模板里只用 $user->name,数据库仍要把 content、description 这类字段整个捞出来、序列化、传网络、PHP 再解析——三重浪费。
- 宽表 + TEXT 字段 → 容易触发
Using temporary; Using filesort - 分页查 20 条,每条带 100KB content → 光传输就多占 2MB,Redis 缓存体积也跟着膨胀
-
ORDER BY或GROUP BY涉及 TEXT 字段 → 索引完全失效,必走全表扫描
怎么把 TEXT 字段从主查询里摘出去
核心原则:列表页、聚合页、搜索页,一律不查大字段;只在详情页按需加载。
- 列表页用
field('id,title,created_at,status'),显式排除content、remark等 - 详情页再单独查:
ArticleModel::where('id', $id)->field('id,title,content,cover')->find() - 如果非要一起查(比如导出),改用子查询或 JOIN 拆开:
->field('a.id,a.title,a.created_at,(SELECT content FROM article_ext WHERE article_ext.article_id = a.id) as content') - 模型关联时,
with('ext')的关联模型里必须写死field('article_id,content'),否则照样全字段拉
数据库层面怎么设计才不踩坑
别指望 ORM 层补救,结构不合理,再怎么 field() 都是亡羊补牢。
立即学习“PHP免费学习笔记(深入)”;
- 把
content、html、json_config等大字段单独拆成_ext表,用外键关联,主表保持轻量 -
_ext表的外键字段(如article_id)必须加索引,否则 JOIN 变全表扫 - 避免在大字段上建索引,哪怕只
WHERE content LIKE '%xxx%',也会让索引膨胀、写入变慢 - TEXT 字段默认值设为
NULL,不是空字符串 —— MySQL 对 NULL 的存储更省空间
最常被忽略的一点:字段缓存(optimize:schema)会把 SHOW COLUMNS 结果固化,但如果你后期加了 TEXT 字段又没重新生成,ORM 仍按旧结构解析,可能漏掉字段或报错。上线前务必确认 runtime/schema/ 下的缓存文件已更新。



















