<p>SELECT * 会查出当前权限和上下文下可见的所有列,但不等于“全部数据”,它不包含被WHERE过滤、GROUP BY合并、事务隔离隐藏或RLS限制的行,也不保证返回物理存储的全部行。</p>

SELECT * 会查出所有列,但不等于“全部数据”
直接写 SELECT * 确实能返回表里当前存在的所有列,但它不会自动包含被 WHERE 过滤掉的行、被 GROUP BY 合并掉的重复行,也不会返回被事务隔离级别隐藏的未提交数据。所谓“全部数据”,得先明确你指哪一层——物理存储?逻辑可见?还是业务语义上的“完整记录”?
常见错误是以为 SELECT * 就万无一失,结果在有视图、权限限制或行级安全策略(如 PostgreSQL 的 RLS)的库中漏查数据。
- 如果表有列权限控制(比如某些列对当前用户不可见),
SELECT *会静默跳过那些列,不报错也不提示 - MySQL 5.7+ 默认开启
sql_mode=STRICT_TRANS_TABLES,但SELECT *本身不触发校验,它只忠实地返回你能看到的列 - 在有生成列(generated column)或虚拟列的表中,
*包含它们;但在旧版 MySQL 或 SQLite 中可能不支持,需显式写出列名
想查“真正全部行”,必须绕过 WHERE 和 LIMIT
很多初学者写完 SELECT * 发现结果比预期少,其实是忘了自己或他人在客户端、ORM、中间件里悄悄加了默认 LIMIT(比如 DBeaver 默认限 200 行,MySQL Workbench 可设上限)。真正的“全量”意味着显式排除所有过滤和截断。
- 检查是否隐含
LIMIT:执行SELECT COUNT(*) FROM table_name对比你看到的行数 - 确认没有默认
WHERE条件:有些 BI 工具或 ORM(如 Django 的Model.objects.all()在 debug 模式下可能加了 soft-delete 标记过滤)会注入条件 - 若用 Python + psycopg2 查询 PostgreSQL,记得关掉游标
fetchmany()或fetchone(),改用fetchall();但大数据量时别这么干,容易 OOM
SELECT * 在生产环境要谨慎
它看似省事,实际常引发性能和维护问题。PostgreSQL 的 planner 可能因列过多放弃使用索引;SQL Server 在涉及宽表(100+ 列)时,* 会让网络传输和内存占用陡增;而 Oracle 的 SELECT * 在物化视图刷新时可能因列顺序变化导致失败。
- 列顺序不稳定:ALTER TABLE ADD COLUMN 会改变
*返回顺序,下游解析 CSV 或用位置取值(如row[0])的代码就崩了 - 无法利用覆盖索引:如果只需要
id和status,却用*,数据库仍要回表读所有字段,I/O 浪费明显 - ORM 场景更危险:Django 的
values_list('*')实际发的是SELECT *,但序列化时按模型定义顺序拼接,一旦 DB 字段顺序和模型不同步,数据就错位
替代方案:用 INFORMATION_SCHEMA 构建动态 SELECT
当真需要“全部列”且列名不确定(比如迁移脚本、通用导出工具),与其硬写 *,不如查元数据动态拼 SQL。这能避开列权限干扰,也便于加注释或类型转换。
例如在 PostgreSQL 中获取非系统列:
SELECT string_agg(column_name, ', ') FROM information_schema.columns WHERE table_name = 'users' AND column_name NOT LIKE '\_%';
结果是字符串 id, name, email, created_at,再拼进 SELECT [result] FROM users 即可。
- MySQL 要用
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='t' - SQLite 没有 INFORMATION_SCHEMA,得用
PRAGMA table_info(table_name) - 注意:不同方言对大小写敏感性不同,PostgreSQL 默认小写,MySQL 默认大小写不敏感,拼接前最好统一转小写
列太多时,人眼很难核对 SELECT * 是否真包含了你要的字段,动态拼接虽然多一步,但每列都明明白白列在 SQL 里,调试和交接时少扯皮。

















