不推荐用 select *,因为它会放大维护成本、隐藏性能隐患、破坏索引优化机会;尤其导致覆盖索引失效、增加网络与内存开销、引发字段变更时的静默故障。

直接说结论:不推荐用 SELECT * 不是因为它“慢”,而是因为它在多数生产场景下会放大维护成本、隐藏性能隐患、破坏索引优化机会——尤其当表结构变化或字段含大文本时,问题会突然爆发。
覆盖索引失效是最大隐性代价
MySQL 优化器只有在明确知道所需字段全部被索引覆盖时,才会走“覆盖索引”(index-only scan),避免回表。而 SELECT * 让优化器无法做这个判断,哪怕你只查 id 和 name,只要表里有 content TEXT 这种字段,SELECT * 就大概率放弃覆盖索引,强制回表或全表扫描。
- 典型场景:查询用户基础信息,但表里带
avatar BLOB或bio TEXT - 后果:原本能走
idx_user_id_name的查询,变成先走索引再逐行回表取所有字段 - 验证方式:
EXPLAIN看Extra列是否出现Using where; Using index(覆盖)还是Using where(需回表)
网络和内存开销在分布式架构下会被放大
当应用与数据库不在同一台机器(绝大多数线上环境),SELECT * 会把所有字段——包括你根本不用的 created_at、updated_at、status 甚至 memo LONGTEXT ——全部序列化、传输、反序列化。这不是“多几个字节”的问题。
- 实测案例:某日志表含
raw_data JSON字段(平均 12KB),SELECT *比SELECT id, ts多消耗 97% 的网络带宽 - 内存风险:ORM(如 MyBatis、Hibernate)默认将整行映射为对象,
*会导致 Java 堆中加载大量无用字段,GC 压力陡增 - 注意:即使字段类型是
VARCHAR(200),只要实际存了长内容,InnoDB 仍可能触发额外 IO(超 728 字节时溢出存储)
字段变更时的脆弱性远超预期
SELECT * 在开发期看似省事,但一旦上线,任何列增删都可能引发静默故障——尤其在使用 resultMap、DTO 映射或视图时。
- MyBatis 中
resultMap未同步更新字段顺序,会导致id映射到username字段 - 视图定义含
SELECT * FROM user,后续在user表加tenant_id,视图不会自动包含,但查询仍成功(只是漏数据) - INSERT ... SELECT * 场景下,源表和目标表字段顺序不一致,会错位插入(比如把
email写进phone列) - ORM 如 JPA 的
@Entity字段删掉后,SELECT *仍返回该列,但实体类已无对应属性,抛SQLException: Column not found
真正危险的不是“写起来麻烦”,而是 SELECT * 把字段依赖从显式契约变成了隐式假设。它在小表、单机、纯读场景下可能无感,但只要涉及大字段、跨服务、长期迭代,问题就会在某个凌晨三点精准出现。










