青菜年糕汤

一周一篇,一期一会。以文会友,以友辅仁。

2019年10月2日

五十年前的一桩公案:数据库关系模型的流行史(上)

作者:青菜年糕汤

1970年,埃德加·科德(Edgar F. Codd)提出了数据库的关系模型,至今仍是数据库领域的事实标准。

这个模型在今天看来理所当然,以至于几乎没有文章讲清楚它是怎么成功的。

我希望通过谈一谈五十年前的这一桩公案,给今天在NoSQL与NewSQL浪潮中航行的我们一些启发。

上篇侧重于科普,熟悉关系型数据库的读者,可以直接跳到最后的结论,但请一定关注明天发布的下篇——那才是我的重点。

顺理成章的第一个问题是,在科德提出关系模型的时候,别人在用什么?

当时的明星产品是IBM的一款层次模型的数据库,叫IMS。

层次模型其实就好比文件夹,比如下面的结构就存储了两个小动物吃了多少食物的信息。

.
├── 青菜, 大柜子
│   ├── Zoom, 🐻, 10岁, 50千卡
│   └──  Zip, 🐿️,  9岁, 100千卡
└── 年糕, 小柜子
    └── Zoom, 🐻, 10岁, 200千卡

这样的存储方式最大的问题是信息重复。比如这里,在两个食物的“文件夹”下都存了“Zoom是只10岁的熊”这一信息。

重复会浪费存储空间,要知道五十年前人们还在用兆字节(MB)来衡量磁盘的大小。

同时重复就对信息的一致性提出了要求。要是加一条Zip吃年糕的记录,但在记录的时候把年龄记成了8岁,哪个才算是对的呢?

还会有人注意到,这样的表达无法记录尚未进食的小动物。这倒不难解决,加个假的(dummy)食物就好。

其时科德也是IBM的员工,深知IMS数据库的痛点。因而才有了今天的主角,关系模型的横空出世。

在关系模型中,上面的例子可以被表达为三个关系。

食物信息:
青菜, 大柜子
年糕, 小柜子

小动物信息:
Zoom, 🐻, 10岁
 Zip, 🐿️,  9岁

进食信息:
青菜, Zoom, 50千卡
青菜,  Zip, 100千卡
年糕, Zoom, 200千卡

这里的关系(relation)的说法来自集合论,指的是几个集合的笛卡尔积的子集。比如第一个关系就是(青菜, 年糕)×(大柜子, 小柜子)的一个子集。

在之前的层次模型的数据库,用户可以通写简单的命令,游历一个有层次的树,解决诸如“吃青菜的小朋友年龄分别是多少”之类的问题。

在关系模型中,这个问题涉及到了两个关系:小动物信息和进食信息。这时就得用到关系的一种操作——连接(join)。顾名思义,连接后就变成了如下:

青菜, Zoom, 🐻, 10岁, 50千卡
青菜,  Zip, 🐿️,  9岁, 100千卡
年糕, Zoom, 🐻, 10岁, 200千卡

然后只要找到有青菜的这几行的年龄即可。

说了这么多,虽然数据是不重复了,但怎么感觉新的模型比之前的更麻烦了呢?别急,接着看。

这时候用户有了个新的问题:“Zoom要去哪些储藏食物的地方?”。

如果用层次模型,这跟刚刚的问题完全不一样,需要写另外一个程序。但对于关系模型来说,就只要换成连接食物信息和进食信息两个关系就好了。

这个简单的例子其实还不足以体现关系模型的强大。当有更多的记录类型(如储藏地点信息、饲养员信息)时,用层次模型的程序员可能彻底陷入了混乱,关系模型还是以不变应万变。

(注意这里的模型更多指的是表达上的一种语言,而非实际的存储和访问形式。对此在下篇会有更详细的说明。)

这个新的模型为描述数据的结构、描述要解决的问题,提供了更强大、更灵活的表达能力。

科德应该是预料到,随强大功能而来的是,要实现它需要的更复杂的程序。

所以他指出,数据库的使用者应该只负责用关系模型描述要解决的问题(操作集合),具体用什么算法解决(操作数据),就交给数据库软件的开发者去操心吧。

从此数据库的使用者(后端程序员、数据库管理员)与数据库系统的开发者走上了不同的道路。

对于使用者来说,“傻瓜式”数据库成为了可能。他们可以专注于实际应用的开发,只要把数据库的问题抽象成关系和关系的运算,而不用纠结于底层的实现,无疑解放了生产力。

对于开发者来说,这对数据库的功能提出了更高的要求,但这同时也让它更加普适、更加好用,从而收获了更大的市场。有了钱就有更多人才和资源,这个领域也越来越精深。

这个故事这么写,就像是个无比正能量的辉格史。可实际上,关系模型并不是一开始就时今天的样子,在提出后也长期争议不断,十几年后才算是奠定了江湖地位。这就是下一篇的内容了。