SELECT * 在大表中慢是因为需读取整行数据,字段多或含大字段会增大I/O和网络开销,且无覆盖索引时会强制回表增加随机IO;应明确列出所需字段并注意WHERE条件字段、去重字段及计数场景优化。

为什么 SELECT * 在大表里特别慢
因为 MySQL 要把整行数据从磁盘或缓冲池里读出来,哪怕你只用其中 1 列。字段越多、尤其是含 TEXT、BLOB、长 VARCHAR,单行体积就越大,I/O 和网络传输开销直线上升。更隐蔽的是:如果没覆盖索引,SELECT * 会强制回表——先走索引查到主键,再根据主键去聚簇索引里捞全行,白白多一次随机 IO。
怎么写才是真“只查需要的字段”
把 SELECT * 换成明确列出字段名,且字段顺序尽量和业务使用顺序一致(对后续应用层处理友好)。重点注意:
- 别漏掉
WHERE条件里用到的字段——它们可能已建索引,但若不在SELECT列表里,优化器有时会放弃使用覆盖索引 - 避免用
SELECT DISTINCT *,必须去重时只列参与去重的字段,否则 MySQL 得比对整行 - 如果只是计数,直接用
COUNT(1)或COUNT(主键),别COUNT(*)后再丢弃数据
示例:
SELECT id, name, status FROM users WHERE status = 'active' AND created_at > '2024-01-01';比
SELECT * FROM users WHERE status = 'active' AND created_at > '2024-01-01';快得多,尤其当
users 表有 20+ 字段且含 avatar_url(长文本)时。
哪些场景下 SELECT * 实际影响不大
不是所有情况都得改——要看数据规模和访问模式:
- 小表(
rows )且字段少(≤ 5 个短类型),<code>SELECT *和显式列差异几乎感知不到 - 用作临时中间结果、立刻被程序过滤或映射(比如 ORM 的懒加载初始化),此时瓶颈常在应用层而非 SQL
- 调试阶段快速看数据,没问题;但上线前必须清理掉,避免被遗忘
关键判断依据:看 EXPLAIN 输出里的 key_len 和 Extra 字段。如果 Extra 出现 Using index,说明走了覆盖索引;一旦变成 Using where; Using index; Using filesort 或更糟的 Using temporary,大概率是字段太多触发了额外操作。
ORM 里怎么避免偷偷执行 SELECT *
很多 ORM 默认查全部字段,比如 Django 的 User.objects.get(id=1)、Laravel 的 User::find(1)。要主动限制:
- Django:用
.values('id', 'name')或.only('id', 'name')(后者保留模型对象结构) - Laravel:用
User::select('id', 'name')->find(1),别用find()或first()默认行为 - MyBatis:XML 中明确写
<resultmap></resultmap>只映射需要字段,别依赖resultType="map"全量映射
最容易被忽略的一点:关联查询(JOIN)时,ORM 往往默认把所有关联表字段也拉全。这时候光限制主表字段没用,必须同步约束关联表的 SELECT 列表,否则性能照样崩。


















