SQL vs NoSQL:异同比较

SQL的表 vs NoSQL的文档

数据结构

关系型

关系型数据库最典型的数据结构是。通常长下面这个样子:

Table A

id content
1 test a
2 test b

Table B:

id table_a_id content
1 1 test c
2 1 test d
3 2 test e
4 2 test f

文档型

文档性数据库最典型的数据结构,当然是文档。以 JSON 为例,通常长下面这个样子:

Collection A:

       
       
[
{
"id": 1,
"content": "test a"
},
{
"id": 2,
"content": "test b"
}
]

Collection B:

       
       
[
{
"id": 1,
"collection_a_id": 1,
"content": "test c"
},
{
"id": 2,
"collection_a_id": 1,
"content": "test d"
},
{
"id": 3,
"collection_a_id": 2,
"content": "test e"
},
{
"id": 4,
"collection_a_id": 2,
"content": "test f"
}
]

使用场景

文档性

经常进行连接查询

我们要对上面的 A 和 B 进行连接查询,关系型数据库返回的是两个表的笛卡尔积:

id content table_b.id table_b.table_a_id table_b.content
1 test a 1 1 test c
1 test a 2 1 test d
2 test b 3 2 test e
2 test b 4 2 test f

上表中的第 2 行和第4 行都是冗余的。这种现象在复杂的连接查询中会被放大的很可怕。我们有的业务在一次查询中连接了 12 张表,冗余的数据可不止一个A.content字段这么简单。

这种情况下再来看看文档性数据库的返回结果,就非常合理了:

       
       
[
{
"left": {
"id": 1,
"content": "test a"
},
"right": [
{
"id": 1,
"collection_a_id": 1,
"content": "test c"
},
{
"id": 2,
"collection_a_id": 1,
"content": "test d"
}
]
},
{
"left": {
"id": 2,
"content": "test b"
},
"right": [
{
"id": 3,
"collection_a_id": 2,
"content": "test e"
},
{
"id": 4,
"collection_a_id": 2,
"content": "test f"
}
]
}
]

也有冗余,但是冗余的是字段的 key,完全是可控的。假如使用 protobuf 提前定义好结构,这种冗余甚至会被消除。

同一个表不同记录的数据结构经常不一样

假如你的表中存在很多互斥的字段,文档型数据库可能更适合你的系统。例如你又这样一张表:

id x y z
1 1 null null
2 null 2 null
3 null null 3
4 null null null

商标中xyz字段在每条记录张最多出现一次,然而null依然需要在存储或传输的时候占位。

这种时候,相比之下采用文档性数据库会更合理:

       
       
[
{
"id": 1,
"x": 1
},
{
"id": 2,
"x": 1
},
{
"id": 3,
"x": 1
},
{
"id": 4
}
]



SQL数据库提供了一族相关的数据表。举例来说,如果你在运营一个线上的书店,书的信息可以被加入到一个叫book的表。

ISBNtitleauthorformatprice
9780992461225JavaScript: Novice to NinjaDarren Jonesebook29.00
9780994182654Jump Start GitShaumik Daityariebook29.00

每一行都是一条不同的书德尔记录。这种设计是比较苛刻的,你没法使用同样的表去存储不同的信息或者在是数字类型的字段插入字符串。

NoSQL数据库存储类JSON的键值对文档:

{
    ISBN: 9780992461225, 
    title: "JavaScript: Novice to Ninja", 
    author: "Darren Jones", 
    format: "ebook", 
    price: 29.00
}

相似的文档可以被存储进一个集合,这跟SQL表十分类似。但是,在文档里面你可以存储任何形式的数据,NoSQL数据库并不会抱怨:

{ 
    ISBN: 9780992461225, 
    title: "JavaScript: Novice to Ninja", 
    author: "Darren Jones", 
    year: 2014, 
    format: "ebook", 
    price: 29.00, 
    description: "Learn JavaScript from scratch!", 
    rating: "5/5", 
    review: [ 
        { name: "A Reader", text: "The best JavaScript book I've ever read." }, 
        { name: "JS Expert", text: "Recommended to novice and expert developers alike." } 
    ]
}

SQL表创建的是严格的数据模板,所以你很难犯错。而NoSQL更加灵活,但是能够在任何地方存储数据可能会导致持续的问题。


SQL架构 vs NoSQL去架构

在一个SQL数据库中,在你确定表和字段类型这些架构之前是无法添加数据的。SQL架构还可能包括其他的信息:

  • primary keys: 主键,就像ISBN一样标示了唯一的一条记录
  • indexs: 索引,经常被查询的字段会被加上索引来提高检索速度
  • relationships: 关系,数据字段之间的逻辑联系
  • 诸如存储过程和触发器这样的机制

你的数据架构一定要在实现任何操作数据的应用逻辑之前被设计和实现出来。尽管之后再修改是可行的,但是大量的变更会非常复杂。

在NoSQL数据库中,数据可以被非常灵活的添加。并不需要事先进行字段设计和表的设计。比如说,在MongoDB中,下面的命令会在book表中创建一条新的记录,如果book表不存在,那它也会被同时创建:

db.book.insert(
    ISBN: 9780994182654, 
    title: "Jump Start Git", 
    author: "Shaumik Daityari", 
    format: "ebook", 
    price: 29.00
);

(MongoDB会自动在表中添加一条唯一的_id字段,随后你也可以对索引进行定义。)

一个NoSQL数据库可能更适合初始数据形式很难确定的项目中。但这并不意味着,你可以因此而偷懒:在项目开始的时候忽视设计正确的数据表,可能会在之后引入问题。

SQL 中心化 vs NoSQL去中心化

假定我们需要往book数据库中添加出版社信息。在SQL数据库中,一个出版社会包含多个标题,我们创建了一张新的publisher表:

idnamecountryemail
SP001SitePointAustraliafeedback@sitepoint.com

我们可以在book表里面添加一个publisher_id字段,来作为publisher.id的外键。

ISBNtitleauthorformatpricepublisher_id
9780992461225JavaScript: Novice to NinjaDarren Jonesebook29.00SP001
9780994182654Jump Start GitShaumik Daityariebook29.00SP001

这使得数据冗余被最小化,我们不必为每本书重复出版社的信息--只需要创建外键就行了。这种技术被称为中心化,而且也确实很有益处。我们可以在不用修改book的数据的前提下更新出版社信息。

在NoSQL中我们也可以使用中心化的技术。看一下在book集合中得一条文档:

{ 
    ISBN: 9780992461225, 
    title: "JavaScript: Novice to Ninja", 
    author: "Darren Jones", 
    format: "ebook", 
    price: 29.00, 
    publisher_id: "SP001"
}

-- 引用了一条在publisher集合中得一条文档:

{ 
    id: "SP001" 
    name: "SitePoint", 
    country: "Australia", 
    email: "feedback@sitepoint.com"
}

然而这有的时候却并不实际。更希望的是能够把数据去中心化,对每一个book重复冗余的publisher.

{ 
    ISBN: 9780992461225, 
    title: "JavaScript: Novice to Ninja", 
    author: "Darren Jones", 
    format: "ebook", 
    price: 29.00, 
    publisher: 
    { 
        name: "SitePoint", 
        country: "Australia", 
        email: "feedback@sitepoint.com" 
    }
}

这会使我们查询的更快,但是再多条记录中更新publisher信息,会非常的慢。


SQL关系型的JOIN vs NoSQL

SQL查询提供了强劲的JOIN语法。我们可以使用一条SQL语句在多个数据表中获取关系数据库。举例来说:

SELECT book.title, book.author, publisher.nameFROM bookLEFT JOIN book.publisher_id ON publisher.id;

这个语句会返回所有的书的标题,作者以及相关的出版人姓名。(假设出版人姓名存在)
NoSQL没有相对应的JOIN,这可能对那些熟练使用SQL的人非常不习惯。如果我们使用中心化的NoSQL集合,那我们需要拉取所有的book文档,再获取所有的publisher文档,再手动的通过程序逻辑来把两者联系起来。这也是为什么对NoSQL往往使用去中心化的方式很有必要。

SQL vs NoSQL 数据完整性

大部分的SQL数据库允许你通过外键限制的方式来强制的保证数据完整性。(除非你还在使用MySQL中得陈旧 不再被维护的MyISAM引擎)我们的book数据表能够:

  • 保证所有的book数据能够有一个合法的对应在pubilisher表中得publisher_id
  • 如果有book数据引用了publisher信息,那么这条数据不能被删除
    这种模式强制了数据库应该遵循的规范。对于开发者或者用户而言,无法在可能引入孤儿数据或非法数据的情况下,对数据条目进行编辑或删除。但是在NoSQL中却没有类似的数据完整性保证。你可以不管其他的文档,只存储你想要存储的内容。理想的情况下,一个数据条目应该成为关于一个事物的唯一信息来源。

SQL vs NoSQL 事务

在SQL数据库中,两条或多条更新语句能够在一个事务(保证成功或失败回滚的机制)中被同时执行。举例来说,假设book数据库包含订单和库存两张表。当一本书被订购的时候,我们需要往订单表添加一条数据,然后在库存表中将库存字段减一。如果我们把这两条更新语句独立执行,一条可能失败,另一条可能成功。因此会造成数据的不同步。而把他们通过事务的方式执行,就能保证一起成功或一起失败。
在NoSQL数据库中,对单个文档的修改是原子的。也就是说,如果你在文档中更新三个字段,那么要么三个字段同时更新,要么都不变。但是对于多条文档的更新而言却没有事务。不过有一个类事务的选项(http://docs.mongodb.org/manual/core/write-operations-atomicity/)。不过在写这篇文章的时候,这些还都需要你在代码里自行处理。

SQL vs NoSQL CRUD 语法

创建、读取、更新和删除数据是所有数据库系统的基础。本质上来说:

  • SQL是轻量级的解释性语言。语法强大,并且已经成为了国际标准。尽管大多数系统实现语法的时候略有不同。
  • NoSQL数据库使用带json参数的类javascript语言一样的查询。基本的操作比较简单,但是对于更复杂的查询来说,嵌套的JSON会非常的繁复。
    一个快速的对比:
SQLNoSQL
SP001SitePoint
插入一条book记录
INSERT INTO book ( `ISBN`, `title`, `author`)VALUES ( '9780992461256', 'Full Stack JavaScript', 'Colin Ihrig & Adam Bretz');db.book.insert({ ISBN: "9780992461256", title: "Full Stack JavaScript", author: "Colin Ihrig & Adam Bretz"});
更新一条book记录
UPDATE bookSET price = 19.99WHERE ISBN = '9780992461256'db.book.update( { ISBN: '9780992461256' }, { $set: { price: 19.99 } });
返回所有$10以上的书的标题
SELECT title FROM bookWHERE price > 10;db.book.find( { price: { >: 10 } }, { _id: 0, title: 1 });第二个JSON对象就是所谓的projection: 它设置了哪些字段要被返回 (_id字段是被默认返回的,所以需要覆盖它).
计算所有SitePoint网站的书的数量
SELECT COUNT(1) FROM bookWHERE publisher_id = 'SP001';db.book.count({ "publisher.name": "SitePoint"});这条语句假定使用了NoSQL的去中心化设计
返回book的格式类型的数量
SELECT format, COUNT(1) AS `total`FROM bookGROUP BY format;db.book.aggregate([ { $group: { _id: "$format", total: { $sum: 1 } } }]);这就是所谓的聚合:一个新的文档集合从原始的文档集合计算出来。
删除所有的SitePoint书
DELETE FROM bookWHERE publisher_id = 'SP001';db.book.remove({ "publisher.name": "SitePoint"});

SQL vs NoSQL 性能表现

或许这是最有争议性的比较。NoSQL通常被认为势必SQL更快的。这并不奇怪。NoSQL更简单的去中心化存储允许你在单词请求中获取一个条目的所有信息。因此并不需要相关的JOIN或复杂的SQL查询。
也就是说,你的项目设计和数据库设计的影响很大。一个被设计的很好的SQL数据库肯定比设计的很差的NoSQL数据库性能好很多,当然反之亦然。

SQL vs NoSQL 扩容

随着你数据的增加,你可能会觉得有必要把负载分布到多台服务器。对于基于SQL的系统来说,这有时候没那么容易。你如何分配相关的数据呢?集群化可能是最简单的选项;多个服务器访问相同的中心化存储 -- 但是即使这样也会有挑战。
NoSQL简单地数据模型会使得扩容简单一些,很多NoSQL数据库一开始就自建了扩容的功能。



SQL vs 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 集合里。

[sql]  view plain  copy
  1. {  
  2.   name:[  
  3.     "Billy","Bob","Jones"  
  4.   ],  
  5.   company:"FakeGoods Corp",  
  6.   jobtitle:"VicePresident of DataManagement",  
  7.   telephone:{  
  8.     home:"0123456789",  
  9.     mobile:"9876543210",  
  10.     work:"2244668800"  
  11.   },  
  12.   email:{  
  13.     personal:"bob@myhomeemail.net",  
  14.     work:"bob@myworkemail.com"  
  15.   },  
  16.   address:{  
  17.     home:{  
  18.       line1:"10Non-ExistentStreet",  
  19.       city:"Nowhere",  
  20.       country:"Australia"  
  21.     }  
  22.   },  
  23.   birthdate:ISODate("1980-01-01T00:00:00.000Z"),  
  24.   twitter:'@bobsfakeaccount',  
  25.   note:"Don't trust this guy",  
  26.   weight:"200lb",  
  27.   photo:"52e86ad749e0b817d25c8892.jpg"}  

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

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

然后执行全文搜索:

场景二:社交网络

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

另外:

  • 大部分的数据更新都来自单个源:用户。任何时候我们不太可能同时更新两条或更多记录,因此不要求类似事务控制的功能。
  • 尽管有些用户可能认为,状态更新失败不可能引起系统崩溃或经济损失。应用程序的接口和性能比数据完整性优先级更高。

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

[sql]  view plain  copy
  1. {  
  2.   user_id:ObjectID("65f82bda42e7b8c76f5c1969"),  
  3.   update:[  
  4.     {  
  5.       date:ISODate("2015-09-18T10:02:47.620Z"),  
  6.       text:"feelingmore positive today"  
  7.     },  
  8.     {  
  9.       date:ISODate("2015-09-17T13:14:20.789Z"),  
  10.       text:"spendingfar too muchtime here"  
  11.     }  
  12.     {  
  13.       date:ISODate("2015-09-17T12:33:02.132Z"),  
  14.       text:"consideringmy life choices"  
  15.     }  
  16.   ]}  

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

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





  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔡俊锋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值