关系模型的实质

关系模型的实质
每当我批评 SQL,就有人说我其实不理解关系模型,说关系模型本身并没有问题,所以现在我就来分析一下什么是关系模型的实质。其实关系模型比起逻辑式语言,基本就是个衍生产物,算不上什么发明。关系代数其实对应逻辑式语言里面的一个很小的部分——它的数据结构及其基本操作,只不过关系模型有更大的局限性而已。所以学会了逻辑式语言的设计之后,你直接就可以把关系模型这种东西想出来。
每当谈到关系模型,总是有人很古板的追究它与 SQL,Datalog 等“查询语言”的区别。然而如果你看透了逻辑式语言的本质就会发现,其实“语言”和“模型”这两者并没有本质区别和明确界限。人们总是喜欢制造这些概念上的壁垒,用以防止自己的理论受到攻击。追究语言和模型的差别,把过错推到 SQL 和 IBM 身上,是关系式数据库领域常见的托词,用以掩盖其本质上的空洞和设计上的失误。所以在下面的讨论里为了方便,我仍然会使用少量 SQL 来表示关系模型里面对应的概念,但这并不削弱我对关系模型的批评。


关系模型与逻辑式语言
我们先来具体探讨一下关系模型与逻辑式语言的强弱关系。

“逻辑式语言”的另一个名字,其实叫做“关系式语言”(relational language)。
在数学上,“关系”(relation)意味着“没有方向”,意味着“可逆”。然而具有讽刺意味的是,所谓的“关系式数据库”并不具有这种可逆计算的能力。Prolog 和 miniKanren 其实是比 SQL 强大很多的语言,是真正的“关系式语言”,它们能够在比较大的程度上完成可逆计算。比如在 miniKanren 里面,你可以使用这样“查询操作”:如果 x+y 等于 10,y 等于 2,那么 x 等于几?很多 Prolog 和 miniKanren 可以表达的查询,SQL 没法表示。SQL 其实只能用于非常简单的,有“明确方向”的查询操作,基本上就像 Lisp 的 filter 函数。由于这些局限性,再加上很多其他的设计失误(比如语法像英语,组合能力弱),它只适合会计等人员使用,一旦遇到程序员需要的,稍微复杂一点的数据结构,它就没法表达了,而且会像 Prolog 一样引起诸多的性能问题。

因此,关系式数据库所谓的“关系”,比起逻辑式语言来说,其实是小巫见大巫。关系式数据库的表达能力,绝对不会超过逻辑式语言。关系式代数里面的“=”,join 等构造都是没有方向的。然而与逻辑式语言不同,这些“可逆操作符”在关系代数里的用法受到非常大的限制。比如,这些可逆操作都不能跨过程,而且关系模型并不包含递归函数。所以你并不能真正利用这种“无方向的代码”来完成比“有方向代码”更加强大的功能,大部分时候它们本质上只是普通程序语言里面最普通的一些表达式,只不过换了一种更“炫”的写法而已。

总是有人声称限制语言的表达力可以让语言更加容易优化,然而如果一个语言弱得不能用,优化做得再好又有什么用。关系模型的核心,其实是普通程序语言里面最简单的部分:表达式。如果缺乏控制结构和递归,这些表达式的能力只相当于最简单的计算器。经验告诉我,就算表达力这么弱的语言,很多数据库的编译器也不能把优化做好,所以这不过是为它的弱表达力找个借口。另外由于这种无方向的表达式让你在阅读的时候很难看清楚数据的“流向”,所以你很难理解这里面包含的算法。这种问题也存在于逻辑式语言,但因为逻辑式语言的表达力在某些方面强于过程式语言,所以感觉还不算白费劲。然而,关系模型有着逻辑式语言的各种缺点,却不能提供逻辑式语言最基本的长处,所以比起过程式语言来说其实是一无是处。


关系模型与数据结构
我们再来探讨一下关系模型与数据结构的关系。很多人认为关系式数据库比起数据结构是一个进步,然而经过仔细的思考之后我发现,它其实不但是一个退步,而且是故弄玄虚,是狗皮膏药。我做过好几个学期数据库理论课程的助教。当时我的感受就是,很多计算机系学生上了“数据结构”课程之后,再来上“数据库理论”课程,却像是被洗脑了一样,仿佛根本没有理解数据结构。经过一段时间的接触之后,我发现其实他们大部分人只是被数据库领域的诸多所谓“理论”,“模型”或者“哲学”给迷惑了。本来是已经理解的数据结构和算法,却被数据库理论给换成了等价却又吓人的新名词,所以他们忽然搞不明白了。

其实,关系模型的每一个“关系”或者“行”(row),表示的不过是一个普通语言里的“结构”(就像 C 的 struct)。一个表(table),其实不过是一个装着结构的数组。举个例子,以下 SQL 语句构造的数据库表:

CREATE TABLE Students ( sid CHAR(20), name CHAR(20), login CHAR(20), age INTEGER, gpa REAL )

其实相当于以下 C 代码构造的结构的数组:

struct student { char* sid; char* name; char* login; int age; double gpa;}

每一个 join,本质上就是沿着行里的“指针”(foreign key)进行“寻址”,找到它所指向的东西。在实现上,join 跟指针引用有一定区别,因为 join 需要查软件哈希表,所以比指针引用要慢。指针引用本质上是在查硬件哈希表,所以快很多。当然,这些操作都是基于“集合”的,但其实普通语言也可以表示集合操作。

所谓的查询(query),其实就是普通的函数式语言里面的 filter, map 等抽象操作,只不过具体的数据结构有所不同。关系式代数更加笨拙一些,组合能力弱一些。比如,以下的 SQL 语句

SELECT Book.title FROM Book WHERE price > 100

本质其实相当于以下的 Lisp 代码(但不使用链表,执行机制有所不同而已):

(map book-title (filter (lambda (b) (> (book-price b) 100)) Book)

所以关系模型所能表达的东西,其实不会超过普通过程式(函数式)语言所用的数据结构,然而关系模型却有过程式数据结构所不具有的局限性。由于经典的关系“行”只能有固定的宽度,所以导致了你没法在结构里面放进任何“变长”的东西。比如,如果你有一个变长的数组需要放进结构,你就需要把它单独拿出来,旋转 90 度,做成另外一个表,然后在原来的表里用一个“key”指向它们。在这个“中间表”的每一行,这个 key 都要被重复一次,产生大量冗余。这种做法通常被叫做 normalization。这种方法虽然可行,然而我不得不说这是一个“变通”。它的存在是为了绕过关系模型里面的无须有的限制,终究导致了关系式数据库使用的繁琐。说白了,normalization 就是让你手动做一些比 C 语言的“手动内存管理”还要低级的工作,因为连 C 这么低级的语言都允许你在结构里面嵌套数组!然而,很多人宝贵的时间,就是在构造,释放,调试这些“中间表格”的工作中消磨掉了。


这些就是关系模型所有的秘密。如果你深刻的理解了数据结构的用法,那么通过反复推敲,深入理解以上这番“补充知识”,你就能把已知的数据结构常识应用到所谓的“关系模型”上面,从而对关系式数据库应用自如,甚至可以使用 SQL 写出非常复杂和高效的算法。

另外有一些人通过关系模型与其它数据模型(比如网状模型之类)的对比,以支持关系模型存在的必要性,然而如果你理解了这小节的所有细节就会发现,使用基本的数据结构,其实可以完全的表示关系模型以及被它所“超越”的那些数据模型。说实话,我觉得这些所谓“数据模型”全都是故弄玄虚,无中生有。数据模型可以完全被普通的数据结构所表示,然而它们却不可能表达数据结构带有的所有信息。这些模型之所以流行,是因为它们让人误以为知道了所谓的“一对一”,“一对多”等肤浅的概念就可以取代设计数据结构所需要的技能。所以我认为它们其实也属于技术上的“减肥药”。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值