select * 在 join 场景下必然导致列名冲突、覆盖索引失效、字段顺序失控、权限失效及性能劣化,必须显式指定带别名的字段。

JOIN 场景下 SELECT * 会直接报错或返回错乱数据
多表 JOIN 时用 SELECT *,数据库第一反应往往是拒绝执行。两个表都有 id、created_at、status 这类通用字段?MySQL、PostgreSQL、SQL Server 全部会抛出类似 Column 'id' is ambiguous 的错误——它根本不确定你要哪个 id。
即使某些数据库(如 SQLite)允许执行,结果集里也会出现重复列名,应用层用 ResultSet.getString("id") 取值时,行为未定义:可能取到左表的、右表的,也可能直接抛异常。BI 工具或 ORM 自动映射时更常见“列名冲突”或“跳过重复列”这类静默丢数据问题。
- 必须显式限定:写成
users.id AS user_id、orders.id AS order_id - 别依赖“自动去重”:没有标准 SQL 规定如何处理同名列,各数据库实现不一致
- 视图或物化视图中用
SELECT *+JOIN,等于主动放弃结构稳定性
SELECT * 在 JOIN 中彻底废掉覆盖索引
假设你有 WHERE users.status = ? AND orders.paid = true,且在 (status) 和 (paid) 上分别建了索引。但只要主查询是 SELECT * FROM users JOIN orders ...,优化器几乎必然放弃走索引,转而全表扫描或回表——因为索引里不可能存下所有字段,而 * 强制它必须把整行数据从聚簇索引里捞出来。
更糟的是,一旦 JOIN 后字段总数超过 20 列,且含 TEXT 或 JSON,单行物理大小暴涨,Buffer Pool 快速被占满,后续查询全卡在磁盘 I/O 上。
- 覆盖索引生效前提:
SELECT的所有字段 +WHERE条件字段,全部落在同一个索引里 -
SELECT *天然违反该前提,尤其跨表时,连“单个索引覆盖单表”都做不到 - EXPLAIN 看到
Using where; Using join buffer或Using temporary就是信号
字段顺序和数量在 JOIN 后完全失控
SELECT * 在单表里至少还按建表顺序返回;一到 JOIN,顺序就由优化器决定——MySQL 可能按左表优先,PostgreSQL 可能按字段物理存储顺序,SQL Server 甚至受统计信息影响动态调整。下游代码若用 rs.getObject(3) 取值,今天跑通,明天加个字段或换数据库版本就错位。
更隐蔽的是:某些 ORM(如早期 MyBatis)会把 SELECT * 结果按列名自动绑定到 Java 字段,但 JOIN 后同名列只绑定第一个,其余被忽略,且不报错——查出来的数据看着对,其实关键字段早丢了。
- 不要靠位置取值:一律用列别名 +
rs.getString("user_email") - 别信“开发环境跑得通”:生产库表统计信息更全,优化器选的执行路径往往不同
- ALTER TABLE 修改字段位置(如
AFTER/FIRST)会直接打乱原有顺序,无提示
权限与安全在 JOIN + SELECT * 下形同虚设
列级权限(如 MySQL 的 GRANT SELECT (id, name) ON users TO 'app')对 SELECT * 不生效。只要用户有表级 SELECT 权,JOIN 后所有字段全暴露——哪怕 users.salt、orders.card_number_encrypted 本不该被读取。
前端或 API 层若直接序列化结果,敏感字段会随响应一起发出。审计系统看到的只是 SELECT * FROM ... JOIN ...,无法判断哪部分数据实际被业务需要,也就没法做精准脱敏或拦截。
- 列级权限只对显式列出的字段起作用
- 某些云数据库(如 AWS RDS for PostgreSQL)的行级策略(RLS)在
SELECT *下可能被绕过 - 日志里记下完整语句,等于把权限边界直接写进慢查询日志里
真正麻烦的不是写几个字段名,而是 SELECT * 在 JOIN 里把「数据契约」整个抹掉:你不再清楚谁在用哪一列、顺序是否可靠、权限是否生效、性能是否可预期。它看起来省了一行代码,实际把所有隐性成本全堆到上线后排查故障的深夜。










