MySQL为何不推荐用SELECT *,尤其在覆盖索引和IO优化场景下?

2026-04-30 21:271阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计975个文字,预计阅读时间需要4分钟。

MySQL为何不推荐用SELECT *,尤其在覆盖索引和IO优化场景下?

直接使用SELECT *会破坏覆盖索引,强制回表查询,这并非可能慢,而是必然慢,因为它是完全随机+I/O的过程。

为什么 SELECT * 必然让覆盖索引失效

覆盖索引生效的前提是:查询所需所有字段都完整存在于某个二级索引的叶子节点中。一旦用了 SELECT *,MySQL 就必须把整行数据捞出来——哪怕你只差一个 TEXTBLOB 字段没在索引里。

TEXTBLOB、超长 VARCHAR(超过 728 字节)根本不会被任何索引存储,MySQL 会静默忽略它们是否出现在联合索引定义中。

  • 执行计划里 type 还是 ref,但 ExtraUsing index 变成 Using where,就是最直接的信号:覆盖已失效
  • 哪怕你给所有“小字段”建了联合索引,只要表里存在 TEXT,这个索引就永远无法覆盖整行
  • 优化器不是“放弃索引”,而是被迫走“索引扫描 + 回表”,成本评估后可能干脆选全表扫描(顺序 I/O 比大量随机 I/O 更快)

大字段带来的额外 IO 开销

InnoDB 对大于 728 字节的字段会做行溢出处理:主记录只存 20 字节指针,真实内容存到单独的溢出页。这意味着读一行,要先读聚簇索引页,再根据指针去读溢出页——至少两次随机 I/O。

  • SELECT * 会把所有溢出字段都拉一遍,哪怕业务完全用不到 logcontentavatar
  • Buffer Pool 被大量无用的大字段占满,挤走真正热的数据页
  • 网络传输体积激增,尤其当应用和 DB 不在同一机房时,延迟和带宽压力明显

显式列名还能规避哪些实际风险

不只是性能问题,SELECT * 在工程落地时容易引发隐性故障:

  • 表结构变更(如新增/删除字段)后,应用层按固定下标解析结果集会错位,比如原 users.id, users.name 变成 users.id, users.email, users.namename 就被读成 email
  • 多表 JOIN 时同名列(如 id)会互相覆盖,不加别名直接报错或返回错误值
  • ORM 映射失败:Hibernate、MyBatis 等依赖列名绑定属性,SELECT * 返回的字段顺序/数量不稳定,容易触发 SQLException 或空指针
  • 敏感字段泄露:表里若有 password_hashtokenSELECT * 会一并查出,后端没过滤就透出给前端

EXPLAIN 是唯一能验证覆盖是否生效的手段

别猜,直接看执行计划。重点盯两个字段:

  • type:确认走了哪个索引(比如 refrange
  • Extra:出现 Using index 才算真正覆盖;只要带 Using whereUsing filesort,就说明有回表或临时表开销
  • 如果 SELECT id, name FROM users WHERE name = ? 走了 idx_nameExtra = Using index,那换成 SELECT *Extra 一定变

真正难的不是写对 SQL,而是每次改查询前,都愿意花 10 秒跑一遍 EXPLAIN —— 很多人跳过这步,直到线上慢查报警才回头补。

标签:Mysql

本文共计975个文字,预计阅读时间需要4分钟。

MySQL为何不推荐用SELECT *,尤其在覆盖索引和IO优化场景下?

直接使用SELECT *会破坏覆盖索引,强制回表查询,这并非可能慢,而是必然慢,因为它是完全随机+I/O的过程。

为什么 SELECT * 必然让覆盖索引失效

覆盖索引生效的前提是:查询所需所有字段都完整存在于某个二级索引的叶子节点中。一旦用了 SELECT *,MySQL 就必须把整行数据捞出来——哪怕你只差一个 TEXTBLOB 字段没在索引里。

TEXTBLOB、超长 VARCHAR(超过 728 字节)根本不会被任何索引存储,MySQL 会静默忽略它们是否出现在联合索引定义中。

  • 执行计划里 type 还是 ref,但 ExtraUsing index 变成 Using where,就是最直接的信号:覆盖已失效
  • 哪怕你给所有“小字段”建了联合索引,只要表里存在 TEXT,这个索引就永远无法覆盖整行
  • 优化器不是“放弃索引”,而是被迫走“索引扫描 + 回表”,成本评估后可能干脆选全表扫描(顺序 I/O 比大量随机 I/O 更快)

大字段带来的额外 IO 开销

InnoDB 对大于 728 字节的字段会做行溢出处理:主记录只存 20 字节指针,真实内容存到单独的溢出页。这意味着读一行,要先读聚簇索引页,再根据指针去读溢出页——至少两次随机 I/O。

  • SELECT * 会把所有溢出字段都拉一遍,哪怕业务完全用不到 logcontentavatar
  • Buffer Pool 被大量无用的大字段占满,挤走真正热的数据页
  • 网络传输体积激增,尤其当应用和 DB 不在同一机房时,延迟和带宽压力明显

显式列名还能规避哪些实际风险

不只是性能问题,SELECT * 在工程落地时容易引发隐性故障:

  • 表结构变更(如新增/删除字段)后,应用层按固定下标解析结果集会错位,比如原 users.id, users.name 变成 users.id, users.email, users.namename 就被读成 email
  • 多表 JOIN 时同名列(如 id)会互相覆盖,不加别名直接报错或返回错误值
  • ORM 映射失败:Hibernate、MyBatis 等依赖列名绑定属性,SELECT * 返回的字段顺序/数量不稳定,容易触发 SQLException 或空指针
  • 敏感字段泄露:表里若有 password_hashtokenSELECT * 会一并查出,后端没过滤就透出给前端

EXPLAIN 是唯一能验证覆盖是否生效的手段

别猜,直接看执行计划。重点盯两个字段:

  • type:确认走了哪个索引(比如 refrange
  • Extra:出现 Using index 才算真正覆盖;只要带 Using whereUsing filesort,就说明有回表或临时表开销
  • 如果 SELECT id, name FROM users WHERE name = ? 走了 idx_nameExtra = Using index,那换成 SELECT *Extra 一定变

真正难的不是写对 SQL,而是每次改查询前,都愿意花 10 秒跑一遍 EXPLAIN —— 很多人跳过这步,直到线上慢查报警才回头补。

标签:Mysql