select * 不一定比写全字段慢,但更危险——它掩盖性能问题、阻碍覆盖索引、增大反序列化压力、引发字段冲突与预编译失败,仅限调试或极小元数据表使用。

SELECT * 真的比写全字段慢吗?
不一定,但绝大多数情况下它更危险——不是因为“多传几个字节”拖慢了网络,而是因为它掩盖了真实性能问题,且在多数场景下反而触发更多开销。
你测出 SELECT * 比 SELECT col1, col2 快,很可能是因为:查询结果集小、字段少、缓存命中高,或者 MySQL 优化器对 * 做了简单路径优化(比如跳过列解析)。但这不具普适性,尤其当表有 TEXT/BLOB 字段、大量冗余列、或走覆盖索引时,SELECT * 就会暴露代价。
- 一旦表结构变更(比如新增一个 10MB 的
json_log字段),SELECT *查询立刻变卡,而明确列出字段的语句完全不受影响 - 无法利用覆盖索引:如果只查
id和name,而这两个字段都在联合索引里,MySQL 可以不回表;但SELECT *强制回表读取所有列,哪怕其他列根本用不到 - 应用层反序列化压力增大:ORM 或 JSON 序列化时,多加载几十个 NULL 或空字符串字段,GC 和内存分配频率明显上升
EXPLAIN 能看出 SELECT * 的隐患吗?
能,但要看清关键字段,不能只扫一眼 type 是不是 ALL。
执行 EXPLAIN SELECT * FROM users WHERE status = 1 后,重点关注:key_len、Extra、rows 这三列。
-
key_len显示实际用到的索引字节数;如果索引包含(status, name, email),但SELECT *导致key_len比SELECT status, name小,说明没走覆盖索引 -
Extra出现Using filesort或Using temporary时,SELECT *往往加剧排序/临时表开销,因为要搬运更多数据 -
rows数值偏大 +type是index或ALL,基本可判定是全索引扫描或全表扫描——这时SELECT *和显式列的物理 I/O 差距就拉开了
什么时候可以勉强用 SELECT *?
仅限于开发调试、临时脚本、或极小数据量的元数据表(比如配置表 sys_config 不超过 100 行)。
生产环境几乎找不到合理理由。连 mysqldump 默认也不用 SELECT *,而是先 SHOW COLUMNS 再拼字段列表。
- 视图定义中禁止用
SELECT *:视图创建后字段绑定固化,源表加列会导致视图查询报错或返回错序字段 - JOIN 场景下绝对避免:两个表都有
id字段,SELECT *会让结果集字段冲突,应用层取值极易出错 - 使用
PREPARE/ 存储过程时,字段数变化会导致预编译失败,错误信息往往是模糊的Column count doesn't match value count
替代方案:怎么写才既安全又高效?
不是靠“习惯”,而是靠工具辅助和流程约束。
- 开发阶段用 IDE(如 DataGrip、DBeaver)自动生成字段列表:右键表 → “Generate SQL” → “Select Statement”,它会按当前 schema 实时生成,不漏不多
- 上线前跑一次
SELECT COUNT(*)+SELECT COUNT(*) WHERE ...验证筛选比例,如果rows接近表总行数,说明索引失效,此时无论写不写 * 都得先调索引 - 在 CI 流程中加入 SQL 审计规则:用 pt-query-digest 或开源 SQL Linter 拦截
SELECT \*出现在非information_schema库的语句
真正难的不是“少打几个字”,而是让团队所有人理解:* 不是偷懒符号,它是隐式依赖——依赖表结构不变、依赖应用层兼容、依赖 DBA 不敢删字段。这种脆弱性,在高并发或大表上,一碰就碎。











