NoSQL这个词在最近几年变得无处不在。但“NoSQL”到底是什么?它如何以及为何如此有用?我更愿意称其为“轻型结构化伪代码”)来编写NoSQL数据库来回答这些问题。OldSQL在很多情况下,SQL已经成为“数据库”的同义词。实际上,SQL是StructuredQueryLanguage的缩写,并不是指数据库技术本身。相反,它指的是一种用于从RDBMS(关系数据库管理系统)检索数据的语言。MySQL、MSSQLServer和Oracle都属于RDBMS之一。RDBMS中的R,即“关系”(relational,associated),是其中最丰富的部分。数据是通过表来组织的,每个表都是由类型(type)与列(column)组成的。所有表、列及其类类型都称为数据库的架构(体系结构或模式)。schema通过每个表的描述信息完整的描述了数据库的结构。例如,一个名为Car的表可能有以下列:Make:一个字符串Model:一个字符串Year:一个四位数字;或者,一个日期颜色:一个字符串VIN(VehicleIdentificationNumber):一个字符串在表格中,每个单独的条目称为行(row),或记录(record)。为了区分每条记录,通常会定义一个主键(primarykey)。表中的主键是列之一,可以唯一标识每一行。在Car表中,VIN是天然的主键选择,因为它可以保证每辆车都有唯一的标识。Make、Model、Yea中可能有两个不同的行r和Color列具有相同的值,但对于不同的汽车,必须有不同的VIN。相反,只要两行具有相同的VIN,我们就可以认为这两行指的是相同的VIN,而无需检查其他列。这是同一辆车。QueryingSQL让我们可以通过查询数据库来获得有用的信息。简单来说,查询就是使用结构化语言向RDBMS提出问题,并将返回的行解释为问题的答案。假设数据库代表了美国所有注册的车辆,为了获取所有的记录,我们可以对数据库执行如下SQL查询:大致将SQL翻译成中文:“SELECT”:“Showme”“Make,Model”:"valuesofMakeandModel""FROMCar":"foreachrowinthetableCar"即,"给我看表Car每一行的Make和Model的值"。执行查询后,我们会得到一些查询的结果,每一个都是Make和Model。如果我们只关心1994年登记的汽车的颜色,那么是的:此时,我们会得到一个类似如下的列表:最后,我们可以使用表(主键)主键,这里是VIN来指定avehicle:上面的查询语句会返回指定车辆的属性信息。主键被定义为唯一且不可重复的。也就是说,具有指定VIN的车辆最多只能在表中出现一次。这很重要,为什么?让我们看一个例子:关系假设我们经营一家汽车修理公司。除了其他必要的事情之外,我们还需要跟踪汽车的服务历史,即车辆上的所有维修记录。然后我们可以创建一个包含以下列的ServiceHistory表:VIN|制作|型号|年份|颜色|执行的服务|机械师|价格|Date这样,每次修车,我们都会在表中新增一行,写上我们这次服务做了什么,谁是维修工,花了多少钱,服务时间等等。但是等等一分钟,我们都知道对于同一款车,所有与车辆自身信息相关的列都是不变的。也就是说,如果我的Black2014LexusRX350被修整10次,即使Make,Model,YearandColor这些信息都不会改变,而且每次还是重复记录。与无效的重复记录相比,更合理的做法是将此类信息只存储一次,并在需要时查询。那么该怎么办?我们可以创建第二个表:Vehicle,它包含以下列:VIN|制作|型号|年份|Color这样,对于ServiceHistory表,我们可以简化为如下几列:VIN|执行的服务|机械师|价格|Date你可能会问,为什么VIN同时出现在两个表中?因为我们需要一种方式来确认ServiceHistory表中的车辆是指Vehicle表中的车辆,即需要确认两个表中的两条记录代表的是同一辆车。这样,我们只需要为每辆车存储一次自己的信息。每次有车辆来维修时,我们都会在ServiceHistory表中创建一个新行,而不是在Vehicle表中添加一条新记录。毕竟,他们指的是同一辆车。我们可以通过SQL查询语句扩展Vehicle和ServiceHistory这两个表中包含的隐式关系:查询的目的是找出所有维修费用大于$75.00的车辆的Model和Year。注意,我们通过匹配Vehicle和ServiceHistory表中的VIN值来过滤符合条件的记录。返回的记录会是两个表中满足条件的一些记录,“Vehicle.Model”和“Vehicle.Year”,说明我们只想要Vehicle表中的这两列。如果我们的数据库没有Indexes(索引)(正确的应该是indices),上面的查询需要进行表扫描(tablescan)来定位符合查询要求的行。表扫描按顺序检查表中的每一行,这通常很慢。事实上,表扫描实际上是所有查询中最慢的。您可以通过索引列来避免表扫描。我们可以把索引看作是一种数据结构,它可以让我们通过预排序的方式快速找到被索引列上的指定值(或者指定范围内的一些值)。也就是说,如果我们在Price列的索引上有索引,那么不用逐行扫描整个表来判断它的价格是否大于75.00,只需要利用索引中包含的信息“跳转”到价格大于75.00的第一行,并返回每个后续行(因为索引是有序的,这些行的价格至少为75.00)。在处理大量数据时,索引是提高查询速度不可或缺的工具。当然,与所有事物一样,需要权衡取舍,使用索引会带来一些开销:索引的数据结构会消耗可用于在数据库中存储数据的内存。这就需要我们权衡利弊,找到一个折中的办法,但是在经常查询的列上加索引是很常见的。ClearBox受益于数据库能够检查表的架构(描述每列包含的数据类型),启用索引等高级功能,并能够根据数据做出合理的决策。也就是说,对于数据库来说,表其实是“黑盒”(或透明盒)的对立面?当我们谈论NoSQL数据库时,请记住这一点。当涉及到查询不同类型的数据库引擎的能力时,这也是它非常重要的一部分。模式我们已经知道表的模式描述列的名称和它包含的数据类型。它还包括其他信息,例如哪些列可以为空,哪些列不允许重复值,以及对表中列的任何其他限制。一张表在任何时候都只能有一个模式,表中的所有行都必须遵守模式的规则。这是一个非常重要的约束。假设您有一个包含数百万消费者信息的数据库表。您的销售团队想要添加一些额外信息(例如,用户的年龄)以提高其电子邮件营销算法的准确性。这需要改变(更改)现有表——添加一个新列。我们还需要确定表中的每一行是否都需要该列的值。通常,让列具有值是非常有意义的,但这样做可能需要我们不容易访问的信息(例如数据库中每个用户的年龄)。因此,在这个层面上,也需要进行一些取舍。此外,对大型数据库进行一些更改通常不是一件小事。重要的是要有一个回滚方案,以防出现问题。但即便如此,一旦进行了模式更改,我们并不总是能够撤消这些更改。模式维护可能是DBA工作中最困难的部分之一。Key/ValueStores在“NoSQL”这个词出现之前,像memcached这样的Key/ValueDataStores(键/值数据存储)可以提供没有表模式的数据存储。事实上,在K/V存储中,根本就没有“表”的概念。只有键和值。如果键值存储听起来很熟悉,那可能是因为这个概念是建立在与Python的dict和set相同的原则上的:使用哈希表来提供键的快速数据查询。基于Python的最原始的NoSQL数据库之一,简单来说就是一个大词典(dictionary)。为了理解它是如何工作的,自己写一个!先来看一些简单的设计思路:一个Pythondict作为主要的数据存储,只支持字符串类型作为键(key)支持存储整数、字符串和列表一个简单的TCP/IP服务器,使用ASCLL字符串传递消息高级命令,如INCREMENT、DELETE、APPEND和STATS有一个基于ASCII的TCP/IP接口用于数据存储。一个优点是我们可以使用一个简单的telnet程序与服务器进行交互,而且没有什么特别的(尽管这是一个很好的练习并且只需要15行代码就可以完成)。对于我们发送回服务器及其他地方的信息,我们需要一种“有线格式”。下面简单说明一下:Commands支持PUT参数:Key,Value目的:向数据库中插入一个新条目(entry)GET参数:Key目的:从数据库中取出一个存储的值PUTLIST参数:Key,Value目的:插入一个数据库中的新列表条目APPEND参数:Key、Value目的:向数据库中的现有列表添加新元素INCREMENT参数:key目的:增加数据库中的整数值DELETE参数:Key目的:从数据库中删除条目STATS参数:无(N/A)目的:请求每个已执行命令的成功/失败统计信息现在让我们定义消息自己的结构。MessageStructure1.RequestMessages一个请求消息(RequestMessage)包含一个命令(command),一个键(key),一个值(value),一个值类型(type)。最后三个取决于消息类型,可以是可选的,不是必需的。;用作定界符。即使没有上述选项,仍然必须有三个;消息中的字符。COMMAND是上面列表中的命令之一KEY是一个字符串,可以作为数据库键(可选)VALUE是一个整数,列表或数据库中的字符串(可选)列表可以表示为以逗号分隔的字符串字符串,例如,“red,green,blue”VALUETYPE描述应该解释VALUE的原因。可能的类型值有:INT、STRING、LISTExamples2.ReponseMessages一个ResponseMessage由两部分组成,passed;分离。第一部分始终为True|False,这取决于执行的命令是否成功。第二部分是命令信息(commandmessage),当发生错误时,会显示错误信息。对于那些执行成功的命令,如果我们不想要默认的返回值(比如PUT),就会出现成功信息。如果我们返回成功命令(例如GET)的值,那么第二部分将是值本身。示例显示代码!我将以块摘要形式显示整个代码。整个代码只有180行,阅读起来不会花很长时间。设置下面是我们的服务器需要的一些样板代码:很容易看出上面只是一个包导入和一些数据初始化。设置(续)我将跳过一些代码,以便我可以继续显示上面准备部分的剩余代码。请注意,它涉及一些尚不存在的功能,但没关系,我们稍后会介绍它们。在完整版中(将在最后呈现),一切都将按顺序组织。下面是设置代码的其余部分:我们创建COMMAND_HANDLERS,通常称为查找表。COMMAND_HANDLERS的工作是将命令与处理该命令的函数相关联。例如,如果我们收到一个GET命令,COMMAND_HANDLERS[command](key)相当于说handle_get(key)。请记住,在Python中,函数可以被认为是一个值,并且可以像存储在字典中的任何其他值一样被访问。在上面的代码中,虽然有些命令请求相同的参数,但我决定分别处理每个命令。虽然可以简单地强制所有handle_函数接受一个键和一个值,但我希望这些处理函数更有条理,更容易测试,更不容易出错。请注意,与套接字相关的代码非常少。整个服务器虽然是基于TCP/IP通信,但是底层的网络交互代码并不多。最后要注意一点:DATA字典,因为这一点不是很重要,所以你很可能会错过它。DATA是真正用于存储的键值对,也正是它们才真正构成了我们的数据库。CommandParser我们来看一些命令解析器(commandparser),它负责解释接收到的消息:在这里我们可以看到发生了类型转换。如果我们希望值是一个列表,我们可以在字符串上调用str.split(',')来获取我们想要的值。对于int,我们可以简单地使用带有字符串参数的int()。字符串和str()也是如此。命令处理程序下面是命令处理程序的代码。它们非常直观且易于理解。注意,虽然有很多错误检查,但并不是面面俱到,很复杂。阅读过程中,如有发现错误,请移步此处讨论。有两点需要注意:多重赋值(multipleassignment)和代码重用。有些功能只是为了更多的逻辑。简单的包装器,例如handle_get和handle_getlist。由于我们有时只需要现有函数的返回值,而其他时候我们需要检查函数返回的内容,此时使用多重赋值。看看handle_append。如果我们尝试调用handle_get但密钥不存在,那么我们只需返回handle_get返回的内容。此外,我们希望能够将handle_get返回的元组作为单个返回值进行引用。然后我们可以在键不存在时简单地返回return_value。如果它确实存在,那么我们需要检查该返回值。此外,我们希望能够将handle_get的返回值作为单独的变量进行引用。为了能够处理以上两种情况,同时考虑到结果需要单独处理的情况,我们使用了多重赋值。这样就不用写多行代码,同时可以保持代码的清晰。return_value=exists,list_value=handle_get(key)可以明确表明我们将至少以两种不同的方式引用handle_get的返回值。这是一个怎样的数据库?上面的程序显然不是RDBMS,但它绝对有资格作为NoSQL数据库。它如此容易创建的原因是我们没有与数据进行任何实际交互。我们只做最少的类型检查并存储用户发送的任何内容。如果我们需要存储更多的结构化数据,我们可能需要为数据库创建一个模式来存储和检索数据。既然NoSQL数据库更容易编写、更容易维护、更容易实现,为什么我们不直接使用MongoDB呢?当然是有原因的,还是那句话,必有损失,我们需要在NoSQL数据库提供的数据灵活性的基础上,权衡数据库的可搜索性。查询数据如果我们在NoSQL数据库上面存储以前的Car数据。那么我们可能会以VIN为key,用一个list作为每一列的value,即2134AFGER245267=['Lexus','RX350',2013,Black]。当然,我们已经失去了列表中每一个索引的意义(meaning)。我们只需要知道某处索引1存储汽车的型号,索引2存储年份。坏事来了,当我们要执行之前的查询语句时会发生什么?找到1994年汽车的所有颜色将是一场噩梦。我们必须遍历DATA中的每个值,以确认该值是否存储汽车数据或其他不相关的数据。比如查看索引2,看索引2的值是否等于1994,然后继续取索引3的值。这比表扫描更糟糕,因为它不仅扫描每一行数据,还需要应用一些复杂的规则来回答查询。NoSQL数据库的作者当然意识到了这些问题,而且(鉴于查询是一个非常有用的特性)他们也想出了让查询不那么“遥不可及”的方法。一种方法是对所使用的数据(例如JSON)进行结构化,以允许引用其他行来表示关系。同时,大多数NoSQL数据库都有命名空间(namespace)的概念,单一类型的数据可以存储在数据库中该类型的唯一“段”中,这使得查询引擎可以使用“形状”要查询的数据信息。当然,虽然存在(并已实现)更复杂的方法来增强可查询性,但存储更少量的模式和增强可查询性之间的折衷始终是一个不可避免的问题。在这个例子中我们的数据库只支持按键查询。如果我们需要支持更丰富的查询,事情就会变得复杂得多。总结至此,希望“NoSQL”的概念已经很清楚了。我们学习了一些SQL,并了解了RDBMS的工作原理。我们看到了如何从RDBMS中检索数据(使用SQL查询)。通过构建一个玩具级的NoSQL数据库,我们了解了在可查询性和简单性之间面临的一些问题,并对一些数据库进行了讨论。作者处理这些问题的一些方式。即使对于简单的键值存储,也有大量关于数据库的知识。虽然我们只触及了表面,但希望您已经了解NoSQL是什么、它是如何工作的以及何时使用它是个好主意。如果您有一些很棒的想法要分享,请随时讨论。
