SQL vs NoSQL:如何选择?

jopen 8年前

在前一篇文章中,我们讨论了 SQL 与 NoSQL 数据库之间基本的区别。接下来,我们我们将应用我们在特定场景中的知识来确定最佳的选择。

回顾一下:

SQL 数据库:

  • 在表中存储相关联的数据

  • 在使用之前需要定义表的一个模式

  • 鼓励标准化减少数据冗余

  • 支持从多个表中检索相关数据表连接在一个单一的命令

  • 实现数据完整性规则

  • 提供事务使两个或两个以上的成功或失败的数据更改作为一个原子单元

  • 可以扩展(有一些努力)

  • 使用一个强声明性语言查询

  • 提供足够的支持,专业技能和工具。

NoSQL 数据库

  • 将相关联的数据存储在类似 JSON 格式,名称-值 

  • 可以保存没有指定格式的数据

  • 通常必须规范化,所以一个项目的信息包含在一个文档里

  • 应该不需要连接(假设使用规范化的文档)

  • 允许任何数据被保存在任何时候任何地方,不需要验证 

  • 保证更新一个文档 - 但不是多个文档

  • 提供出色的性能和可伸缩性

  • 使用 JSON 数据对象查询

  • 是一个新的、令人兴奋的技术。

SQL 数据库是一个理想的项目,确定好了需求和健壮的数据的完整性是至关重要的。NoSQL 数据库是无关理想,不确定的或者不断变化的数据需求 ,在速度和可伸缩性上更重要。 简单的术语:

  • SQL 是数字。它最适合明确的定义,精确规范的独立项目。典型的使用案例是在线商城和银行系统。

  • NoSQL 是模拟。它最适合无固定要求的组织数据。典型的使用案例是社交网络,客户管理和网络分析系统。

一些项目要精准的符合。如果你有较浅的话,任何一种选择都是可行的,或者自然的非规范数据。但是请注意这些简化示例场景与全面的概括!你比我更了解你的项目,我不建议切换从SQL到NoSQL或反之亦然,除非它提供了可观的效益。这是你的选择。在项目的开始要考虑利弊,你不能出错。

场景一:一个联系人列表

让我们重新发明轮子,实现一个基于sql的通讯录系统。我们最初接触表的时候,天真的定义以下字段:

  • id (主键ID)

  • title (标题)

  • firstname (姓)

  • lastname (名)

  • gender (性别)

  • telephone (电话)

  • email   (邮箱)

  • address1 (地址1)

  • address2 (地址2)

  • address3 (地址3)

  • city       (城市)

  • region    (区/县)

  • zipcode    (邮政编码)

  • country    (国家)


问题一: 很少人只有一个电话号码。我们可能需要至少三个号码:一个座机,一个移动电话,一个工作电话。但是有多少个号码无关紧要——有些人、有些地方需要更多。让我们创建一个单独的 telephone 表,这样的话他们想要多少联系人都可以。这也让我们的数据标准化了——我们不需要没有号码的联系人显示为NULL。

  • contact_id

  • name (文本,例如座机号,工作手机等)

  • number

问题二:Email地址有同样的问题,因此我们也创建一个类似的 email 表:

  • contact_id

  • name (text such as home email, work email, etc.)

  • address

问题三:我们可能不想输入一个(地理位置的)地址,或者我们想输入多个地址,工作地,家里,度假住所等。因此我们需要一个新的 address 表:

  • contact_id

  • name (text such as home, office, etc.)

  • address1

  • address2

  • address3

  • city

  • region

  • zipcode

  • country

我们原来的 contact 表简化成:

  • id

  • title

  • firstname

  • lastname

  • gender

太棒了——我们有了一个能存放任意联系人的任意多个电话号码,Email 地址和住址的标准化数据库。不幸的是……

Schema是固定不变的


我们没有考虑到联系人的中间名字、出生日期、公司或职位。我们添加多少字段都没关系,我们很快会受到更新的需求要添加备注、纪念日、关系状态、社交媒体账号、内腿测量值、最喜欢的奶酪类型等字段。预测所有选项是不可能的,因此我们可能需要一个 otherdata 表,用来处理名字-值对。

数据是碎片化的

对开发者或者系统管理员来说,检查数据库并不容易。程序逻辑会变得更慢、更复杂,因为利用单个 SELECT 和多个 JOIN 语句查询联系人数据不太实际。(你可以这么做,但是结果可能需要包含 telephone,email,和 address字段的每一种组合:如果有个联系人有三个电话号码,五个Email地址和两个住址,那么SQL查询将会产生30条结果。)     最后,全文搜索很困难。如果有人输入字符串"SitePoint",我们必须检查所有的表,看看它是否为联系人名字、电话、Email或者住址的一部分,并且需要做相应的排序。如果你用过WordPress的搜索功能,你就会明白这有多虐心。

选择NoSQL

我们的联系人数据关注的是人。他们难以预测,在不同的时间有不同的需求。使用NoSQL数据库,联系人列表将会从中受益。数据库将一个联系人的所有数据存储在一个单独的文档里的contacts 集合里。

{    name: [      "Billy", "Bob", "Jones"    ],    company: "Fake Goods Corp",    jobtitle: "Vice President of Data Management",    telephone: {      home: "0123456789",      mobile: "9876543210",      work: "2244668800"    },    email: {      personal: "bob@myhomeemail.net",      work: "bob@myworkemail.com"    },    address: {      home: {        line1: "10 Non-Existent Street",        city: "Nowhere",        country: "Australia"      }    },    birthdate: ISODate("1980-01-01T00:00:00.000Z"),    推ter: '@bobsfakeaccount',    note: "Don't trust this guy",    weight: "200lb",    photo: "52e86ad749e0b817d25c8892.jpg"}

在这个例子里,我们没有存储联系人的头衔或者性别,我们还添加了一些数据,而这些数据不需要应用到任何其他联系人。没关系——我们的NoSQL数据库不会介意,我们还可以随意添加或移除字段。

由于联系人数据在单独的文档里,我们可以用一条查询语句获取一部分或全部信息。全文搜索也变得简单;在MongoDB里,我们可以这样定义 contact 中的所有文本字段的索引:

db.contact.createIndex({ "$**": "text" });

然后执行全文搜索:

db.contact.find({    $text: { $search: "something" }});

场景二:社交网络

    社交网络可能使用类似的联系人数据存储,但是它会根据功能集合扩展,比如关系链、状态更新、发送消息和”赞“。这些功能可能会根据用户需求来实现或者移除——无法预测它们会怎样演进。

    另外:

    • 大部分的数据更新都来自单个源:用户。任何时候我们不太可能同时更新两条或更多记录,因此不要求类似事务控制的功能。

    • 尽管有些用户可能认为,状态更新失败不可能引起系统崩溃或经济损失。应用程序的接口和性能比数据完整性优先级更高。

    NoSQL看来是个好的方案。它允许我们快速地实现存储不同类型数据的功能。例如,可以用单个文档里的 status 集合替换所有用户的过时的状态更新。

    {    user_id: ObjectID("65f82bda42e7b8c76f5c1969"),    update: [      {        date: ISODate("2015-09-18T10:02:47.620Z"),        text: "feeling more positive today"      },      {        date: ISODate("2015-09-17T13:14:20.789Z"),        text: "spending far too much time here"      }      {        date: ISODate("2015-09-17T12:33:02.132Z"),        text: "considering my life choices"      }    ]}

    文档可能会变得很长,但我们可以获取数组的子集,比如最近的更新。每个用户的所有的历史状态记录都能被快速搜索到。

    现在假设我们想在发布更新的时候引入表情符号选择。这涉及到给 update 数组里的新记录添加图引用。不像 SQL 存储,没必要把之前消息里的表情符号置为 NULL——我们的程序逻辑可以显示默认图片或者没有图片,如果没有设置表情符号的话。

    场景三:仓库管理系统

    考虑一个监控仓库货物的系统。我们需要记录:

    • 送达仓库并被分配到指定位置的物品

    • 仓库内物品的移动,也就是重新整理库存,以便让同样的物品放在相邻的位置

    • 订单以及后续将物品搬出仓库,准备发货

    我们的数据需求:

    1. 通用的物品信息,比如包装数量、尺寸和颜色等可被存储,但这些是我们可以识别并应用到任何物品上的离散数据。我们不太可能关注细节,例如笔记本处理器速度或者智能手机的电池寿命。

    2. 最小化出错的可能是必要的。我们不能让物品凭空消失或者移到已经有别的物品存放的位置。

    3. 简单来说,我们在记录物品从一个物理区域到另一个物理区域的转移——或者从A位置移走,放到B位置。这是同一个动作的两次更新。

    我们需要一个具备强制数据完整性和事务支持的健壮存储系统。(当前)只有 SQL 数据库满足这些需求。

    表现自己!

    我希望这些场景有所帮助,但是每个项目是不同的,最终,你需要做出自己的决定。(虽然,我们开发人员擅长于证明我们的技术选择,不管他们有多好!)

    最好的建议:显露你自己尽可能多的技术。这些知识可以让你对SQL或者NoSQL做出一个理性和情感上公正的判断。祝您好运。