<p>SELECT * 在生产环境危险,因全字段读取放大I/O与网络开销,易致慢查询、内存溢出、超时;ORM全量查询关联大表时更易拖垮服务,须改用明确字段列表并结合执行计划、索引覆盖及业务语义精简。</p>

SELECT * 为什么在生产环境里是危险操作
因为数据库要读取、传输、解析整行所有字段,哪怕你只用其中一两个。尤其当表里有 TEXT、JSON、BLOB 或宽字符串列时,I/O 和网络开销会指数级上升。
常见错误现象:SELECT * 导致查询变慢、内存溢出、应用超时;ORM 自动生成的全量查询在关联多张大表时直接拖垮服务。
- 使用场景:调试阶段查数据可以临时用,上线前必须替换为明确字段列表
- 性能影响:MySQL 在 InnoDB 中即使不返回某列,只要它在
SELECT列表里,仍可能触发额外的二级索引回表 - 兼容性风险:表结构变更(如加列、改类型)会让依赖
SELECT *的代码意外出错或返回错序字段
如何安全地精简 SELECT 列表
不是简单删掉不用的列,而是结合执行计划、业务语义和索引覆盖来判断哪些列真能去掉。
实操建议:
- 先用
EXPLAIN看查询是否走索引覆盖(Extra字段含Using index),如果没覆盖,删列可能没效果,得先建复合索引 - 检查应用层是否隐式依赖字段顺序(比如用
row[0]取值),改成按列名访问(row["user_id"])再删列 - 注意
GROUP BY、ORDER BY、HAVING中引用的列不能删,否则报错Unknown column in 'field list' - 视图或子查询中删列需同步更新上层 SQL,否则出现
Column not found in field list
JOIN 场景下最容易漏删的冗余列
多表 JOIN 时,常把所有表的 * 都写进去,结果返回几十个字段,其中大量重复 ID 或无用元数据。
典型问题:
-
users.id和orders.user_id同时出现在结果里,但业务只用其中一个 - 关联表的
created_at、updated_at被全量拉取,实际只用于排序,可改用子查询或延迟加载 - MySQL 8.0+ 支持
USING (id)简化 ON 条件,但不会自动去重字段,仍需手动指定别名避免冲突
示例:把 SELECT u.*, o.* FROM users u JOIN orders o ON u.id = o.user_id 改成 SELECT u.id, u.name, o.amount, o.status FROM users u JOIN orders o ON u.id = o.user_id,体积减少 60%+ 很常见。
ORM 框架里怎么控制 SELECT 列表
很多框架默认生成全量字段,得主动干预,否则优化白做。
不同语言常见做法:
- Django ORM:用
.values("id", "name")或.only("id", "name"),注意only()对外键字段无效,得配合select_related() - SQLAlchemy:用
session.query(User.id, User.name)替代session.query(User),避免实例化完整对象 - MyBatis:XML 中写明确字段,别用
<resultMap>映射全部字段;注解方式用@Select("SELECT id, name FROM users") - Node.js pg:传入
fields选项或手写 SQL,不要依赖SELECT *+ 客户端过滤
容易踩的坑:有些 ORM 的 distinct() 或分页逻辑会悄悄补全字段,得看生成的 SQL 日志确认。
复杂点在于,字段依赖可能藏在模板渲染、导出逻辑、审计日志里——上线前最好用慢查询日志 + 抓包验证最终返回字段数。


















