从文件存储到数据库:分享一下数据管理的大体脉络

从文件存储到数据库:分享一下数据管理的大体脉络

有没有想过,网店、后台、手机里那堆 App,数据到底存在哪儿?答案很简单,最底层全都躺在文件里。可既然文件就能存,为什么大家还要费劲去折腾数据库这玩意儿?

这回就从"一个让你想放弃文件存储的故事"讲起,一路讲到关系型、NoSQL、宽表、向量库,最后再回头看看你一开始可能问的那些问题。

一个让你放弃文件存储的故事

想象你在经营一家网店。第一天开业,你用一个文本文件 users.txt 记录用户:

张三, 13800000001, 北京
李四, 13800000002, 上海

很完美,简单、直观、零学习成本。

三个月后,生意火了。你雇了两个客服,他们同时打开这个文件编辑,结果李四的地址被另一个客服顺手覆盖了,丢了。你想给用户按城市分组统计,吭哧吭哧写了 200 行解析代码,结果文件里混进一行少写一个逗号的数据,程序直接崩。双十一流量高峰,文件被写坏了,你只能恢复到三天前的备份,几百条订单没了。

半年后更惨。你的用户数据分散在 users.txtorders.csvaddress.json 三个文件里。李四改了手机号,你改了 users.txt,却忘了 orders.csv 里还躺着个旧号码,数据对不上了。

这个故事里的每一个坑,都对应着数据库要解决的一个核心问题。

为什么不能直接读写文件

先说句公道话,文件本身没错,它是操作系统给的基础能力。问题出在,文件系统只负责"存",不负责"管"。一旦数据量、并发量、复杂度上去了,裸用文件就会撞上一连串结构性障碍。

最要命的是并发。操作系统对文件的写入是"块"级别的,根本没有"行"或"记录"的概念。两个进程同时写同一个文件,没有任何机制保证 A 写一半的时候 B 不会插进来。你想解决,就得自己动手实现锁,而正确地实现锁,是计算机科学里最容易翻车的任务之一,死锁、饿死、竞态条件,一个比一个阴。数据库在存储引擎这一层就把行级锁、多版本并发控制做掉了,让"读写不互斥、写写安全排队"成了默认行为 [7]。

其次是查询。在文件里找"所有北京的用户",唯一的办法是从头读到尾。1 万条数据无所谓,1000 万条就是灾难。数据库靠索引(最典型的是 B+ 树)把查找复杂度压到对数级别,1000 万条数据,大概只需要访问 3 到 4 次磁盘页 [7][10]。

再就是脏数据和崩溃。文件根本不关心内容,手机号填了"你好"、年龄填了 -5、必填字段空着,它全都照单收下,数据质量一天天劣化。而写文件时一旦进程崩溃或断电,很可能留下写了一半的内容,你的 users.txt 说不定就永久损坏了。数据库用模式加约束在写入那一刻就把非法数据挡在门外,用预写日志加事务来兜底,要么全部写入成功,要么当作什么都没发生,这就是原子性 [7][10]。

最后还有两个不那么显眼但同样麻烦的问题。一是没有查询语言,换个统计口径就得重写一遍解析代码,数据和逻辑死死缠在一起;数据库用 SQL 让你只说"要什么"不说"怎么拿",优化器自动挑最优执行路径。二是权限太粗,文件权限顶多控制"谁能打开",管不了"谁能看这一列工资、谁能改但不能删"这种细活儿,数据库能给你表级、列级、行级的细粒度控制。

说到底,不是"不能"用文件,而是当你自己动手把并发控制、索引、崩溃恢复、约束、权限、查询优化全都实现一遍之后,你相当于重新发明了一个数据库,而且大概率不如人家三十年打磨出来的稳定。

顺带说个有意思的事,数据库底层最终也是把数据存在文件里的(比如 MySQL 的 .ibd 文件)。数据库不是抛弃文件,而是在文件之上盖起了一整套精密的数据管理体系。它提供的六个核心价值,也就是事务的 ACID、索引查询、约束体系、MVCC 并发控制、WAL 崩溃恢复、三级模式的数据独立性,全部建立在这个基础上 [1][7][10]。

这里重点说说 ACID,它是数据库最伟大的发明。事务保证一组操作要么全部成功,要么全部回滚。其中原子性意味着转账时扣款和到账必须同时发生,一致性意味着转账前后两个账户余额总和不变,隔离性意味着两个人同时买最后一件库存商品不会都成功,持久性意味着提交之后数据库重启数据也不丢。"同时买最后一件商品"这个场景,用文件几乎没法正确实现,用数据库,一句 SELECT ... FOR UPDATE 或者调个隔离级别就搞定了 [10]。

演进史,从关系模型到 SQL

理解了数据库要解决什么,再看它怎么一步步长成今天这样,脉络就清楚了。

1960 年代是层次数据库和网状数据库的天下,数据按树状或网状组织,查数据得写"导航式"代码,先访问 A,再顺着指针走到 B,路径全写死在程序里,数据结构一改,程序就得大改。

1970 年,IBM 研究员 Edgar F. Codd 发表了《A Relational Model of Data for Large Shared Data Banks》,提出用关系(也就是二维表)来组织数据 [1],这篇论文拿了 1981 年图灵奖。它的革命性在于,你只需要描述数据之间的关系,不需要描述访问路径,怎么找数据是数据库优化器的事。这就是声明式查询的威力 [1][5]。核心思想就三条,数据组织成表,行是记录、列是字段;表与表之间靠主键和外键关联;操作数据用关系代数,也就是后来的 SQL。

一个电商的最小关系模型长这样:用户表存 id, name, city,订单表存 id, user_id, amount, statususer_id 是外键,指向用户表的 id。想查"北京用户的订单总额",一句 SQL 就够:

SELECT u.city, SUM(o.amount)
FROM users u JOIN orders o ON o.user_id = u.id
WHERE u.city = '北京'
GROUP BY u.city;

关系模型还带来了一套叫"规范化"的设计理论,核心是"一事一地",也就是一个事实只存一处,通过范式(1NF、2NF、3NF)消除冗余 [10]。实践中大部分业务表设计到 3NF 就够了;分析型场景反而会故意反范式做宽表来减少 JOIN,这给后面的宽表数据库埋了伏笔。

SQL 源自 IBM System R 的 SEQUEL,1986 年成为 ANSI 标准。四十多年过去,它依然是数据领域事实上的通用语,而且 NoSQL 运动绕了一大圈之后,很多产品又纷纷加回了 SQL 接口,这事儿本身就挺说明问题的 [5]。

关系型数据库的代表产品也很好认。Oracle 是功能最全的企业霸主,MySQL 是互联网装机量之王,PostgreSQL 号称"开源界的 Oracle",SQLite 则因为嵌入式特性成了全球部署量最大的数据库,每台手机里都有。

NoSQL 不是否定,是取舍

2000 年代中期,Google、Amazon 这些互联网巨头撞上了传统关系库撑不住的三座大山,一个是海量数据(单机库存不下 PB 级,得水平扩展),一个是超高并发(双十一级别的访问量,单机连接数扛不住),一个是灵活模式(产品迭代快,改一次表结构锁表几小时受不了)。Google 的 Bigtable 和 Amazon 的 Dynamo 两篇论文,直接引爆了整个 NoSQL 运动 [2][3]。

它们做的取舍很激进。放弃关系模型(不做 JOIN,把数据按访问模式拍平),放松事务保证(只保证单键原子性,不保证跨行 ACID),放松一致性(允许短暂的数据不一致换可用性)。所以更准确的理解是,NoSQL 不是对关系数据库的否定,而是在特定场景下,用"少一些保证"换"多一些扩展性和灵活性"。它否定的不是关系模型本身,而是"一种数据库打天下"的通用主义。

Stonebraker 早在 2005 年就预言专用数据库会取代通用数据库 [6],NoSQL 正是这个预言的应验,而 Stonebraker 本人恰恰是关系数据库时代的奠基人之一,革命者居然来自旧阵营内部。

理解 NoSQL 的取舍,绕不开 CAP 定理。分布式系统在网络分区发生时,只能在一致性和可用性之间二选一。传统单机关系库不考虑分区,是"CA";分布式 NoSQL 多数选"AP"加最终一致性,也就是 BASE,基本可用、软状态、最终一致 [4]。

NoSQL 有四大主流类型,每一种都是在关系模型上做减法(减掉 JOIN、Schema 或事务)的同时做加法(加上某种场景的极致优化):

  • 键值型(KV):减掉一切查询能力,换来极限速度,代表是 Redis、DynamoDB
  • 文档型:减掉 JOIN 和 Schema,换来"一次查询取回完整对象",代表是 MongoDB
  • 宽表型:减掉关系完整性,换来线性写扩展,代表是 Cassandra、HBase
  • 图型:把"关系"从 JOIN 操作变成一等公民存储结构,代表是 Neo4j

宽表,为海量写入而生

为什么叫"宽表"?设想一个传感器数据表,不同型号的传感器上传的字段完全不同,温度传感器有 temperature,电量传感器有 voltage。用关系型建模,要么建一堆可空列(表越来越"宽"),要么拆成 EAV 模型(查询痛苦得要命)。宽表数据库的答案,是让每一行可以拥有完全不同的列,列不用预先定义,百万列也无所谓。"宽"指的是逻辑上允许一张表横向无限延展。物理上,数据按行键有序存储,同一行的列稀疏存储,没值的列不占任何空间,这和关系库"每行必须填满所有列"完全是两回事。

宽表数据库普遍采用 LSM 树作为存储引擎 [12],和关系库的 B+ 树形成鲜明对比。B+ 树就地更新、为读优化,写放大高;LSM 树追加写、先写内存再批量刷盘、为写优化,顺序写对磁盘极其友好。一句话,B+ 树为读优化,LSM 树为写优化,这就是 Cassandra 敢号称"单集群每秒百万级写入"的底气。

两大门派里,HBase 源自 Bigtable 论文 [2],构建在 Hadoop/HDFS 之上,强一致,适合和大数据生态配合;Cassandra 融合了 Bigtable 的数据模型加 Dynamo 的分布式架构(无主 P2P、一致性可调)[3],AP 型高可用,多数据中心支持出色。

注意别和另一个"宽表"搞混。数据仓库里的宽表是为了分析方便,故意把几十张维度表反范式 JOIN 成一张超宽的分析表。前者是一种存储引擎结构,后者是一种建模手法,俩只是恰好都叫"宽表"。

其他类型与选型

数据库的版图还在扩张。近十年起来的,有时序数据库(为"按时间写、按时间读、按时间删"而生,代表 InfluxDB、TimescaleDB、Prometheus)、向量数据库(AI 时代的新贵,把文本图片映射成高维向量,查询变成找最相似的 K 个向量,代表 Milvus [9]、Pinecone,以及 pgvector 这类关系库插件,RAG 应用的核心基础设施就是这类数据库)、NewSQL 或叫分布式 SQL(想"既要 NoSQL 的水平扩展,又要关系库的 ACID 和 SQL",Google Spanner [8]、TiDB、CockroachDB 是代表,本质是把 Codd 的关系模型在分布式环境下重新实现了一遍 [11])。还有 HTAP 让一套系统同时跑交易和分析,多模数据库让一个引擎同时支持关系、文档、图、KV 多种模型,PostgreSQL 就是个活生生的例子,关系型数据库自己反而长成了全栈选手。

没有最好的数据库,只有最合适的数据库。常规业务系统(订单、用户、财务)用 PostgreSQL 或 MySQL,因为需要 ACID 和关系完整性;缓存、排行榜、会话用 Redis,微秒级延迟;字段频繁变化、以 JSON 为主用 MongoDB;海量日志或传感器写入用 Cassandra 或 ClickHouse;关系深度遍历用 Neo4j;监控指标和行情用 InfluxDB;AI 语义检索用 Milvus 或 pgvector;需要水平扩展又不牺牲一致性用 TiDB 或 CockroachDB;手机 App 内嵌用 SQLite。

默认从 PostgreSQL 开始,它今天已经是事实上的多模数据库,能覆盖 80% 场景,错了迁移成本也最低;没有明确理由就别引入新数据库,每种数据库都是一笔运维成本和技术债;组合使用是常态,主流互联网公司的标准配置是"关系库做交易 + Redis 做缓存 + ES 做搜索 + ClickHouse 做分析",各司其职。

总结

为什么不能直接读写文件?因为文件只负责"存",不负责"管"。并发安全、高效检索、崩溃恢复、数据约束、权限控制,这些需求文件系统一概不提供,而自己实现它们等于重新发明数据库。

数据库是什么?它是建立在文件之上、提供 ACID 事务、索引查询、并发控制、恢复机制和声明式查询语言的数据管理系统。需要它,是因为现代应用对数据的正确性、并发性和规模有要求,而这些要求裸文件满足不了。

关系型和非关系型是否定关系吗?不是,是取舍与分工。关系模型提供最强的一致性保证和查询表达力,至今仍是交易类系统的基石 [1];NoSQL 是互联网规模下"用部分保证换扩展性和灵活性"的场景化特化 [2][3][6];NewSQL 又把两者重新缝合 [8][11]。数据库的历史是一场"分久必合、合久必分"的螺旋,不是单向革命。

宽表数据库是什么?一种以"行键 + 动态列族"组织数据、以 LSM 树为存储引擎、为海量写入和稀疏数据优化的 NoSQL 类型,代表是 Cassandra 和 HBase [2][3][12]。

在数据库五十多年的历史里,"颠覆关系数据库"的预言至少出现过五次,而关系数据库的市场份额至今还在涨。Codd 在 1970 年用数学奠定的大厦 [1],比任何一种具体的存储引擎都活得久。真正不朽的,从来不是某种技术实现,而是"用干净的抽象管理复杂的数据"这个思想本身。


参考文献

[1] Codd, E. F. (1970). A Relational Model of Data for Large Shared Data Banks. Communications of the ACM, 13(6), 377–387.(关系模型奠基论文,作者因此获 1981 年图灵奖)

[2] Chang, F., Dean, J., Ghemawat, S., et al. (2006). Bigtable: A Distributed Storage System for Structured Data. Proceedings of OSDI '06, USENIX Association.(Google Bigtable 论文,宽表模型与 HBase 的直接源头)

[3] DeCandia, G., Hastorun, D., et al. (2007). Dynamo: Amazon's Highly Available Key-value Store. Proceedings of SOSP '07, ACM.(Amazon Dynamo 论文,AP 取舍与最终一致性的工程范本)

[4] Gilbert, S., & Lynch, N. (2002). Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services. ACM SIGACT News, 33(2), 51–59.(CAP 定理的证明)

[5] Hellerstein, J. M., Stonebraker, M., & Hamilton, J. (2007). Architecture of a Database System. Foundations and Trends in Databases, 1(2), 141–259.(数据库系统架构的权威综述,Now Publishers)

[6] Stonebraker, M., & Çetintemel, U. (2005). "One Size Fits All": An Idea Whose Time Has Come and Gone. Proceedings of the First Biennial Conference on Innovative Data Systems Research (CIDR '05).("专用数据库取代通用数据库"的著名预言)

[7] Stonebraker, M., & Hellerstein, J. M. (2005). What Goes Around Comes Around. In M. Stonebraker & J. M. Hellerstein (Eds.), Readings in Database Systems (4th ed.), MIT Press.(数据库五十年技术轮回的历史回顾)

[8] Corbett, J. C., Dean, J., et al. (2012). Spanner: Google's Globally-Distributed Database. Proceedings of OSDI '12, USENIX Association.(NewSQL 技术标杆论文)

[9] Wang, J., Yi, X., Guo, R., et al. (2021). Milvus: A Purpose-Built Vector Data Management System. Proceedings of SIGMOD '21, ACM.(向量数据库代表性论文)

[10] Silberschatz, A., Korth, H. F., & Sudarshan, S. (2019). Database System Concepts (7th ed.). McGraw-Hill Education.(数据库经典教材,ACID/事务/并发控制/范式的系统论述)

[11] Pavlo, A., & Aslett, M. (2016). What's Really New with NewSQL? SIGMOD Record, 45(2), 45–55.(NewSQL 概念的系统梳理)

[12] O'Neil, P., Cheng, E., Gawlick, D., & O'Neil, E. (1996). The Log-Structured Merge-Tree (LSM-Tree). Acta Informatica, 33(4), 351–385.(LSM 树原始论文,宽表数据库存储引擎的理论基础)

[13] Ramakrishnan, R., & Gehrke, J. (2003). Database Management Systems (3rd ed.). McGraw-Hill.(一部经典教材,查询处理与存储结构章节)

[14] Kleppmann, M. (2017). Designing Data-Intensive Applications. O'Reilly Media.(现代数据系统设计的入门书,中文版《数据密集型应用系统设计》,B 树/LSM/一致性/分布式事务的对比分析写得很清楚,想深入了解的话值得一读)