百科问答小站 logo
百科问答小站 font logo



如何看待王垠对数据库的理解? 第1页

  

user avatar   li-tian-yu-96-4 网友的相关建议: 
      

肤浅至极,给大家展示什么叫隔行如隔山。

作为一个研究数据库系统的人,明确告诉大家SQL的这层“非指令式”是现代数据库大多数性能提升的来源。所谓NoSQL快,只是在特定情况下而已。 比如把数据库当成一个不会丢失的hash table来用,那么K-V store显然会比支持ACID transactions和各种分析性事务的RDBMS要来的高效。

该文的一堆“就是C的struct, 就是指针, 为什么不能RPC”等等错误言论,本周放假无聊,逐条批驳。


讲到数据库系统, 首先要明确的是为什么需要用到数据库系统。 王垠开篇便开始详尽叙述数据结构和数据库的相同之处,如何“一个表本质上是一个数组”等等。 这是典型的本末倒置。 研究一个系统的时候,首先需要明确的是这个系统解决什么样的问题,给使用者提供了什么样的假设和便利,来把原来复杂的问题简单化,然后才研究以什么样的API或者编程范式来展现给使用者。后者的选择受到前者的限制。那么为什么需要一个数据库系统呢?现代计算机的存储技术很大程度上分为快速但可丢失的内存储(寄存器,多层缓存,DRAM)和缓慢但断电不丢失的硬盘,SSD等。数据库系统存储设计的本质是简化以及优化程序和外存储的互动

数据库系统设计的根本问题是,内存储快但是断电会丢失数据,外存储虽然相对可靠但是比内存储慢了一个数量级,不可能所有操作都直接读写硬盘(DRAM读写速度大约10-20倍于SSD, 对于HDD更加明显, 硬盘延迟是80倍左右)。而且由于现代计算机存储架构的原因,CPU不能直接操作硬盘,需要把硬盘数据先读入内存,然后进入缓存,然后读取到CPU寄存器上。同一份数据,在读写过程中自然地有多份copy,而且他们之间不自动同步。数据库系统的职责就是, 在这样的硬件环境下,保证性能并且保证不会丢失数据。

解决这个问题的方式,是由单独的数据库系统进程来管理其他程序与硬盘的互动。因为硬盘读写十分缓慢,数据库系统可以集中几次操作的量,一起写出,来保证速度。打一个比方就是一群人需要去图书馆借书(读数据)还书(写数据),但是从住处到图书馆的车程要十个小时,而且路窄易堵车。与其每个人不停开车来回全部堵死在路上,更有效地办法是指定一个人定期收集大家要借的书单和要还的书,定期往返。 数据库系统就扮演了这样一个角色。

那说到这里很多人就要问了,这活操作系统不就能做了吗,费那功夫干什么?

答案是操作系统只是一个开车的文盲司机,而数据库系统是一个会开车的图书管理员。车的后备箱里容量是有限的,与其一车一车地运,数据库系统知道,如果顾客要借一本新华字典,肯定不是为了闲着没事翻翻玩的,而是要查字典用。所以,如果顾客事先说,“我需要一本新华字典,查找“颰”字的读音”, 或者"我需要一本新华字典,数一下一共多少字是木字旁的", 数据库系统不需要借两本新华字典,只需要找到一本,并且算出答案,写在小纸条上给他们就好了。

同样在还书的问题上,因为操作系统是一个文盲,顾客只能让其“把这本书放到左边第二个书架的第三个空格上去”。顾客自己必须脑海里有一套图书管理体系,并且和其他顾客商量好。但是数据库系统是图书管理员出身,所以只要把书给他,他就可以自己把书放到正确的地方,方便下次查找。

那么接下来说数据丢失的问题。首先,停电,系统崩溃这种事情不是人能控制的。灾难可以在任何时候发生。就算我们的数据库系统根本不在乎性能,每次读写都直接走硬盘,依然会出现如下的问题:

操作:

  1. 找到写了“别人什么都不懂”的硬盘区域
  2. 覆盖写入“老王什么都懂”

写入的操作并不是一次完成的,字要一个个写。这个时候,在写入“老王”的时候,突然有人踢到了电源线,断电了。于是,硬盘上的数据是这样的:

“老王什么都不懂“

更不要说数据散落在内存和硬盘两地的状况了。数据库系统的职责就是,在系统恢复的时候,记住原来的操作,意识到现在的数据是不对的,并且在别人重新开始读取之前把数据改对了,要么改回“别人什么都不懂”,要么改成“老王什么都懂”。并且关系型数据库的能力不仅可以做到这个,还可以保证随意大小的操作,只有发生过和没有发生过两种结果,在任何时间点绝对不会出现发生了一半的状况。

具体的实现比较复杂(论文在此cs.stanford.edu/people/),在此不复赘述。


明确了数据库的根本目的之后, 一条一条批驳这篇文章的无知观点

可以说,“只告诉它 What,而不是告诉它 How”,只是一个不切实际的妄想,而且它并不是 SQL 首创的想法

从一个高级语言编译到低级语言,确实不是首创的想法,因为所有现代语言都是这样的。我写C的时候只告诉电脑要一个变量”int a“,不告诉它要存在内存的哪里,还是放在寄存器里,也不告诉它整数是什么,存几个字节,真是一个不切实际的妄想啊。

Prolog (miniKanren)的性能问题在 SQL 里面得到了部分的缓解,这是因为 SQL 对于基本的数据结构进行了“索引”

不,索引是要人去建的,SQL只是有一个优化器而已。

然而 SQL 的表达力也受到比 Prolog 更大的限制。很多 Prolog 可以表达的问题,SQL 没法表示。所以后来你就发现,SQL 其实只能用于非常简单的,适合会计等人员使用的查询操作。一旦遇到程序员需要的,稍微复杂一点的数据结构,它就会引起诸多的性能问题。

对于一个设计出来给会计等人员用的简单的查询操作的语言,听起来没什么不对的?

更要命的是,这种性能问题的来源是根本性的,不可解决的,而不只是因为某些数据库的 SQL 编译器不够“智能”。很多人不理解这一点,总是辩论说“我们为何需要 Java 而不是写汇编,也就是我们为何需要 SQL。”然而,把 Java 编译成高效的汇编,和把 SQL 编译成高效的汇编,是两种本质上不同的问题。前者可以比较容易的解决,而后者是不可能的(除了非常个别的情况)。

请多读论文少胡思乱想,这年头你的数据库不把SQL编译成汇编都不好意思出去和人打招呼。

我只举一个例子来说明这个问题。如果你需要迅速地在地图上找到一个点附近的城市,SQL 无法自动在平面点集上建造像 KD-tree 那样的数据结构。这是很显然的,因为 SQL 根本就不知道你的数据所表示的是平面上的点集,也不理解平面几何的公理和定理。跟 B-tree 类似,知道什么时候需要这种特殊的索引结构,需要非常多的潜在数学知识(比如高等平面几何),所以你肯定需要手动的建立这种数据结构

没有错,从来都是手动建的。SQL从来没有说过要帮你建。

你发现了吗,你其实已经失去了所谓的“描述性”语言带来的好处

没发现,毕竟建好的index用哪个,怎么用,是SQL优化器选的,依然不需要人来选。

这就是为什么你几乎总是需要手动指定索引的原因,而且这种索引需要数据库“内部支持”。你一次又一次的希望 SQL 能够自动为你生成高效的索引和算法,却一次又一次的失望,也就是这个原因。当然,你永远可以使用所谓的 stored procedure 来扩展你的数据库,然而这就像是我的 IU 同学们用 miniKanren 来实现 HM 类型系统的方式——他们总是先使用一种过程式语言(Scheme)来添加这种描述性语言的“相关特性”,然后欢呼:“哇,miniKanren 解决了这个问题!”而其实呢,还不如直接使用过程式语言来得直接和容易。

如果手动建一个index你都懒得建,你会去直接用过程式语言写上万行代码来保证ACID?

而其实,数据结构的理论,可以很容易的解释所有关系式数据库里面的操作

首先,废话,大家都是图灵完备的。其次,数据库的操作在加上transaction以后自带了对于断电等情况的应对,需要相对应加强代码的理论模型(pdos.csail.mit.edu/pape)来应对这样的语义。


每一个“foreign key”,其实就是一个指针

foreign key更接近于一个”reference“,而不是C语言里的pointer。有人可能要问这俩有啥区别,pointer本质上是一个东西的物理位置(内存地址0xdeadbeef开始的10个字节),而reference只是泛指找到某个东西的办法,可以通过物理位置,也可以通过别的方式。数据库里的foreign key不是pointer。数据库经常需要把数据更换位置来解决存储碎片化的问题,不可能用pointer来实现这个。

每一个join操作,本质上就是对指针的“访问”,找到它所指向的对象

看好了上面有多少种基础的join。(分布式数据库等会有更多种类)Join有没有foreign key都可以执行。这位老兄怕是只写过equi-join。

在实现上,join跟指针引用有一定差别,因为 join需要查“索引”(index),所以它比指针引用要慢。

如何实现一个Join是由数据库查询优化器(Query Optimizer)来决定的,基础的算法就有nested-loop join, sort-merge join, hash-join等好几种,也会有用索引和不用索引,甚至半用索引的区别。优化器会根据对于数据分布的理解挑选合理的join顺序和算法来实现每一个join。

至于用指针来实现join,你有种写一个给我看看。

所谓的查询(query),本质上就是函数式语言里面的filter, map等操作。只不过关系式代数更加笨拙,组合能力很弱。

SQL操作的data model是relational bag algebra (有别于Codd本来的relational algebra,基于集合), 和函数式编程可以转换,但是没有可比性,只能说各有优劣。你用functional给我写几个join看看,是不是还觉得SQL笨拙。

由于“行”只能有固定的宽度,所以导致了你没法在里面放进任何“变长”的对象

打脸啪啪响,现代数据库系统不是严格基于Codd的Relational Algebra的。那些1-NF, 2-NF, BCNF 的东西基本没有人理睬。

比如,如果你有一个数组,那你是不能把它放在一个行里的。

当然可以,刚刚贴的doc。

你需要把数组拿出来,旋转90度,做成另一个表B。从原来的表A,用一个“foreign key”指向B。这样在表B的每一行,这个key都要被重复一次,产生大量冗余。

如果是嵌套式的,那么不止A的foreign key,A的整个行都要重复好几次嵌套到B里去。更加不要说update一次A的值需要把所有持有A的B的值一起update的问题了。normalization在很多情况下更高效(多个B的值指向同一个A, A表加一列等等)。

使用基本的数据结构,其实可以完全的表示关系模型以及被它所“超越”的那些数据模型

之所以用数据模型,是因为需要实现ACID数据库的数据结构一点都不基本。您用C写完了自己的数据结构以后请加上一个buffer pool,WAL,disk-backed B Tree,并且写上所有高并发的优化来解决transaction的问题。

数据模型可以完全被普通的数据结构所表示,然而它们却不可能表达数据结构带有的所有信息

这句话大体是正确的,但是用数据模型的原因不就是因为你不需要在乎数据结构的信息吗?你写个webapp在乎Bw-Tree是怎么实现lock-free的吗?明明是好处,却被当作了问题。

最早试图冲破关系模型和SQL限制的一种技术,叫做“列模式数据库”(column-based database),比如Vertica, HBase等。

Vertica是使用SQL的RDBMS。Column-Store诞生是为了更有效地解决大数据分析的问题(OLAP workloads),节省硬盘I/O(就是所谓列压缩),运用SIMD等等。不知道这和逃脱SQL有什么关系。

很显然,每一个数组需要一个字段来表示它的长度N,剩下的空间用来依次保存每一个元素,这样你只需要一个key就可以找到数组里所有的元素,而不需要把key重复N遍。

这是什么压缩算法?把字典压缩理解错了?把run-length压缩理解错了?这怎么解决变长问题?

其实数据库的问题哪有那么困难,它其实跟“远过程调用”(RPC)没什么两样

RPC最著名的问题就是怎么在保证性能的情况下靠近"Exactly Once"的semantics。数据库相反,一定要”Exactly Once“,然后再来看性能能提升多少。完全不一样啊。

只要你有一个程序语言,你就可以发送这语言的代码,到一个“数据服务器”。服务器接受并执行这代码,对数据进行索引,查询和重构,最后返回结果给客户端

恭喜您发明了Spark。但是这怎么解决写入的问题?

如果你看清了SQL的实质,就会发现这样的“过程式设计”,其实并不会损失SQL的“描述”能力

牺牲的不是”expressiveness“,是”guarantee“。还记得那个只知道往左边第二个书架的第三个空格上放书的文盲司机吗?


user avatar   cheng-jun-6-50 网友的相关建议: 
      

列压缩列存储目的都是降 IO,因为数据在列上的相关性、相似性远大于行,而 query 从需求上来说是按列 fetch,按行去 load 就得读大量无关列得数据,浪费 io 且 cache 效率极低。

跟解决灵活性完全无关,更不是解决变长问题。

performance 监控或者报表类业务采用列压缩特性的数据库,压缩比能到100-1000,这意味2个小时的查询能弄到十几秒,因为读很少数据块就能命中一大堆需求数据。

列压缩的特性,Sybase 十几年前的数据仓库就有了。

Vertica 这种列压缩,读提高了千倍完全碾压普通数据库,写就得全部是追加写,并发低得要命。

数据库根本就是个天坑,没有万灵药,百灵药都没有。

要灵活,性能就狗屎。要性能,灵活就狗屎。

读快了,写就狗屎。写快了,读就狗屎。

并发高,吞吐就稀烂,吞吐高,并发就稀烂。


工程这种事,一定得愿意弄脏自己手才能体会,否则再聪明也碰不到实质。


user avatar   xing-jiankuan 网友的相关建议: 
      

看了一遍王垠的文章,觉得很是有趣,也顺便白话白话吧。

我同意这篇文章的很多观点,比如:

  • 我认同SQL的表达能力不如编程语言。比如没有window function的SQL做个group+window统计费死劲了;比如没有CTE的递归支持处理树状结构的数据几乎不可能;有时候SQL只表述what,不能指定how,的确会让查询优化更困难;
  • 我认同关系型数据库的结构远没有数据结构灵活;实际上我大概有一半的场景会把数据读到代码里用python或者js处理,因为我需要某种定制的嵌套结构而不是关系型那种一行行的数据;
  • 我认同prolog从工程的角度很多时候并不是很管用,逻辑这个问题动辄组合爆炸,从工程的角度大多数要走Heuristic的路子;而人工智能也绝对不是靠逻辑的,AI繁荣这么多年了,稍微了解的都知道基于概率的统计分析算法和神经网络的学习工具是这个领域真正的主力;
  • 我认同SQL不够智能,如果真的做GIS,是要开发者指定某种Geometry Indexing的。做不到自动。(但是一旦你设定了,在用位置信息查询数据时,数据库会“自动”的用Geometry Indexing帮你优化查询)。
  • ……

但是这篇文章最大的问题就是只讲缺点,但完全无视关系型数据库和SQL这么多年来对整个计算机科学和工程领域的巨大影响和作用,也不讲怎么解决问题

现在但凡是写个需要存储的程序基本上都要某种数据库,而且极大概率是“关系型数据库“。如果真如王垠所说,SQL和关系型数据库如此的不堪,就等价于承认所有SQL/关系型数据库的使用者都是傻X。为什么在关系型数据库从学术到商用长达几十年的历史上没有一个人比王垠更聪明,发现这个问题,并去解决它?虽然理论上的确有这个可能性,但是从直觉上看,这种情况发生的概率又有多少呢?

如果照他的思路推广,那么汽车就就不是我们现在看到的样子:不需要ESP和转向助力,直接控制轮子转向是最好的,况且ESP也无法“智能”的判断开车的人是想转向躲开障碍物,还是就是想玩漂移;不需要ABS和EBS,直接人来控制硬刹车是最好的;不需要自动挡,因为高手操控手动挡既省油,又能更快的加速;不需要车的外壳,因为车的本质就是4个轮子+发动机,本质上就是用来跑的;至于那些反光镜、雨刷、天窗、收音机、导航、空调、舒适的皮质座椅……,更加都是浮云,LOW的不值一提。

我觉得王垠的这篇和其他很多篇都有同样的问题,就是无视现实问题的复杂性,只强调某个特定角度(大概率是他熟悉的领域)的优势,而不管“易用性”、“成本”、“兼容性”、“抽象约定“、“优化空间”、“可运维性“……等等一系列现实中必须考虑的问题,甚至不考虑要解决的问题本身到底是什么。换句话说,他拿着一个锤子(应该承认,是个大锤子),看哪里都是钉子。

那么为什么SQL和关系型数据库可以长期占据统治性地位呢?我不是这个领域的专家,只从我看到的一些参考资料上简单说说。希望有专家帮助补充。

先从存储说起。王垠的文章中提到了struct会比关系型灵活的多。struct里可以有指针任意的指、可以任意嵌套。而关系型说白了就是一个大数组。问题是,struct的强大能力只有在可以很容易随机访问的内存里才能实现,而数据库一开始就要处理数据量远超内存的场景。数据库必须解决外存(早期主要是硬盘)的问题。硬盘因为要物理旋转,需要按照“簇”的方式访问,所以像struct那种指来指去的方法到了外存上基本上就会把性能表现降低数个数量级。上过计算机课的人都知道有些数据结构和算法适合内存,而有些适合外存(比如BTree)。关系型数据库最终选择了BTree。这就要求整个数据的结构必须大致得是“一行行”的,放在树的节点;树的结构本身用来做查询的优化。

因为整个数据的存储形式形成了BTree,整个查询的优化就可以围绕BTree来做。假如用struct,因为是任意的,所以就无法做优化。只有开发人员自己才知道数据结构,才知道怎么使用。比如丢给你个指针做入口,查找某个可能触达的数据,不知道内部结构的情况下,能做的也就是深度优先和广度优先去扫。

有些数据结构可以帮忙,比如常用的hash,list,stack,skiplist……。开发者可以用它们来优化自己的查询。就算是外存的相关逻辑,也可以抽象出来,用某种lib来做。问题是,这么做对于稍微复杂一点的场景,就会无比的麻烦,以至于在工程上不可行。你能体会到一个业务程序员在思考者各种折扣和返券的时候,还得考虑哪个数据存内存,哪个存到磁盘的哪个簇,然后再做查询优化的心情吗?这里还没提隔离的问题、原子性的问题、锁的问题、磁盘刷盘的问题……。没有靠谱数据库的支持,根本就无法在合理的时间deliver可用的代码,程序员需要同时花大精力和机器与PM双线作战。并不是每个人都是计算机科学的编码高手,熟悉算法、数据结构和存储,以及业务逻辑,都能在两边游刃有余。并不是每家公司都能雇佣多个像王垠这样的做编译的高级工程师写数据库的访问程序。

出现一个靠谱的、通用的数据库的前提必然是数据访问提供一个抽象和一套接口。而这个接口从使用者的角度,必然围绕着数据增删改查来做的,绝对不可能暴露底层的实现细节(比如对MyISAM表插入一条记录会加个表锁,而对InnoDB插就是另外一套基于MVCC的逻辑)。也许SQL并不一定是个完美的接口,但是是经过多少年的用户和数据库厂商磨合的结果。这个结果甚至逼迫着得很多现代非关系型数据库、分布式数据库必须提供类似于SQL的部分语法支持。有了SQL和关系型数据库,大量不那么熟悉数据结构、查询优化、存储的开发者可以加入到开发的队伍中,构建起大量的信息系统。整个产业就在这个过程中极大的发展。

在整个行业发展过程中,一些需求催生了各种各样的的优化的机会。有人抓住机会去提出新的数据模型和查询接口。比如:

  • 想内存数据库,上redis
  • 想高性能访问KV模型,有rocksDB
  • 想制作树状结构的数据,mongo你值得拥有
  • 想做图分析,有图数据库
  • 想要时序数据,有influxdb和Prometheus这种列存
  • 想做Data Warehouse,有GP、有大规模的并行计算引擎
  • 想做高性能海量数据存储,但是访问的方式相对简单,可以上Hadoop全家桶,Spark全家桶
  • 想做海量数据的KV,有HBase,Cassandra
  • ……

就连SQL自己也在演进,比如各大数据库增加了对json格式的支持,MySQL还搞了个X-API,弄得像mongo一样;Postgres中一列可以是复合的类型(类似于struct),也可以是数组类型。mysql和postgres还支持全文索引(按照王垠的讲法,是不是这时候得先用一套LSM tree库玩转sstable管理,然后再攒一个lucene做分词和倒排?)

根据实际需求,从上面这些备选中,总能找到几个东西组合在一起满足需要。如果还不够用,那就按照需求定制,就像polarDB,TiDB。但我相信,绝大部分的开发场景,弄个mysql,sqlite之类的就完事了,不需要特别仔细的优化。

遇到了SQL处理不了的,有价值的问题,去解决,去优化,去建模就好。光吐槽SQL这也不行,那也不行,没有任何卵用。

———

晚上迷迷糊糊写的,早上一看这么多赞。感谢支持。其实只是白话,不如这个问题下其他答案干货,推荐大家多阅读存储从业者的回答,可以学到更实在的东西。

之前我对王垠的印象还停留在很多年前他评价操作系统的时候,看到本题就去走读了他最近的blog。很多观点我都认同,也能学到一些东西,但不知道为啥他的很多文章一到推导结论时就会莫名跑偏。所以看看就好吧。我不会再写评论他文章的文了,DONOT REPEAT YOURSELF:-)

____

突然发现这篇文章是2014年的。不知为何这两天推到我时间线上了。如果垠神现在不这样了,那就当我在这里犯个傻吧~




  

相关话题

  将文件编译成某种代码,打印到A4纸上,这种想法是否可行? 
  为什么不能能向方法同时传入dynamic 类型,和lambda类型的参数? 
  非计算机专业,想刷leetcode,请问在此之前需要做什么准备? 
  如何理解编程过程中的逻辑严谨性? 
  汉语编程语言意义何在? 
  程序猿如何快速高效的改 bug?改bug都有哪些技巧? 
  编程代码不会,无人可请教,甚至没有标准答案,该怎么办? 
  CPython有GIL是因为当年设计CPython的人偷懒吗? 
  为什么上古编程语言(比如 COBOL)总喜欢把代码全部写成大写字母? 
  LeetCode 刷不下去怎么办? 

前一个讨论
孩子的梦想是成为一名科学家,如何纠正孩子的想法?
下一个讨论
怎么算修佛?





© 2024-11-15 - tinynew.org. All Rights Reserved.
© 2024-11-15 - tinynew.org. 保留所有权利