select * 必须禁用,因它破坏覆盖索引致回表、引发字段冲突与隐式转换、加剧网络内存开销、导致结构变更时静默故障,并放大缓存与安全风险。

在生产环境的 MySQL 中,SELECT * 不是“慎用”,而是“必须禁用”——它不是慢一点的问题,而是会在索引、网络、缓存、维护、安全多个层面埋下静默故障的种子。
为什么 SELECT * 会破坏覆盖索引
覆盖索引要求查询字段全部命中索引,无需回表。一旦写成 SELECT *,哪怕 WHERE 条件只用 user_id,只要表里有 avatar_url(TEXT)、profile(JSON)这类大字段,优化器就无法走覆盖索引,强制触发回表操作。
常见错误现象:
- 执行
EXPLAIN显示type=ref但Extra列出现Using where; Using index→ 实际没生效,因为*拉了太多字段 - 加了
(user_id, name)复合索引,SELECT name FROM user WHERE user_id = 123走覆盖索引;换成SELECT * FROM user WHERE user_id = 123就变成Using where; Using index condition+ 回表
为什么 SELECT * 在 JOIN 和子查询中极易出错
多表 JOIN 时,SELECT * 会把所有字段平铺展开,字段名冲突、顺序错乱、类型隐式转换全在运行时暴露,应用层取值静默失败。
使用场景与风险:
-
SELECT u.*, o.order_no FROM user u JOIN order o ON u.id = o.user_id:一旦user表新增order_no字段,o.order_no就可能被覆盖或类型不一致 -
SELECT * FROM (SELECT * FROM user) t:MySQL 8.0+ 直接报错Error 1248: Every derived table must have its own alias,且嵌套越深越难定位 - UNION 查询中,
SELECT * FROM a UNION SELECT * FROM b要求两表字段数、类型、顺序完全一致,任一变更即中断
为什么显式写字段能规避结构变更风险
表结构变更是常态,但 SELECT * 让应用代码和数据库 schema 形成强隐式耦合,新增字段、调整顺序、删字段都会导致结果集不可控。
参数差异与后果:
- 字段顺序变化:
ALTER TABLE user MODIFY email VARCHAR(255) FIRST→ 原本第 3 位的email变成第 1 位,ORM 或 JDBCResultSet.getString(2)可能取到username变成id - 新增敏感字段:
ADD COLUMN password_hash CHAR(60)→SELECT *会把哈希值传给前端或日志,违反最小权限原则 - 字段类型变更:
MODIFY status TINYINT→ 原本返回字符串"active"的代码,突然收到整数1,JSON 序列化直接报错
为什么网络和内存开销不是次要问题
即使 DB 和应用同机部署,TCP 协议栈仍存在序列化/反序列化成本;而 TEXT/BLOB 字段超过 728 字节后,InnoDB 会额外触发一次磁盘 IO 读取溢出页 —— 这些开销在单条 SQL 看不出,但在 QPS 上千的接口里会集中爆发。
性能影响示例:
- 一个含
content MEDIUMTEXT的表,SELECT *平均单行传输 120 KB;显式查id,title,created_at仅 200 B → 同样 1000 行,网络字节差 118 MB - JVM 中
ResultSet持有全部字段引用,GC 压力上升,频繁 Full GC 风险提高 - Redis 缓存
SELECT *结果,一条缓存占几 MB,击穿后 DB 直接雪崩
最常被忽略的一点:你写的 SELECT * 可能当下没出问题,但只要它存在于某个定时任务、导出脚本或旧管理后台里,就随时可能因一次 ALTER TABLE 被激活成线上事故——而排查时没人会先怀疑那句“只是取个数据”的查询。











