数据库表为什么不可以只设置一个主键,一个text类型,序列化存储对象,这难道不跟nosql差不多了?
啊……这个……看着一群人见山不是山的一阵胡扯,不由得有些尴尬……
首先,明确回答题主的问题:在你面对的工程问题面前,你的想法完全可行。
但是,这个世界上,是有很多完全不同的问题的……
想说清楚这个,我就得从头开始科普了。
一、关系型数据库背后是什么
关系型数据库背后是所谓的“关系代数”。
这个东西意思嘛……大致来说是这样的:对于一组二维表格格式的数据,在上面可以做的基本操作只有四种,也就是并、交、差、笛卡尔积,其它运算都可以通过基本运算的组合得到。
比如,选择、投影,其实是分别从行、列两个维度执行了求差/求交操作;
条件选择,也就是select的where子句,其实就是“求当前表格中某列取值都是xx的条目构成的子集”;带多个条件就是子集的子集,以此类推。
再比如,自然连接,其实是笛卡尔积之后,去掉两个表键值列取值不同的行(选择),再去掉第二个表的键值列(投影)。
1、以关系型数据为基础,再往上可以做什么
熟悉计算机技术或者有一定数学造诣的都知道,一旦一个系统的所有操作可以归结到很少的几种,那么我们就可以通过某种方式(算法)机械式推广、或者把一切复杂问题归结/化简到这些基本操作上面了……
这就是SQL(Structured Query Language结构化查询语言)。
对SQL来说,并交差笛卡尔积就是它的“汇编语言”或者“机器指令”;于是,用户只要表达出自己想对行列数据做什么,编译系统就可以自动帮他们翻译到机器指令。
这就使得SQL被称为“声明式编程”——你声明你需要什么,剩下的,让编译程序伤脑筋。
当然,这更多是从数学-使用层面上考虑的。
另一个层面呢,就是优化。
比如,内连接,它的确可以翻译成笛卡尔积之后按条件选择……但,笛卡尔积可是O(N^2)消耗!
幸好,数据库程序可以非常智能的识别出“你要做的是内连接”,于是它不先做笛卡尔积,而是先求交、后“连接”(也就是1对1的笛卡尔积),从而把消耗降低到O(N logN),其中N是查询涉及到的数据行数。
SQL编译过程,这个优化是重中之重。越优秀的数据库系统,它的复杂SQL语句的执行效率就越高——数据库系统之所以全球只有少数公司可以做,就是因为这个门槛实在不低。
总之,你看,你只要“声明”你想要什么数据,SQL就会自动帮你翻译成繁难的操作——甚至还帮你直接优化到最优!
2、以关系型数据为基础,再往下如何优化
前面提到,关系型数据其实就是二维表。
那么,二维表的特征是什么?
有很多行,每行可以叫一个记录;每个表又有若干列;所有记录在同一列的数据,都是同样的类型、同样的含义……
这样的数据该如何存储呢?
我们知道,HDD访问数据是需要寻道的,而寻道消耗非常大。
每次访问,先访问磁盘的文件分配表,找到根目录;然后找子目录……如此一次次访问,找到文件项,从中找到文件使用的链,最终定位数据所在扇区……
你看,这消耗,能小吗?
然后,你需要的资料,又在文件中的哪个位置呢?
这是不是……又是一连串的寻道?
别说HDD了,就是内存,你这样随机访问,它也吃不消啊。
更别说往里写了——旧记录删除了,新记录……能写到旧纪录所在位置吗?长度够不够?需不需要……把后面的100万条记录整体后移1个字节?
那么,关系型数据就有一个极其优良的特性,这就是它的每条记录是等长的——不等长的text/blob字段,它丢其他地方另外存储,通过数据项索引就好了。
既然每条记录等长,那么要访问第1000条记录时,它在哪里?
是不是只要fseek(dataStart + 999 * len(record)),然后fread就行了?
不仅如此。我们知道数据结构里面有个B树,还有个B+树——这两个数据结构可以说就是为“在磁盘存储定长记录”量身定做的。它充分利用了磁盘按扇区分块存储数据以及顺序读取效率最高等等特点,是最高效的磁盘信息管理方案。
总之,借助一系列的优化,我们就可以把“等长记录型数据的读写”优化到极致——文件inode在内存里,不用从头索引,磁头可以一次寻道就到文件头;然后利用B/B+树以及记录等长的特性,可以在极少几次寻道后直接访问到记录。
进一步的,还可以搞出针对定长记录定制的“(内存)缓存机制”,进一步提高数据访问的效率。
更进一步的,数据可靠性。
啊,用户A昨天买了个东西,今天卖家打算发货,他又给退了……
那么,怎么保证用户、卖家双方的一致性呢?怎么确保用户、卖家、银行三方账目清晰、没有丝毫含糊也没有任何人利益受损呢?
事务,锁……表锁、行锁……这些锁最好也可以自动管理,不需要用户干预……
这,又是一个超级大坑。实现难度不是一般的大——其中关于死锁以及它的发现和避免的研究论文甚至拿了图灵奖。
但,一旦实现了,我们用户用起来,那可是省心、放心……
毫不夸张的说,假设用户A买了个东西,在点提交按钮的一瞬间,有个疯子冲进机房,一斧子劈了机房出口路由器或者总电源箱、甚至一桶水泼了服务器……那么,只要硬盘没有损坏,这宗交易的状态仍然是清清楚楚、绝无含糊的。
哪怕用户自己都说不清楚,但数据库记录里面必须清清楚楚。
这是关系数据库事务性的基本要求——做不到的,可没人承认它有事务支持哦。
如果没有数据库,你知道该怎么写程序,才能达到这样的安全性、可靠性吗?
二、关系型数据库对使用者有什么要求
如你所见,关系型数据库要求记录项目数固定、数据类型、长度统统固定。
否则,你就别想享受前面提到的“贴心服务”了。
这就要求它的使用者具有足够的抽象能力,要对业务发展有预见性 ,从而确保数据格式几年、十几年甚至几十年不变——不变,但却能够“以不变应万变”……
因此,数据库设计是一个技术门槛很高的工作。
能入门,甚至能到殿堂级,那么数据库系统就是你最强有力的后盾——你只要存数据进去,需要对数据做什么操作,数据库系统都能自动给你全球最专业团队优化出来的、最高效的方案。
甚至,在专业用户手里,你也可以通过explain指令要求数据库展示它是怎么执行你敲入的SQL语句的、每个步骤消耗了多少时间,从而找出性能瓶颈。
如果它没有给你优化到最优,那么你或者可以提bug、要求数据库软件公司修改程序(如果你确信有它应该自动发现的更优方案的话),也可以修改你自己的数据库查询语句、想办法给编译器更多提示,让它换一种方式执行你的查询;你也可以通过增加索引、修改数据库表结构等方式,使得你的查询能够以最优化方式查询。
但如果不能入门……那前面提到的所有好处都与你无关;反而是,为了服务那些入门者甚至殿堂级使用者而特意设计的高级功能会干扰你、妨碍你,让你焦头烂额疲于应付:不是啊,我就敲错个标点,你怎么就敢这样对我!为什么我写个简单的日志都会遭遇锁!什么多看书?!那么厚一本鬼画符,谁看得懂!
三、为什么在google的业务面前,关系数据库不够用了
原因是:google是做互联网搜索的。
互联网搜索的特点是什么?
数据毫无规范。
你看我这篇文章,它符合哪个规范?哪有“起承转合”可言,对吧。
互联网网页更乱。文字,表情包,动画,视频……跟帖,反驳,引用……
总之,没人知道自己会得到什么样的数据。
然后,第二点,互联网网页数目超级多,而且还在爆炸性增长——信息爆炸嘛。
对比一下,中国有14亿人口,每人每年消费1000笔,14000亿条记录——是很多;但我们可以按消费地区、时间分表,很容易就能弄到若干个不超过1亿条甚至一百万条的表里、分散在若干服务器上。
你看,格式单一,容易分片,总体来说是很简单的事,大力出奇迹就行。
但网页呢?每年会有多少个网页?你每年在网上灌了多少水?
你看,超级多的数据(互联网每年新增内容是天文数字,哪怕以PB为单位)、数据毫无规范——是不是和传统的关系型数据库严重不搭?
甚至可以说,传统数据库绞尽脑汁对格式数据做的很多优化,对于这类非格式数据反而是有害的。
尤其事务、锁之类……纯粹是来添乱的。
四、为什么bigtable能够解决问题
因为bigtable并不要求存储于其中的数据是关系型数据。
它的设计思路是这样的:网页是一堆杂乱的文本(以及二进制数据);每个网页又有一个独一无二的URL……
那么,就把网页当普通的html文件存储吧——剩下的,让文件系统头疼去。搞这个,它专业。
然后,做好URL到文件的索引就行了,对吧。
你看,这样一改造,问题是不是简单粗暴就解决了?
而且,将来可以搞一堆服务器,网站A的数据存服务器A,网站B的数据存服务器B;网站C和D太小,一起存服务器C;网站E又太大,按年份存服务器E、F、G、H……
将来要查询了,是不是可以把查询请求分流到不同的服务器,让它们并行处理?
然后,进一步优化……
比如,keyA对应的数据才100字节,别存单独的磁盘文件了,浪费空间!让它和keyB、keyC等的数据凑一块,存同一个文件。又省地方又减少寻道次数,一次就全读进缓存了!
另外,同一个网站的数据经常同一批的被很多人查询,所以存同一台服务器并不是最佳选择……那怎么办呢?
搞个glusterFS(这是个开源仿造品,并不是google自己用的版本),自动把数据分成条块、存储在一大堆服务器上——然后做好数据的备份恢复工作,使得哪怕服务器意外坏掉了若干台,数据也不会丢失(甚至都不影响业务、不附加延迟)……
你看,轻松实现并行访问、透明扩展,于是速度,可靠性……自然都来了。
五、NoSQL的弱点
在处理海量的、非格式化的数据时,NoSQL的确很强大。
但,key-value这个抽象太简陋了,它没有为NoSQL系统的设计者提供任何保证。
不像传统数据库,它对数据本身有很多约束;这些约束确保它可以“基于最基本的几个关系代数操作,支持用户对表格型数据的一切需求”。
于是,传统数据的用户可以很省心。只要数据库结构设计好了,那么,他们已经有了一切。
——当然,关系代数本身并不“图灵完备”,所以不可能像图灵机一样万能。也就管管数据了。
而key-value呢?
你什么都得不到。
给出key,它给你value——甚至可以像源码管理系统一样,给你某个数据的历史版本。
但,到此为止了。它就是一个单纯的、带强大索引能力的数据存储系统而已。
想要得到别的,你只能自己写程序解决。
没错,它可以“支持”json……但对json的支持其实只能算是个“语法糖”……
不当“语法糖”做?那就是“退化”而不是“进化”了。
原因很简单,key-value中,已知key找value,这是极其重要的基础设施;如果你敢拿json当基础存储结构,那么,请问,你如何实现由key到value的查询呢?
从磁盘上读1000个扇区、把整个字符串读进内存、然后解析大json串、找到对应key吗?
你看,这就扯淡了。这效率没法看。
当然,它也可以用key-value结构去“模拟”传统关系数据库。
但问题是,这个“模拟”,它的存储效率、访问效率,相对于关系数据库,那都是一塌糊涂。
关系数据库专门搞了几个范式,确保从一个索引准确索引到全部相关数据;然后又把“一组相关数据”的读写访问到了极致……
你用key-value模拟关系数据库,请问,对一个有10个字段的表格,每个字段的主key它要存几遍?取一行数据时,它又得索引几次?
你能模拟其形,却模拟不了其神。
除非你给NoSQL数据库里面集成一个MySQL子模块,关系型数据就存关系数据库里面……
反过来说也对: 除非给关系型数据库设计一个NoSQL子模块,否则,它的基本结构就决定了,它玩不了海量无格式数据。
工程师眼里,约束,经常是福利而不是限制。
六、如何正确/错误使用SQL和NoSQL
一旦搞明白了根本道理,那么这个问题的答案自然不言而喻。
如何使用SQL?
仔细分析你的需求,然后精研数据抽象,把关系数据库的第一范式、第二范式、第三范式、BCNF范式玩的出神入化……最后,针对自己的需求、灵活运用自己的知识,搞出稳固、可靠、满足所有需求的设计。
能做到这个,你自然就不会遇到“动不动要改数据库”的烦恼、同时又能充分享受到关系型数据库查询精准、访问快捷、操作有事务支持等种种便利。
同样的,如何使用NoSQL?
如果你需要处理海量无格式数据、但需要基于数据内容做一些索引,那就用它吧。
但与之同时,你需要自己实现自己面对的所有需求点——如果数据其实是有格式的,那么,请发挥自己的聪明才智吧。
如何把NoSQL用成祸害?
啊,现在什么时代了!NoSQL大兴、SQL当死!
你要是个这样只会喊口号的傻冒……你已经是祸害了。
好好写你很有前途的PPT吧。
如何把SQL用成祸害?
哎呀我不懂什么抽象我也看不懂什么叫范式,反正你就给我存里面呗!一个字段不够就添加第二个,一直添加到col101、col102……col999!
什么?还是解决不了问题?
那……干脆直接搞成json,存text字段?
嗯……何不用NoSQL呢?
当然,一般这么瞎搞的,都不过是在玩具项目里面小打小闹——不搞玩具?你又不会啊。
所以,随便搞,无论NoSQL还是SQL,它们优化的都很好,禁得起你们祸害。多大点事啊。
但是,如果项目到了一定规模……其实我们一般是这样用SQL解决问题的:
Diagrams E-R图默认示例。一点都没改。
本以为还得自己画个图呢,哈哈。省心了。
总之,少喊口号。工程师是做实事的人,俺们讲究的是出手就精准解决问题,不多一分不少一分。
你对这些基础背后的东西了解的越多,它就越能帮你轻松搞定面对的问题;停留在表面,那么自然越用问题越多——甚至可能像俺吐槽过的某个投资上亿的项目一样,无声无息的死在数据库上。
有经验的工程师,他的设计总是“力透纸背”的。换句话说,他在设计表的时候,其实已经考虑好所有业务最终对应的集合代数操作都有哪些、能否得到最佳效率了——或者,决定使用NoSQL时,他脑子想的绝不会是“NoSQL很新潮很流行”,而是“我面前这个破任务好麻烦只能充分利用NoSQL、其他逻辑则只能‘我行我上’了”:没有这个亲自造轮子的觉悟,你最好别用NoSQL。
不能透过表面看到本质、对着点表象稀里糊涂见山不是山见山又是山的,多半是自己也没搞明白。