SQL和NoSQL数据库的区别

    作者:课课家教育更新于: 2017-05-05 09:38:13

      创新的背后往往会刺激痛苦。这一点在PDD(我们亲切地称为痛处驱动开发)软件开发领域尤为真实。从上世纪80年代以来,我们就都知道如何处理关系型数据——只要把数据放到关系型数据库管理系统(RDBMS)中,就可以使用SQL语句操作数据。然而,在过去几年来,我们的行业采纳NoSQL数据库的趋势在增长,数据不见得都在关系型数据库中存储了。

      诚然,在互联网上有成千上万关于选择SQL还是NoSQL的辩论。但是,这两者是不是必须站在对立面战斗呢?如果你选择一种或另一种,你知道为什么做选择,知道各自有何潜在益处吗?本文简要地讨论了SQL和NoSQL两种方法最常见的优点和缺点,包括简单的比较和开发者考虑的因素。像别的一些话题一样,这个问题没有对错,永远正确的经典答案依然是:具体问题具体分析。

      数据表VS.数据集

      关系型和非关系型数据库的主要差异是数据存储的方式。关系型数据天然就是表格式的,因此存储在数据表的行和列中。数据表可以彼此关联协作存储,也很容易提取数据。与其相反,非关系型数据不适合存储在数据表的行和列中,而是大块组合在一起。非关系型数据通常存储在数据集中,就像文档、键值对或者图结构。你的数据及其特性是选择数据存储和提取方式的首要影响因素。

      预定义结构VS.动态结构

      关系型数据通常对应于结构化数据,因为数据表都有预定义好的结构(列的定义),结构描述了数据的形式和内容。这一点对数据建模至关重要,你必须“第一时间先把结构定义好”。虽然预定义结构带来了可靠性和稳定性,但是已经存入数据的表结构要修改就非常痛苦了。另一方面,非关系型数据基于动态结构,通常适用于非结构化数据。非关系型数据可以很容易适应数据类型和结构的变化,因为动态结构本身就支持这一点。

      存储规范化VS存储代价

      关系型数据库的数据存储是为了更高的规范性,把数据分隔成最小的逻辑表(关系表)以避免重复,获得最精简的空间利用。虽然数据规范性会使数据管理更清晰,但它通常也会带来一点点复杂性,尤其是单个操作可能涉及多个关系表的时候,数据管理就有点麻烦。另外,更精简的空间利用通常可以节约宝贵的数据存储,但是在当今世界我们基本可以认为存储的代价(磁盘空间)是微不足道的。而非关系型数据存储在平面数据集中,数据经常可能存在重复。单个数据库很少被分隔开,而是存储成一个整体,这样是为了整块数据更容易读写。

      纵向扩容VS横向扩容

      SQL和NoSQL数据库最大的差别可能是在扩展方式上,要支持日益增长的需求当然要扩展。要支持更多并发量,SQL数据库是纵向扩展,也就是说提高处理能力,使用速度更快速的计算机,这样处理相同的数据集就更快了。因为数据存储在关系表中,操作的性能瓶颈可能涉及很多个表,这都需要通过提高计算机性能来客服。虽然SQL数据库有很大扩展空间,但最终肯定会达到纵向扩展的上限。而NoSQL数据库是横向扩展的。非关系型数据存储天然就是分布式的,NoSQL数据库的扩展可以通过给资源池添加更多普通的数据库服务器(节点)来分担负载。

      结构化查询VS非结构化查询

      关系型数据库通过所谓结构化查询语言(也就是我们常说的SQL)来操作数据。SQL支持数据库CRUD(增加,查询,更新,删除)操作的功能非常强大,是业界标准用法。非关系型数据库以块(像文档一样)为单元操纵数据,使用所谓的非结构化查询语言(UnQL),它是没有标准的,因数据库提供商的不同而不同。关系型表中主键的概念对应非关系存储中的文档Id。SQL数据库使用预定义优化方式(比如列索引定义)帮助加速查询操作,而NoSQL数据库采用更简单而精确的数据访问模式。

      事务性VS纯扩展性

      如果你的数据操作需要高事务性或者复杂数据查询需要控制执行计划,那么传统的SQL数据库从性能和稳定性方面考虑是你的最佳选择。SQL数据库支持对事务原子性细粒度控制,并且易于回滚事务。虽然NoSQL数据库也可以使用事务操作,但它们真正闪亮的价值是在操作的扩展性和大数据量处理方面。

      ACID VS CAP

      SQL数据库久负盛名的价值就是通过所谓的ACID属性(原子性,一致性,隔离性,持久性)保证数据完整性,大部分关系型存储供应商都支持ACID。我们的目标是支持隔离不可分割的事务,其变化是持久的,数据也保持一致状态。而NoSQL数据库是让你在CAP(一致性,可用性,分区容忍度)中的任意两项中选择,因为在基于节点的分布式系统中,很难做到三项都满足。

      数据VS大数据

      SQL数据库可以可靠地存储和处理数据,而NoSQL最大的优势是在应对大数据方面,也就是由我们社会或者计算机每天产生的大量非结构化的数据实体。NoSQL用无模式方式做数据管理,所以其横向扩展潜力是无限的,这可能是深度处理大数据捕获、管理、检索、分析和可视化的唯一有效途径。

      数据记录VS物联网和人联网

      关系数据库在关注数据规范化和保证性能的基础上精简存储。但是近年来,我们产生数据的速度远大于关系型存储能满足存储的能力增长。刺激数据如此迅猛增长的原因是:巨大量的用户数和物联网。连接到互联网的用户在成倍增加,在同步使用我们的应用。由于大量移动设备数据传感设备接入互联网,机器产生的数据量也大幅增加。因此企业必须寻求NoSQL技术及基础架构来处理持续涌入的半结构化和非结构化数据。

      内部部署VS云计算

      云计算现在已经无处不在了,它兼具SQL和NoSQL数据库的益处。云环境中的关系型存储通常是以服务形式提供的,是可复制、高可用性且分布式的,极大地提高了横向扩展能力。托管于云服务中的NoSQL数据库也天然享有自动分片的好处,可以阶段性地灵活弹性处理,集成高速缓存和巨大的计算能力来捕获、存储和分析大数据。

      付费VS开源

      有一种看法认为,SQL数据库大多数比较昂贵,而NoSQL数据库通常都是开源的。事实上,两种类型数据库都有开源的和商业的。常见的SQL数据库有微软公司的SQLServer,MySQL,SQLite,Oracle和PostGres。流行的NoSQL数据库有Couchbase,MongoDB,Redis,BigTable和RavenDB。

      映射VS本地化

      SQL和NoSQL数据存储的选择还取决于开发人员,尽管这个因素影响不大。采用面向对象编程语言的开发人员通常会同时操作一个或多个数据实体(包括嵌套数据、列表和数组的复杂结构),把数据传递给应用程序用户界面。要是讨论到底层数据库,事情就并不总是那么公平合理了。在关系型存储中,数据实体通常需要分成多个部分进行规范化,然后分开存储到多个关系型表中精简存储。幸运的是,这是一个长期存在的问题,大部分编程平台都有相应的简单解决方案,比如ORM层(对象关系映射)。ORM是位于关系型数据源和开发者使用的面向对象数据实体之间的一个映射层。然而,对于非关系型存储,不需要规范化数据,复杂数据实体可以整体存放在独立单元中。应用程序中使用的对象通常序列化为JSon串,存储在NoSQL数据库的JSon文档中。

      梳理好SQL和NoSQL数据库的技术差别,下面来看一下如何高效地将SQL数据映射到NoSQL存储系统中!

      通常来说,我们都知道:

      SQL数据库只限在单机上运行,但它提供了更强的事务管理、schema与查询功能。

      NoSQL数据库为了伸缩性与容错性的目的,放弃了事务管理与schema。

      而FoundationDB的SQL层结合了这两个方面:它首先是一个开源的SQL数据库,能够线性地伸缩与提升容错性,并且还具有真正的ACID事务功能。曾经互不相容的两种特性,现在已融合在一个统一的系统中。

      对于处于以下几种情况的公司来说,这一特性是非常重要的:

      ①新的项目要为大规模的伸缩性进行计划。

      ②现有的项目遇到了数据库伸缩性的瓶颈。

      ③现有的许多项目希望能用一个唯一的、容错性强的数据库抽象层统一工作模式。

      在文中,我将为读者介绍FoundationDB,并解释FoundationDB的SQL层是怎样将SQL数据映射到FoundationDB中的键-值存储后台系统中的。

      NoSQL数据库——FoundationDB的键-值存储系统

      FoundationDB是一个分布式的键-值存储系统,支持全局ACID事务操作,并且性能出众。在安装系统时,可以指定数据分发的级别。数据分发为容错性提供了支持:当某个服务器或网络的某部分产生故障时,数据库仍然可以正常操作,你的应用也不会受到影响。

      键-值与SQL架构

      我们开发的这套架构能够在键-值存储系统上支持多个层,每个层都能够在FoundationDB的基础上提供一套不同的数据模型,例如SQL数据库、文档数据库或图形数据库。许多使用者也自行创建了自定义的层。

      下图中列出架构中的了关键部分。处于最底层的是FoundationDB集群,无论集群的实际大小如何,对它的操作与一个单独的逻辑数据库并没有分别。SQL层则以一种无状态的中间层方式运行在键-值存储系统之上。这一层通过SQL与应用程序进行通信,并使用FoundationDB的客户端API与键-值存储系统进行通信。由于SQL层是无状态的,因此可以并行地运行任意数据的SQL层。

    下图中列出架构中的了关键部分。处于最底层的是FoundationDB集群,无论集群的实际大小如何,对它的操作与一个单独的逻辑数据库并没有分别。SQL层则以一种无状态的中间层方式运行在键-值存储系统之上。这一层通过SQL与应用程序进行通信,并使用FoundationDB的客户端API与键-值存储系统进行通信。由于SQL层是无状态的,因此可以并行地运行任意数据的SQL层。

      SQL层为键-值存储系统带来了如Google的F1般的能力

      SQL层是对SQL与键-值存储API进行转换的一套逻辑严密的层。首先,SQL层会从一条SQL语句开始,将其转换为最高效地键-值操作。这种方式类似于编译器将代码转换为低级别的执行格式。并且,这种转换是完全符合ANSISQL92标准的。开发者可以将该功能与ORM、RESTAPI进行接合,或者直接使用SQL层的命令行界面进行调用。从代码的角度来说,SQL层与键-值存储是完全分离的,它是通过FoundationDB的java绑定方式与键-值存储进行通信的。感兴趣的读者可以查看FoundationDB的SQL层在GitHub上的代码库,其代码是完全开源的。眼下唯一能够和这套系统进行比较的是Google的F1,后者是一套基于该公司的Spanner技术所创建的SQL引擎。

      如以下的简单图例所示,SQL层是由一系列组件所组成的。应用程序通过某种受支持的SQL客户端向SQL层发送查询语句,在解析之后转换为一棵计划节点树。优化器(Optimizer)会计算最佳的执行计划,并以一棵操作符树的方式表现出来,随后由执行框架(ExecutionFramework)运行。在执行阶段,对数据的请求将被发送到存储虚拟(StorageAbstraction)层,这一层通过使用Java的键-值API在数据与FoundationDB集群之间进行传输。数据库模型将存放在InformationSchema层中,这一层将被其它多个组件所调用。

     如以下的简单图例所示,SQL层是由一系列组件所组成的。应用程序通过某种受支持的SQL客户端向SQL层发送查询语句,在解析之后转换为一棵计划节点树。优化器(Optimizer)会计算最佳的执行计划,并以一棵操作符树的方式表现出来,随后由执行框架(ExecutionFramework)运行。在执行阶段,对数据的请求将被发送到存储虚拟(StorageAbstraction)层,这一层通过使用Java的键-值API在数据与FoundationDB集群之间进行传输。数据库模型将存放在InformationSchema层中,这一层将被其它多个组件所调用。

      将SQL数据映射到键-值存储系统

      SQL层需要管理两种类型的数据,首先是信息Schema的元数据,它负责描述所创建的表与可用的索引。其次,它还需要存储实际的数据,包括表内容、索引及序列。我们首先来描述一下这些数据是如何保存在键-值存储系统中的。

      本质上讲,每个键都是对应了某张表中的特定行的指针,而值则包含了该行的数据。键的分配是由Table-Group所决定的,它是包含了一个或多个表的组。稍后会对这个概念的细节进行更深入的讲解。SQL层会通过使用键-值存储目录层为每个Table-Group创建一个目录,存储目录层是为用户管理键空间的一个工具,它为每个独立的目录分配一个简短的字节数组,作为该目录的唯一键。同时,它也维护着其它元数据,以实现通过名称进行查找的功能。

      下面这个例子演示了如何创建目录的映射,通过以下语句分配键。

    本质上讲,每个键都是对应了某张表中的特定行的指针,而值则包含了该行的数据。键的分配是由Table-Group所决定的,它是包含了一个或多个表的组。稍后会对这个概念的细节进行更深入的讲解。SQL层会通过使用键-值存储目录层为每个Table-Group创建一个目录,存储目录层是为用户管理键空间的一个工具,它为每个独立的目录分配一个简短的字节数组,作为该目录的唯一键。同时,它也维护着其它元数据,以实现通过名称进行查找的功能。    下面这个例子演示了如何创建目录的映射,通过以下语句分配键。

      在键-值存储系统中有一些预定义的目录:

    在键-值存储系统中有一些预定义的目录:

      在存储数据时,可以选择使用以下三种格式中的一种:“元组(Tuple)”、“原始数据(Row_Data)”或者是“Protobuf”。如果使用默认的Tuple存储格式,那么每一行内容都将保存为一个单独的键-值对,键是通过连接以下字符串所生成的元组:目录前缀、该表在Table-Group中的位置,以及主键。而值的内容则是由该行中的所有列所组成的一个元组。

      举例来说,以下代码对之前创建的表进行操作,产生对应的键与值。

    在存储数据时,可以选择使用以下三种格式中的一种:“元组(Tuple)”、“原始数据(Row_Data)”或者是“Protobuf”。如果使用默认的Tuple存储格式,那么每一行内容都将保存为一个单独的键-值对,键是通过连接以下字符串所生成的元组:目录前缀、该表在Table-Group中的位置,以及主键。而值的内容则是由该行中的所有列所组成的一个元组。    举例来说,以下代码对之前创建的表进行操作,产生对应的键与值。

    在存储数据时,可以选择使用以下三种格式中的一种:“元组(Tuple)”、“原始数据(Row_Data)”或者是“Protobuf”。如果使用默认的Tuple存储格式,那么每一行内容都将保存为一个单独的键-值对,键是通过连接以下字符串所生成的元组:目录前缀、该表在Table-Group中的位置,以及主键。而值的内容则是由该行中的所有列所组成的一个元组。    举例来说,以下代码对之前创建的表进行操作,产生对应的键与值。

      了解了键-值存储系统中键的结构之后,你就能够从存储系统中直接读取数据了。我们将使用FoundationDB的PythonAPI来演示这一功能。在SQL层中,键与值是通过“.pack()”方法进行编码,并通过“.unpack()”方法进行解码的。下面的示例为你演示如何获取并解码数据。

    了解了键-值存储系统中键的结构之后,你就能够从存储系统中直接读取数据了。我们将使用FoundationDB的PythonAPI来演示这一功能。在SQL层中,键与值是通过“.pack()”方法进行编码,并通过“.unpack()”方法进行解码的。下面的示例为你演示如何获取并解码数据。

      以上代码会输出类似下面的结果:

    以上代码会输出类似下面的结果:

      现在让我们再来近距离观察一下Table-Group。每个独立的表都属于一个单独的组,如果某张额外的表能够创建一个对第一张表的“组外键”引用,那么它也能够加入到同一个组中。当我们为某张表创建组外键时,字表将与父表所在的目录进行交互。字表将成为Table-Group的一部分,在源表之后进行命名。这两张表的数据在将同一个目录中进行交互,这保证了范围扫描的高速,并且在Table-Group之内访问对象及表连接的开销极小。为了演示这一特性,我们将继续之前的示例,这一次的SQL语句如下:

    现在让我们再来近距离观察一下Table-Group。每个独立的表都属于一个单独的组,如果某张额外的表能够创建一个对第一张表的“组外键”引用,那么它也能够加入到同一个组中。当我们为某张表创建组外键时,字表将与父表所在的目录进行交互。字表将成为Table-Group的一部分,在源表之后进行命名。这两张表的数据在将同一个目录中进行交互,这保证了范围扫描的高速,并且在Table-Group之内访问对象及表连接的开销极小。为了演示这一特性,我们将继续之前的示例,这一次的SQL语句如下:

          该语句将返回以下结果:

    现在让我们再来近距离观察一下Table-Group。每个独立的表都属于一个单独的组,如果某张额外的表能够创建一个对第一张表的“组外键”引用,那么它也能够加入到同一个组中。当我们为某张表创建组外键时,字表将与父表所在的目录进行交互。字表将成为Table-Group的一部分,在源表之后进行命名。这两张表的数据在将同一个目录中进行交互,这保证了范围扫描的高速,并且在Table-Group之内访问对象及表连接的开销极小。为了演示这一特性,我们将继续之前的示例,这一次的SQL语句如下:

      由于第三张表的键都处于第一张表中各行的命名空间范围内,因此第三张表中所有插入的行都能够与第一张表的行相关联。键中的两个额外的值分别对应了Table-Group中的位置以及第三张表中的主键。对表1与表3通过引用键进行连接也无需通过标准的连接操作实现,直接通过线性扫描就语句了。这种排序方式比起传统的关系型数据库系统有着极大的优势。

      由于键都已经经过排序,因此索引可以直接利用这一点所带来的便利性。所有的表索引只包含一个键值,其中包括两部分内容。每个索引都创建于该表所属的目录之下,一个名为index的子目录中,这是该键元组的第一部分内容。第二个部分是一个组合,首先是该索引所对应的各个列的值,之后则是指定这一行所必须的列的值。

      举例来说,我们可以为这张表的c列创建一个索引。

    由于键都已经经过排序,因此索引可以直接利用这一点所带来的便利性。所有的表索引只包含一个键值,其中包括两部分内容。每个索引都创建于该表所属的目录之下,一个名为index的子目录中,这是该键元组的第一部分内容。第二个部分是一个组合,首先是该索引所对应的各个列的值,之后则是指定这一行所必须的列的值。    举例来说,我们可以为这张表的c列创建一个索引。

      接下来使用Python读取这个索引的内容,我们需要在Python解释器中加入以下内容:

    接下来使用Python读取这个索引的内容,我们需要在Python解释器中加入以下内容:

      这段代码会输入类似于下图中的内容,显示了键的两个组成部分:即该索引所在的目录的字节值,以及创建索引的c列的值加上主键的值。最后一个部分将被索引的值链接到某个特定的行,而该索引键所对应的值为空。

     这段代码会输入类似于下图中的内容,显示了键的两个组成部分:即该索引所在的目录的字节值,以及创建索引的c列的值加上主键的值。最后一个部分将被索引的值链接到某个特定的行,而该索引键所对应的值为空。

      如果要对SQL层的行为进行更多的控制调整,可以使用以下三种存储格式:一是之前描述过的元组格式,一是列键格式,以及protobuf格式。列健格式会为某一行的每个列值创建一个独立的键-值对。而protobuf存储格式为会每一行创建一个protobuf消息。

      接下来还需要对元数据进行存储与组织。SQL层使用protobuf消息与基于SQL的数据的结构进行通信。这个结构是由schema、组、表、列、索引与外键等对象共同组成的。

      SQL与NoSQL的混合模式

      如果在应用程序级别使用只读的键-值API,那么SQL层就能够在客户端进行直接访问。可以通过键-值API直接访问数据,但如果增加或改写了SQL层所用的关键数据,那就很可能破坏系统的运行。这里例举一些可能会产生的问题:缺乏对索引的维护、缺乏应有的限定,以及忽略了对数据及元数据的版本维护。而这种方式的好处,哪怕是在进行数据读取时也并不明显,因为SQL层本身的额外开销就非常小。因此总的来说,性能的开销主要取决于网络延迟。

      小编结语:

      SQL与NoSQL的结合使用能够相互利用两者的优点。FoundationDB的键-值存储系统为SQL层带来的好处包括可伸缩性、容错性及全局ACID的事务属性。你的应用程序同样也能从中受益,因此赶紧尝试一下吧!对应那些要执行大量的小批数据读取及写入的应用程序来说,FoundationDB提供了一个高伸缩并且安全的解决方案,并且可以任意使用SQL或NoSQL。

      更多内容尽在课课家教育!

课课家教育

未登录