数据库的黎明:当数据还在“流浪”时
1959年,IBM波士顿实验室的灯光彻夜未熄。一群工程师围在一台IBM 1401计算机旁,机器轰鸣,纸带穿孔的声音如同老式打字机的节奏。他们正在处理一份来自美国人口普查局的紧急订单:数百万张人口登记卡片需要在六个月内完成数字化录入与统计。没有数据库,没有SQL,甚至没有像样的存储设备——只有一箱箱穿孔卡片、一堆堆磁鼓,以及一份份散落在工程师桌面上的COBOL程序草稿。项目负责人约翰·麦卡锡后来回忆道:“我们不是在管理数据,而是在与数据搏斗。每一张卡片都可能丢失,每一个程序都可能因数据格式微调而崩溃。数据,在那个年代,是真正的‘流浪者’。”
这一幕,正是20世纪50至60年代数据管理的缩影。在关系型数据库诞生前的十余年里,数据如同没有户籍的游民,漂泊于文件系统、卡片存储与早期数据模型之间,经历着一场漫长而痛苦的“黎明前夜”。本章将带你回到那个穿孔卡片与磁鼓记忆体的时代,通过IBM、通用电气、RCA等公司的真实决策现场,揭示数据如何从“无序流浪”走向“有序归家”的艰难旅程。我们将看到,数据独立性(data independence)的缺失如何让应用程序如履薄冰,数据冗余(data redundancy)如何成为系统维护的噩梦,以及层次模型(hierarchical model)与网状模型(network model)如何在挣扎中试图建立秩序——却最终因其自身的复杂性,为关系模型的横空出世铺平了道路。
---
1.1 卡片与纸带:数据管理的“石器时代”
在晶体管计算机普及之前,数据管理的物理形态是具体而微的。20世纪50年代,企业数据的“活体”是穿孔卡片(punched cards)——标准的IBM 80列卡片,每一行孔洞代表一个字符,每一列代表一个数据字段。一个大型企业的客户信息、库存记录、工资单,可能占据整整一个仓库的卡片箱。数据“处理”意味着将卡片塞入制表机(如IBM 405),通过机械插杆读取孔洞,在另一叠卡片上打印汇总结果。这与其说是“计算”,不如说是“机械统计”。
1.1.1 文件系统的兴起与本质缺陷
随着电子计算机(如UNIVAC I、IBM 650)的引入,穿孔卡片被输入机器,数据以文件(files) 的形式存储在磁鼓(drum)或磁盘(disk)上。此时,操作系统提供的文件系统(file system) 成为数据管理的唯一基础设施。在文件系统中,数据被组织为具有名称的字节流,应用程序通过低级I/O指令(如`READ`、`WRITE`)直接访问这些文件。
从表面看,文件系统提供了“存储”能力,但其设计初衷是为程序服务,而非为数据服务。这导致了三个根本性缺陷:
1. 数据与程序的紧耦合(Tight Coupling):文件的结构(记录长度、字段顺序、编码方式)被硬编码在应用程序中。若需修改数据格式(如将员工ID从5位扩展到6位),必须重写所有相关程序。数据结构的任何变化都会引发应用程序的“海啸式”修改。 2. 数据冗余(Data Redundancy):由于缺乏统一的数据视图,不同部门往往独立创建自己的文件。销售部门可能有`CUSTOMER.MASTER`,财务部门有`CLIENT.LIST`,两者内容高度重叠却格式不一。这不仅浪费存储空间(在磁鼓时代,存储极其昂贵),更导致数据不一致(data inconsistency)——同一客户在两个文件中地址不同。 3. 数据孤立(Data Isolation):文件是“信息孤岛”。要生成“按地区统计的销售业绩”报告,程序员需手动编写程序,分别读取销售文件、客户文件、地区代码文件,在内存中自行关联(join)数据。这过程繁琐、易错,且无法被其他程序复用。
```mermaid graph TD A[应用程序A] --> B[文件系统<br/>(硬编码结构)] B --> C[物理文件:<br/>customer.dat] A --> D[硬编码格式:<br/>ID(5), Name(20), ...] E[应用程序B] --> B E --> F[硬编码格式:<br/>CustID(6), FullName(30), ...] C --> G[数据冗余与不一致] B --> H[无数据独立性<br/>程序依赖物理结构] ```
*图1-1:文件系统下的数据孤岛与紧耦合。应用程序直接依赖文件物理结构,导致冗余、不一致与脆弱性。*
这种“每个程序自带数据格式”的模式,在业务简单、数据量小时尚可维持。但随着企业规模扩大、数据处理需求激增,其弊端呈指数级放大。数据独立性(data independence) 的缺失成为最致命的伤疤:应用程序深陷于数据的物理存储细节(如块大小、索引方式),无法与数据的逻辑视图分离。一次存储设备升级或文件重组,都可能引发大规模程序崩溃。
1.1.2 COBOL:标准化尝试与“新瓶装旧酒”
1959年,一个旨在解决程序可移植性的会议召开,催生了COBOL(Common Business-Oriented Language) 语言。COBOL引入了`FILE SECTION`和`DATA DIVISION`,试图以声明式语法描述文件结构:
```cobol DATA DIVISION. FILE SECTION. FD CUSTOMER-FILE. 01 CUSTOMER-RECORD. 05 CUST-ID PIC X(5). 05 CUST-NAME PIC X(20). 05 CUST-BALANCE PIC S9(7)V99. ```
这看似是进步——结构被集中声明。然而,COBOL的`FD`(File Description)条目描述的是物理文件的布局,而非数据的逻辑关系。它只是将硬编码的格式从程序语句移到了数据部,并未解决根本问题:数据本身仍是无模型的字节流,应用程序仍需自行处理记录间的关联。COBOL标准化了“如何读一个文件”,却未触及“数据意味着什么”。文件系统时代的数据,依然在流浪。
---
1.2 层次与网状:建立秩序的早期尝试
到了60年代中期,企业数据处理进入“大规模时代”。航空公司需要实时订票(如SABRE系统),银行需要处理海量交易,制造业需管理复杂物料清单(BOM)。文件系统的脆弱性暴露无遗。 industry 迫切需要一种能描述数据间内在联系的系统,以实现数据共享(data sharing) 与数据完整性(data integrity)。这催生了两种早期数据模型:层次模型(hierarchical model) 与网状模型(network model)。
1.2.1 层次模型:树状结构的秩序与僵化
层次模型将数据组织为树形结构(tree structure),每个节点代表一个记录类型(record type),节点间通过父子关系(parent-child relationship) 连接。最著名的实现是IBM的信息管理系统(IMS, Information Management System),诞生于1966年,最初为阿波罗登火计划服务,后成为IBM大型机的核心产品。
在IMS中,数据被严格定义为树。例如,一个“公司”记录可以拥有多个“部门”子记录,每个“部门”又拥有多个“员工”子记录。访问路径是预设的:从根节点(公司)出发,沿着树分支逐层下探。这带来了两个“礼物”:
- **数据完整性**:父记录存在是子记录存在的前提(如不能有员工属于不存在的部门),天然支持**参照完整性(referential integrity)**。
- **访问效率**:沿着预定义的路径访问,性能极高,适合已知查询模式(如“查询某部门所有员工”)。
然而,层次模型的代价是僵化。树形结构强制要求“多对一”关系:一个子记录只能有一个父记录。但现实世界充满多对多(many-to-many) 关系。例如,一个员工可能参与多个项目,一个项目也有多个员工。在纯层次模型中,这需要引入冗余(如将项目信息复制到每个员工记录)或创建复杂的“虚拟子记录”,导致结构不自然、查询迂回。数据独立性虽有所提升(应用程序通过逻辑记录名访问,不直接涉及物理存储),但逻辑数据独立性(logical data independence) 依然脆弱——若树的结构需调整(如增加新的父子关系),相关应用程序可能需重写。
```plantuml @startuml hide empty members skinparam componentStyle rectangle
package "层次模型 (IMS)" { [公司] as company [部门] as dept [员工] as emp
company "1" *-- "N" dept : 拥有 dept "1" *-- "N" emp : 包含 }
note top of company 根节点,唯一入口 访问路径固定:公司→部门→员工 end note
note right of emp 问题:员工参与多个项目? 方案:冗余存储项目信息,或 引入“项目”作为部门的子节点 (不自然) end note @enduml ```
*图1-2:层次模型(以IMS为例)的树状结构。强制的父子关系导致多对多关系难以自然表达。*
1.2.2 网状模型:更自由的网络,更复杂的迷宫
为弥补层次模型的僵化,网状模型(network model) 应运而生。它允许记录类型之间形成图结构(graph structure),一个子记录可以有多个父记录。其核心标准是CODASYL(Conference on Data Systems Languages) 模型,由1969年的DBTG(Data Base Task Group) 报告正式定义。通用电气的IDMS(Integrated Database Management System) 是早期商业实现代表。
在网状模型中,数据被组织为记录集(record sets),记录间通过指针(pointers) 相连。应用程序使用导航式(navigational) 访问:先定位一个“锚点”记录,然后通过`GET-NEXT`、`GET-PRIOR`、`GET-OWNER`、`GET-MEMBER`等命令,沿着指针在记录间“漫游”以找到目标。这提供了极大的灵活性——多对多关系可被直接建模(如“员工”记录与“项目”记录通过“参与”集合连接)。
然而,这种自由的代价是复杂性爆炸:
- **应用程序负担沉重**:程序员必须清楚数据的物理连接路径,手动编写导航代码。查询逻辑被“如何走迷宫”淹没,业务意图(“找出所有参与项目X的员工”)被低级指针操作掩盖。
- **数据独立性名存实亡**:虽然逻辑模式(schema)与物理存储分离,但应用程序仍高度依赖**存取路径(access path)**。若需优化性能而改变指针结构(如添加新索引),应用程序代码可能需同步修改。
- **系统复杂性与成本**:DBMS需管理复杂的指针网络、并发控制与恢复机制。IDMS等系统成为庞大、昂贵且难以驾驭的“巨兽”。
```mermaid graph TD A[应用程序] --> B[CODASYL/网状DBMS] B --> C[逻辑模式<br/>(记录集与集合)] C --> D[物理存储<br/>(指针网络)] A --> E[导航式API] E --> F[“找到部门D01的经理”] F --> G[1. 定位部门D01记录] G --> H[2. 沿“管理”集合<br/>获取员工记录] H --> I[3. 沿“拥有”集合<br/>获取项目记录...] I --> J[代码冗长,<br/>业务逻辑 obscured]
style E fill:#e1f5fe style F fill:#f3e5f5 style I fill:#ffebee ```
*图1-3:网状模型的导航式访问。应用程序需手动遍历指针网络,业务逻辑与物理路径深度绑定。*
1.2.3 共同的挣扎:数据独立性的幻影
层次与网状模型相比文件系统,无疑是巨大的进步:它们引入了数据模型(data model) 的概念,提供了模式(schema) 的定义,实现了物理数据独立性(physical data independence)——即存储结构(如磁盘块组织)的变化,可以不改变逻辑模式,从而保护应用程序。
然而,它们都未能实现真正的逻辑数据独立性(logical data independence)。在层次模型中,若需在树中增加一种新的关系(如“供应商”与“部门”的关系),可能需重构整棵树,导致应用程序失效。在网状模型中,若需改变集合定义(如“参与”集合的成员记录类型),应用程序中导航该集合的代码同样需要调整。模式演进(schema evolution) 成为系统性难题。
更深层的问题是:这两种模型都将数据的访问方式(导航)强加给用户。程序员必须思考“如何找到数据”,而非“想要什么数据”。数据操作的声明性(declarative) 诉求被完全压抑。当通用电气的工程师在IDMS中为一个简单的“跨部门员工查询”编写数十行导航代码时,一个更根本的疑问悄然滋生:为什么不能让用户只说“我要什么”,而由系统决定“怎么拿”?
---
1.3 冗余与一致性:挥之不去的幽灵
在文件系统与早期数据库模型的漫长挣扎中,数据冗余与由此引发的不一致性如同幽灵,始终徘徊在企业信息系统的上空。其根源在于:缺乏单一、权威的数据源(single source of truth)。
1.3.1 冗余的代价:不只是存储
在1960年代,磁盘存储每MB成本高达数千美元。冗余直接意味着高昂的硬件成本。但更致命的是维护成本。想象一个制造企业的数据分布:
- **库存文件**:包含零件号、名称、当前库存量、仓库位置。
- **采购文件**:包含零件号、供应商、最近采购价、交货周期。
- **生产计划文件**:包含零件号、所需数量、计划开工日。
零件号是唯一公共键。若零件`P-100`的供应商在采购文件中从“ACME Corp”变更为“Globex Inc”,程序员必须: 1. 更新采购文件中的供应商字段。 2. 手动检查库存文件与生产计划文件是否也需同步更新(可能不需要,但需确认)。 3. 若漏掉任何一处,报表将显示同一零件不同供应商,导致采购决策混乱。
这种更新异常(update anomaly) 是冗余的直接产物。此外还有插入异常(insertion anomaly)——无法插入一个尚无库存的新零件信息,因库存文件是主文件;以及删除异常(deletion anomaly)——删除一个零件的最后库存记录时,可能误删其供应商信息。
1.3.2 尝试解决:冗余控制与数据字典
数据库系统的先驱们清醒地认识到冗余必须控制。IMS和IDMS都引入了数据字典(data dictionary) 或模式目录(schema catalog),集中存储元数据(metadata):记录类型、字段定义、关系等。这比文件系统前进了一大步——至少所有数据定义有了“官方登记处”。
然而,字典本身并不自动消除冗余。它只是文档化了冗余。在网状数据库中,若两个不同的记录集(如`EMPLOYEE`和`CONTRACTOR`)都包含“姓名”和“地址”字段,字典会分别记载,但系统不会阻止这种重复。数据完整性约束(integrity constraints) 的声明与 enforcement 依然薄弱。程序员需在应用层自行编写代码,确保跨文件的更新同步——这恰恰是最容易出错、最难以维护的部分。
真正的解决方案,需要一种能在逻辑层面统一描述所有数据及其关系的模型,并让DBMS自动负责冗余消除、约束 enforcement 与一致性维护。这要求模型具备关系(relation) 作为一等公民,而非指针或树分支。
---
1.4 时代的脉搏:关系模型的必然性萌芽
在层次与网状模型主导的60年代末,批判与反思已在学术圈悄然兴起。1969年,IBM圣约瑟西实验室的埃德加·科德(Edgar F. Codd) 开始了一项看似“脱离实际”的研究:如何用数学理论(特别是关系代数(relational algebra) 和谓词逻辑(predicate logic))来建模数据。
科德的出发点直指痛点:导航式访问的复杂性。他在1970年发表的经典论文《A Relational Model of Data for Large Shared Data Banks》中开宗明义:“现有的数据模型(指层次/网状)在三个方面存在不足:缺乏逻辑数据独立性、非程序员的用户难以使用、难以表达某些简单查询。”他提出,数据应被简单地视为元组的集合(sets of tuples),即关系(relations)。用户只需用声明式语言描述“想要的关系”,而无需关心“如何获取”。数据的逻辑结构(表)与物理存储(文件、索引)彻底分离,实现真正的逻辑数据独立性。
这篇论文最初在IBM内部未获重视——IMS正为IBM带来巨额利润。但其思想迅速在学术界引发海啸。1974年,加州大学伯克利分校的迈克尔·斯通布雷克(Michael Stonebraker) 等人启动了Ingres项目,探索基于关系模型的交互式查询语言(QUEL)。与此同时,IBM的唐·张伯伦(Don Chamberlin) 与雷·博伊斯(Ray Boyce) 在科德理论基础上,开发了SEQUEL(Structured English Query Language),即SQL的前身。
历史在此刻展现出其必然性:文件系统的混乱、层次/网状模型的复杂性、冗余与不一致的顽疾,共同孕育了对一个更简单、更声明式、更数学化的模型的渴求。关系模型并非凭空出世,而是对前十年“数据流浪”困境的理论升华与终极解答。它承诺:
- **单一逻辑视图**:所有数据均为关系(表),用统一的**关系运算(selection, projection, join)** 操作。
- **彻底的逻辑独立性**:模式变更(如增加列、拆分表)不影响现有查询,只需调整视图(view)。