必须显式指定SELECT字段而非使用SELECT *,否则含BLOB/TEXT的表会全行加载导致内存暴增、临时表激增及OOM;需配合覆盖索引与ORM字段限定才能真正生效。

直接在 SELECT 里写死字段名,别用 SELECT *——这是唯一真正起效的起点。所有后续优化(索引、拆表、ORM调用)都建立在这个前提上。
为什么 SELECT * 在含 BLOB/TEXT 的表里特别危险
不是“慢一点”,而是 MySQL 会把整行(包括几 MB 的 avatar 或 raw_data)全加载进内存,哪怕你只想要 id 和 name。视图、子查询、CTE 都不会自动帮你剪掉这些字段;ORM 的 .all() 或 find() 默认行为也照单全收。
常见错误现象:
- 执行
EXPLAIN显示rows很小但Extra有Using temporary或Using filesort -
SHOW SESSION STATUS LIKE 'Created_tmp_disk_tables'值持续上涨 - 应用层内存占用陡增,GC 频繁,甚至 OOM
怎么写 SELECT 才安全
核心就一条:显式列出业务真正需要的字段,且明确排除大字段。
错误写法:SELECT * FROM users WHERE status = 'active'
正确写法:SELECT id, name, email, status, updated_at FROM users WHERE status = 'active'
实操建议:
- 把
content、description、blob_data这类字段从SELECT列表里彻底删掉 - 如果某接口偶尔需要大字段(如详情页),单独建一个
user_detail视图或查询,不要混在列表逻辑里 - 用
EXPLAIN FORMAT=JSON检查query_block -> table -> rows和bytes字段,确认没膨胀
覆盖索引必须跟上,否则白写了字段列表
只写字段还不够。如果底层表没对应索引,MySQL 仍会回聚簇索引读整行(含被你排除的大字段),IO 一样爆。
检查方法:EXPLAIN SELECT id, name FROM user_list WHERE status = 'active',看 Extra 是否出现 Using index
建索引示例:CREATE INDEX idx_status_id_name ON users(status, id, name)
注意点:
-
status是WHERE条件,放最左 -
id和name是SELECT字段,补在右侧构成覆盖 - 索引顺序不能颠倒,否则无法命中
ORM 调用时字段控制最容易被忽略
Django、Laravel、SQLAlchemy 等框架默认生成的 SQL 往往是 SELECT *,哪怕你定义的视图本身已精简。
Django 示例:UserView.objects.values('id', 'name', 'status') 安全;UserView.objects.all() 危险
Laravel 示例:UserView::select('id', 'name', 'status')->get() 安全;UserView::all() 危险
关键动作:
- 开启框架查询日志,确认最终发出的 SQL 是不是你写的那几个字段
- 禁止在生产环境用
.all()、find()、get()不带字段参数的调用 - 团队代码规范里明确写死:视图查询必须用
select()或values()
真正麻烦的从来不是“不知道要写哪些字段”,而是开发时图省事用 *,上线后才发现内存和 IO 已经被拖垮——而这时往往连 EXPLAIN 都跑得比平时慢。

















