代码评审的时候,我见过太多次关于 SELECT * 的争论。批评的人搬出性能、带宽、可维护性,辩护的人一句“表里就几万行,能慢到哪去”就能顶回去。两边都有道理,但很少有人意识到,他们争的并不是一条 SQL 的写法,而是数据库这个行业持续了 50 多年的一条暗线:关系模型和它的一代代挑战者,从 1970 年打到现在还没打完。
说得再直白一点,你每天随手敲下的那行 SELECT *,其实是这场“硅谷战争”里最显眼的一面旗帜。它背后站着 Codd 的关系代数、SQL 标准化的商业史、NoSQL 的造反、NewSQL 的回归,还有几代工程师对“声明式查询”和“导航式查询”两种世界观的反复拉扯。
接下来我从这条线讲起,把关系代数、SQL 标准、列存数据库、分布式数据库的演进串起来,再落回你真正关心的工程问题:SELECT * 到底错在哪、什么时候可以大胆用,以及不想敲它的时候,怎么又快又稳地把列清单生成出来。
1. 别急着批 SELECT *:这场战争到底争的是什么
1.1 排头兵 Codd:一场针对“导航式数据库”的起义
1970 年之前,主流数据库压根不叫 database,而是 IMS、CODASYL 这类层次模型或网状模型。那个时代的查询方式用今天的眼光看非常“原始”:你得知道数据存在哪个记录里,沿着指针一条一条走下去,像在老城区按门牌号找朋友。想找某栋楼里住的所有程序员,你很可能会先遍历每一层、每一个房间,手动判断每个人的职业。
这种“按路径导航”的方式学名叫做导航式数据库,它暴露的问题也很明显:应用代码和物理存储强耦合,数据结构一改,查询逻辑全废。1970 年 6 月,IBM 研究员 Edgar Codd 在《Communications of the ACM》上发表了一篇论文,题目叫“A Relational Model of Data for Large Shared Data Banks”。这篇论文提出一个颠覆性思路:数据只是一张张“关系”(也就是表),行是元组,列是属性,查询应该描述“我要什么”,而不是“怎么找到它”。
关系模型最有杀伤力的三个点,放在当时都是异端:
- 物理独立性:应用不关心数据存在哪个文件、哪块磁盘、走的哪个指针。
- 集合操作:一次查询描述一个集合,而不是一条记录一条记录地遍历。
- 数学基础:基于关系代数和谓词逻辑,而不是一堆没有理论支撑的指针跳转。
Codd 这篇论文刚发表时,连 IBM 内部都有很多人觉得“这玩意儿只是学术玩具,性能不行”。但历史证明,他点燃的这场观念革命,才是今天每一行 SQL 真正的前身。
1.2 两边争论的真问题:不是“用不用星号”,而是“谁掌握查询入口”
很多人把这场战争理解成“公司商战”,其实它更像是一场“世界观之争”。
关系派认为,数据查询应该像做数学题:把条件写清楚,交给优化器去算执行路径。SQL 就是这场运动的公共语言。而反关系派或者说导航派认为,现实世界的查询往往是沿着关系网络导航的,比如“朋友的朋友最近买了什么”,这在 join 里表现为多次递归,成本未必低。早期 NoSQL 的拥护者也说:把数据按聚合根组织好,应用自己按路径取,比关系模型的“先拆开、再 join”高效得多。
两边其实都承认一个事实:没有一个模型能在所有负载下通吃。于是这场仗打了 50 年还没停。关系模型赢下了 OLTP 的主流市场,但挑战者从来没有真正死心。每隔十年,就会有一波“反 SQL”浪潮卷土重来——对象数据库、NoSQL、文档数据库,以及各类专用存储——而浪潮退去后,SQL 又总是用“NewSQL”或“SQL-on-Hadoop”等马甲重新占领高地。
1.3 为什么 SQL 是软件行业里少有的“爷爷级”语言
一个有意思的现象:编程语言平均寿命通常不到 20 年,主流框架更是三五年一换,但 SQL 从 1974 年的 SEQUEL 算起,已经活了超过 50 年。核心原因恰恰在于它的数学基因。
SQL 的底层是关系代数和关系演算。关系代数本身是完备的,可以表达所有一阶逻辑能表达的查询。这意味着 SQL 有“严肃的语法根基”,不是什么公司的临时设计。AI 换了一茬又一茬,框架换了一茬又一茬,但你跟数据库说“我要 A 表里满足 B 条件的 C 列”,这句声明式请求,50 年前是这么写,今天还是这么写。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么偏偏是 SELECT * 成了“两军阵前那面旗”
2.1 从关系代数看 SELECT *:一个被名称误导的“投影”操作
先说一个很多人不知道的历史误会。
在关系代数里,有两个基本操作:一个是选择(selection,符号是 σ),作用是“筛选出满足条件的行”,对应 SQL 里的 WHERE;另一个是投影(projection,符号是 π),作用是“只保留我需要的列”,对应 SQL 里 SELECT 后面的列清单。
也就是说,SQL 里的 SELECT 和关系代数里的“select”根本不是同一个东西。真正的列筛选,在关系代数里叫做“投影”,到了 SQL 里却被命名为 SELECT。这个命名误导已经持续了半个世纪,直接后果就是:新手学 SQL 时最容易混淆的概念,不是 JOIN 也不是 GROUP BY,而是“为什么 SELECT 既负责选列,又带着 WHERE 去选行”。
弄明白这层关系后,你再看 SELECT *,它本质上是“投影所有属性”。你用一对双引号把整张表的所有列都搬走,并且不打算告诉数据库你到底关心哪些字段。这是一种用户和数据库之间的“免责声明”:都给我,我后面自己挑。关系模型的哲学是“让用户声明所需”,而 SELECT * 恰恰是最不声明的一种写法。所以当反关系阵营批判 SQL 啰嗦、语义模糊时,SELECT * 就成了最显眼的靶子。
2.2 字典式的胜利与反叛者的尴尬:想革 SQL 的命,最后还是回到 SELECT
到这里你应该能理解,为什么每次“反关系革命”都避不开 SELECT * 这个话题。
2000 年前后,Google 的 BigTable 论文、Amazon 的 Dynamo 论文,把“可水平扩展的键值存储”推上了神坛。当时很多工程师觉得:SQL 太受限,干脆不要 join、不要复杂事务,我们用 API 自己控制数据访问。于是 MongoDB、CouchDB、Redis 们登台唱戏,“NoSQL”这个标签在 2009 年旧金山一场技术聚会后迅速走红。
结果呢?NoSQL 确实赢了“扩展性”这场局部战役,但输掉了“开发者心智”这场更大规模的战争。原因是残酷的:你可以教育极客们“去 SQL 化”,但你不能让全世界几百万后端工程师忘掉熟悉的 SELECT、WHERE、JOIN。开发者已经形成的肌肉记忆,比任何技术布道都顽固。
于是你看到——Hadoop 生态火了 Hive、Presto(后来叫 Trino),Google 自己给 BigQuery 做了完整的 SQL 接口,MongoDB 从 5.0 开始正式支持 SQL 连接器,而 Spanner、CockroachDB、TiDB 这类 NewSQL 数据库,更是直接把 SQL 和分布式事务同时拉了回来。
反叛者最终都要给 SQL 建一层壳。这也是为什么说 SELECT * 是那面旗:它不仅是一个语法,更代表了“声明式查询”这个已经统治了工程师心智 50 年的习惯。
2.3 顺带澄清:poll/epoll 里的 select 和 SQL 的 SELECT 不是一回事
搜“select 和 poll 和 epoll 区别”的人,往往不是数据库开发,而是做 Linux 网络编程的同学。这里的 select 是 I/O 多路复用系统调用,解决的问题是“一个进程怎么同时盯住大量文件描述符,等它们变得可读或可写”。select 在 fd 数量较多时存在集合拷贝和线性扫描瓶颈,poll 去掉了部分上限但复杂度仍是 O(n),epoll 则基于事件驱动解决了高并发问题。
这跟 SQL 的 SELECT 除共用同一个英文单词外没有任何关系。如果你在写 Go,select 还是一个用于 channel 多路等待的关键字。搜索引擎把所有这些词混在一起给热搜,只会让新手困惑。你现在有了上下文,以后看到任何“select 优化”文章,先分清对方说的是数据库,还是操作系统,还是某门语言自身的关键字,再看结论。
3. 五十年关键战役:从 System R 到 Spanner,SQL 如何输了战役赢了战争
3.1 上半场:关系模型把“导航式数据库”赶出主流
我不打算写枯燥的编年史,但有几个时间点一定要记住,因为它们串起了整个故事。
1974 年到 1979 年,IBM 圣何塞实验室的 System R 项目把 Codd 的关系模型变成了真正
