NoSQL现在很流行。我看的简历十之八九都写着熟悉NoSQL,但是很少有人能把NoSQL背后的细节说清楚,甚至NoSQL中的No是什么意思。很多人都弄错了。图片来自Pexels这个No并不是Not的意思,而是NotOnly的缩写。不得不说,这个缩写真的是骗人的,光是字面意思应该谁也猜不出来。而且即使解释为NotOnlySQL,还是有点云里雾里,不是很准确。因为SQL的全英文是StructuredQueryLanguage,也就是结构化查询语言的意思。它可以被认为是一种特殊的编程语言,但“不仅仅是SQL”是什么意思呢?实在是比较费解,所以我们不能从字面上去理解,需要从实际的应用场景中去理解。SQL的应用场景是关系型数据库,比如我们常用的Oracle和MySQL,都是关系型数据库。我们在了解数据库的时候,往往会从表的结构开始了解。数据库一张一张地存储表格。表由数据行组成,每一行数据都有固定的字段。这一点我想大家应该都非常熟悉了。就算你没有学过数据库,或者像我一样还给过老师,也应该或多或少有印象。但是为什么叫关系数据库而不叫表结构数据库呢?因为在数据库中,关系比表结构更重要。表结构只是一种形式,数据库中最核心的设计理念其实就是关系。这就是为什么我们学习数据库需要从ER图开始,而不是讲数据库的使用方法或者SQL语言的细节。如果你不明白这句话的意思,没关系,我们暂时搁置一下,最后回到这个话题。问题来了,我们知道常用的SQL数据库是关系型数据库,那么NoSQL代表的数据库是什么呢?关于NoSQL的概念,我至少看过两种说法:非关系型数据库,文档型数据库,个人理解的时候感觉这两种说法都不是完美的,但是相比之下第二种明显好一些,因为第一种根本不向我们提供任何信息。文档库这里的文档并不是指我们通常理解的文档的意思,而是指数据存储的结构和核心逻辑。一个生动的例子和大多数技术概念一样,NoSQL也比较晦涩难懂,很难用文字描述清楚。所以我决定举一个大家耳熟能详的生动例子——万能的X宝。下面是Xbao中商品详情页的图片(随意选择,不是广告,如有巧合请支付推广费):这张图大家应该不陌生吧。在我们平时的网购活动中,想必见过很多次。看起来有点眼花缭乱。我们对以上内容进行了抽象和简化,画了一张草图。大致是这样的(确实有点马虎):也就是说,我们大致将一个商品详情页展示的内容分为三部分:商品图片商品的一些介绍解释用户的评论商品的设计各大电商的详情页大同小异,部分细节可能有所不同,但整体模块相差不大。为了简化问题,我们假设商品详情页需要关联图片信息、文字描述和用户评论三张表。事实上,这种划分并不十分科学。例如,文字介绍和商品图片都可以存储在商品详情页的表中。比如除了这些信息之外,还有商品的销售信息,比如库存、价格、当前折扣、活动等等,但是这些和我们最后的结论关系不大,可以通过这种简单的方式来理解。按照上面的划分方式,我们应该是根据itemID来查询商品的图片、文字和评论信息,表面上当然是没有问题的。但实际上,这是有问题的。问题是这些数据不是一对一的关系,而是一对多的关系。例如,页眉上显示的图片往往不止一张,文字描述也不止一段,用户评论也不止一条。如何解决这个问题呢?你可能会想出一个解决方案。这并不难。我们将itemID字段添加到img、text和comment表中。我们查询的时候,用itemID来关联,不就可以了吗??当然你可以这样做。假设你是负责这个项目的程序员。当你更新上线成功后,产品对你提出了新的需求。她说我要在文字介绍和用户评论中展示图片。虽然一开始系统不是这样设计的,但是我无所谓,我就是需要,马上。你翻了翻白眼,冷静了半天,想了想,最后想到了两个解决方案:第一个解决方案是在当前图片表中增加一个字段,判断图片的目的是为了展示详情页或者评论页展示,后面要添加的评论页的文字介绍和图片仍然存放在这个表中。第二种方案是重建一张新表,专门用来存放评论页和描述页的图片。第一种方案的好处是我们不需要新建表,避免了表冗余。如果每一个需求都需要新建一张表,对于后续的维护来说显然是一个巨大的负担。但是它的缺点是我们需要批量修正所有之前的数据,因为之前的数据中没有source字段。当然,你也可以不加这个字段,直接用ID来区分,但这不符合规范,必然会留下安全隐患。第二种方案的优点是操作简单,不需要改变之前的数据,安全隐患较小,但问题是需要占用新的资源,利用率低。因为详情页的一些图片和置顶的图片是可以共享的,如果分开存放的话,需要存放多份。这两种方案各有优缺点,看起来都还不错,但是都有一个共同的缺点,就是增加了当前系统和查询的复杂度。一是增加查询时传入的字段,二是发起额外的查询。无论选择哪一个,系统都会变得越来越复杂。之后,一个用户请求会带动几十个联动请求,拼凑出完整的数据。现在Xbao最新版详情页的商品介绍都是图片显示,没有文字,可能是这个问题驱动的。让我们回顾一下这个例子。为什么我们的查询复杂,其实跟数据库的核心概念有关。存储在关系数据库中的数据是一种关系。在这个问题中,我们在查询一个详情页的时候,需要查询商品和图片的关系,商品和描述的关系,商品和评论的关系,评论和图片的关系,等等。也就是说,我们最终看到的页面其实就是这一系列关系绞在一起的结果。每个查询的背后都是一个分解和合并关系的过程,所以会很复杂。文档型数据库我们刚刚看了关系型数据库在电商场景下的问题,下面我们来看一下文档型数据库在相同场景下的性能表现。与关系型数据库不同,文档型数据库存储的核心是文档。当然这里的文档不是指我们通常意义上的文档,而是Json或者XML格式的数据。在目前的NoSQL数据库中,Json类型的数据是比较常用的。我们也以刚才的详情页为例,看看这个数据在NoSQL数据库中是如何存储的:{"itemID":123,"itemName":"iPadPro","topImgs":["imgs1.path","imgs2.path"],"desc":[{"text":"iPadPro","img":""},{"text":"2020newversion","img":"imgs1.path"}],"comments":[{"userName":"chengzhi","comment":"goodproduct","imgs":["imgs3.path","img4.path"]}]}你看,在文档数据库中我们可以得到刚才的复杂数据,需要经过多次查询和一系列处理后才能拼在一起。我们可以通过itemID一键查询得到。单纯从使用上来说,比关系型数据库方便很多,但也不是没有缺点。其中一个很大的问题是我们将所有的数据直接存储在文档中,这一方面造成数据冗余,另一方面限制了可扩展性。比如同一商户下的同类商品不能共享图片,必须存储多份,造成空间浪费。再比如,如果我们要支持用户修改之前的评论,那会很麻烦,因为我们要找到所有存储用户评论的文档进行修改(假设其他场景也用到用户评论),这就是经常跨系统而且很不方便。这个问题并非无解。比如我们可以用一个ID来代替文档中存储的具体数据。比如Comment不再存储具体的图片和评论信息,而是存储一个评论ID,然后在使用的时候进行关联。由于文档数据库的结构,对关联的支持不好,我们经常需要根据ID手动查询来模拟连接,这也会带来额外的开销。还有一个小缺陷就是在文档数据库中,我们访问数据的路径变长了。比如在商品评论中添加我们要获取第二个item中的第一张图片。我们需要写成item['comments'][1]['imgs'][0],但是在关系型数据库中,这样更方便,因为图片是直接通过关系查询得到的。除此之外,NoSQL数据库的开发周期比MySQL等更成熟的关系型数据库要短得多,所以支持的特性也相对较少。小结通过一个例子,我们形象地比较了关系型数据库和NoSQL数据库的区别。为什么我们学习数据库要从ER图入手,而不是直接学习数据库的原理和使用方法呢?我想理解了上面的例子之后,看这个问题应该容易很多。因为关系型数据库的核心逻辑是存储关系,所以使用规范、各种技术、特性本质上都是围绕这个核心展开的。如果我们在没有Get到这种程度的情况下使用数据库,很容易误入歧途,这就是多少不可思议的操作的来龙去脉。比如有人把前端页面的代码存入数据库,比如把ID拼接成字符串存多个值等等,这也说明经典教材的内容没有废话,每一章有其预期的功能。所以,当我们觉得有些内容没用的时候,可能不是教材写错了,而是我们没有理解好。作者:梁唐编辑:陶家龙来源:转载自微信公众号TechFlow(ID:techflow2019)
