编者按:此文原题为《What I’m Telling Business People About Why Relational Databases Are So Bad》,作者是Lance Gutteridge博士,文章是其《避免IT灾难》一书里面的内容,旨在向商业人士解释为什么关系式数据库是那么多企业系统问题的根源。为了向一般受众解释清楚,里面尽量避免了技术术语,但是技术受众从中看看需要向非技术人员解释些什么东西也能有所收获。
1970年代,IBM的Codd博士和Date博士提出了一种新型的数据库。这种数据库叫做“关系式”数据库。现在,我从来都没见过有任何迹象表明Codd或者Date曾经开发过任何真正的企业系统。如果有的话,我相信他们就会意识到关系式计算其实很不适合企业系统。反正又不需要让系统正常工作,他们所需要做的只是给出人为的学术例子就行了。但任何做过企业系统的人都会明白,真正的系统要复杂得多。写企业软件很难。这是计算机和人类行为的交叉,要想满足那些有时候会相互冲突的需求是很难的。
当时我正在给一个大型协会写软件,我记得人人都在谈关系式数据库。其实这些人都不知道关系式数据库究竟是什么,他们只是听说了这个术语并且认为它很好。当时正值计算机术语进入到普通商业场所之时。很多像《Byte》、《PC Mag》这样的杂志开始出现到报摊,其中一些故事还被报纸报道了。数据库的想法——把数据像文件柜一样存储起来的想法本来就挺吸引人。
然后就是“关系式”这个名字。有文章会告诉你它将如何让你利用关系在数据中遍历。类似于获得项目的项目经理所在部门的名字这样。但实际上关系式中的关系这个词指的是关系的数学概念,也就是一组数据。如果两个东西在一组内就可以说它们是相关的。实际上,关系的数学概念避开了相关联对象之间的连接的概念,而是把关系定义为一组相关的东西。这样的一个集合就是数学理论上的关系。这跟我们商业上的关系改变已经完全脱离,前者要求必定存在某种连接。如果某人告诉你一组对象对是关联的,你一般会寻求某种连接。这样设置一定是有什么道理的,我们往往会设法去寻找其中隐藏的因素是什么。模式寻找是人特有的一种本能。但是数学理论上的关系并不关心这个。哪怕只是偶然配对在一起的也会被视为一种关系。
我在纽约大学教数理逻辑的时候教过一门关系数学理论的课程。一些学生总是没有办法摆脱关系必须有某种规则的理念。这门理论的抽象概念的基础是他们所不能理解的。当我听说IBM提出了一种基于关系式计算的数据库时我吓了一跳。因为当时写了很多企业软件,我看不出把东西强制做成那么正式和抽象的结构会有什么好处。
在我看来这是复杂性和系统超限的罪魁祸首之一。不理解?且听我慢慢道来。
SQL注入
你有没有看过1920、1930年代时候的老电影里面某人读电报的场景?
那场景总是像这样的:
已与DANNY私奔句号(STOP)会在马德里度蜜月句号非常幸福句号
总是这样,一堆的句号。这有什么用?
要想理解这个,我们得回到布尔战争,这是发生在20世纪之交的一场英国人和布尔人为了争夺南非殖民地而展开的肮脏战争。这场战争除了给世界引入了集中营和堑壕战的概念以外,还是使用无线电报的第一场战争,部队在前线就能通过电报接收到命令。前线是非常肮脏的地方,在20世纪之交的前线尤其如此,因为被污泥和马粪溅得到处都是。因为担心纸上溅落的泥巴会被无人城逗号或者句号从而改变命令的意图,英国战争部命令所有的标点符号都需要拼写出来,比方说COMMA(逗号)。至于句号他们选择了用STOP来表示,这是一种更为英式的“句号”表示法。
电报码中将句号写成STOP成为了一种实践。当大家大声读出来的时候他们会本能地读成单词STOP而不是把它当作表示句子结束的句号。
你可能会问“这跟关系式数据库有什么关系?”给关系式数据库下达的命令采用的是用可读的文本形式。这种文本是一种叫做SQL(Structured Query Language,结构化查询语言)的语言。就像电报用单词STOP来断句一样,SQL也使用标点符号来分隔命令。就像电报一样,从效果上来说数据库以文本流的形式获取命令,每句命令之间会有个STOP。
看看这个例子:
UPDATE CUSTOMER_TABLE SET NAME=“John Smith” WHERE CUSTOM_NO=2333 STOP UPDATE …
这是一条SQL语句的命令,意思是更新客户记录2333,将名字改为John Smith。
现在请留意一下引号之间的文本比如“John Smith”。这是哪儿来的?
好吧则其实是某人通过浏览器填写表格输入的。填表单的那个人输入了“John Smith”然后点击提交按钮。网站代码将输入的引号之间的文本放到SQL语句里面然后发送给数据库执行。
现在假设这位客户心存恶意这样输入他的名字:
John” STOP DELETE CUSTOMER_TABLE STOP
如果网站把它放进SQL语句的话结果会变成这个样子:
UPDATE CUSTOMER_TABLE SET NAME=“John” STOP DELETE CUSTOMER_TABLE STOP” STOP UPDATE …
可能你已经看出来发生了什么变化了。这是一条将客户表中的名字设为John的命令,会被正常执行,但只有它就会执行那位恶意客户插入的下一条命令,这条会删除整个表的数据。记住,这条命令是由具备更新数据库的充分权限的任务来完成的。
这就是所谓的“SQL注入”。
SQL注入曾经是破解网站和入侵公司最具破坏性的一项技术。超过90%的主流网站渗透都是通过SQL注入来完成的。你只需要google一下就会看到一堆的数据泄露,受累的信用卡、被吸干的银行账号以及被暴露的个人信息高达数亿。
请在仔细观察一下这里发生的事情。数据库命令是文本形式的,而互联网用户通过web表格输入的数据会被并入该文本里面,这就给填表格的人愚弄数据库让后者不正确地解释命令创造了机会。
如果说在你看来这似乎是很愚蠢的做事方式的话,你的感觉完全正确。
这大概是有史以来做出的最愚蠢的,被使用得最广泛、代价最高昂的技术决定了。
这种软件就相当于核电站将控制室跟参观室设在了一起。
把两个东西拆开,也就是一个是命令,一个是来自表格的数据,然后再合并到一起,接着反复进行一场不要被愚弄把数据当成命令的实际部分的技术战争,这种做法根本毫无意义。
为什么一开始就要把它们揉到一起呢?
这就是一个非常糟糕的架构,这个架构要为全球各个组织数十亿美元的损失负责。
但是关系式数据库的故事比这还糟。
不要重复自己
假设你有一份实现软件的工作时间记录表(timesheet)。这份记录表有一个员工号。现在你要展示这份工作时间记录表,同时你想将员工姓名找出来。但姓名是在员工表上的,所以你得创建一条像这样的SQL语句:
SELECT EMPLOYEE WHERE EMPLOYEE.NUMBER EQUALS TIMESHEET.EMPLOYEE_NUMBER输入
这条语句会查询员工表将其与timesheet表进行匹配。你在这里做的其实是定义员工与timesheet的关系。你可以说他们是通过timesheet上的员工号连接在一起的。
记住那份timesheet表格已经描述给软件了。你说timesheet上的字段是员工号,所以基本上此时软件已经知道了这一关系了。但是现在你却要很麻烦地构建一条SQL语句然后发给数据库去执行,然后在返回一组表的行记录再从中选出你需要的信息。这完全是毫无必要!因为这份timesheet上与员工有关的信息已经跟软件沟通过了。采用关系式数据库导致这一信息被无视并且还得用一种完全不同的语言去重新定义这种关系。
计算机科学有一条原则叫做DRY(Don’t Repeat Yourself),意思是不要重复自己。其主要信条是每个不同的代码片段或数据仅在一个位置上出现。代码你应该编写一次然后在计算需要的时候进行调用。然而,这一原则也延伸到各个消除冗余性的地方。这是一条减少无序/复杂性的原则。相同的计算采用相同的代码消除了两个不同的实现走乱步伐的可能性。
用SQL表达已经在不同的表格上被表示过的数据之间的“关系”完全就是对DRY原则的违背。在软件里面信息是很宝贵的。信息被捕捉到之后就应该物尽其用,永远都不应该重新输入这一信息。你永远都不应该输入某个可以通过之前输入过的地方获取的东西。这么做就会制造出一条信息两个版本搞乱的可能性。
自从关系式数据库被提出以来,我就一直对为什么这种似乎非常怪异的架构还能存在感到困惑。
这就好像让你的档案室讲外语然后所有的指令都要用那门语言编写一样。
但情况其实还要糟糕。当你把那份timesheet保存进关系式数据库时,你必须把它分开,头信息放在一个表,所有分配工时给项目的明细记录又是一行行记录组成的另一张表。你必须把那张表拆开然后构建SQL来操作和存储它们。哦是的,如果你想按照同样的次序还原那张工时记录表的话,你还得给每一条明细记录行分配一个序号。当你想要要回那张表时,你得编写SQL指令将表格联合起来然后你还得从返回结果中选择所有的timesheet信息再拼凑成一份表格。
有人把这描述成每晚回家时你得把你的车拆卸下来,把部件挂到车库墙上,然后早上再重新组装才能开车。
这一切需要大量的额外编码才能让关系式数据库与面向对象软件这两个不同的世界能够对话。额外的代码意味着多余错误的可能性。
对象—关系式阻抗不匹配
如果这还不够糟,关系式数据库中数据的存储方式更适应的是1980年代的编程语言而不是现代的面向对象语言。在今天现代的面向对象语言中所有数据都得编码成这些原子的数据类型。
这有时候被称为“对象—关系式阻抗不匹配”。严重吗?扬声器与放大器之间的阻抗不匹配我还能理解,因为这是一种真正的物理现象。但这种背景下这个说法会制造技术上的含糊,其实应该用“一个真正愚蠢的架构的后果”来替代。
如果你想知道为什么企业系统经常会失败,这个不是全部单至少是主要原因之一。被迫用不同语言复制所有这些逻辑的必要性,以及用不同方式表达数据,给ERP系统制造了大量的混乱/困惑。
多年来由不同的人对一个老一点的代码库进行大量补充和修改,然后试图针对新情况进行定制,这一切会增加关系式数据库的复杂性,项目就会被置于严重风险之中。
话虽如此,关系式数据库无所不在倒是真的。多到有程序员从来都没见过其中类型的数据库,以为所有的数据库都是关系式的。
关系式数据库是有史以来败坏了一个行业领域的最糟糕的技术。将如此大量的额外混乱倾倒到系统里面是企业系统为什么失败会如此频繁的主要原因。
原文链接:
编译组出品。编辑:郝鹏程。