Sql Or NoSql,看完这一篇你就懂了

前言数据库

你是否在为系统的数据库来一波大流量就几乎打满CPU,平常CPU居高不下烦恼?你是否在各类NoSql间纠结不定,到底该选用那种最好?今天的你就是昨天的我,这也是写这篇文章的初衷。缓存

这篇文章是我好几个月来一直想写的一篇文章,也是一直想学习的一个内容,做为互联网从业人员,咱们要知道关系型数据库(MySql、Oracle)没法知足咱们对存储的全部要求,所以对底层存储的选型,对每种存储引擎的理解很是重要。同时也因为过去一段时间的工做经历,对这块有了一些更多的思考,想经过本身的总结把这块写出来分享给你们。安全

 

结构化数据、非结构化数据与半结构化数据性能优化

文章的开始,聊一下结构化数据、非结构化数据与半结构化数据,由于数据特色的不一样,将在技术上直接影响存储引擎的选型。服务器

首先是结构化数据,根据定义结构化数据指的是由二维表结构来逻辑表达和实现的数据,严格遵循数据格式与长度规范,也称做为行数据,特色为:数据以行为单位,一行数据表示一个实体的信息,每一行数据的属性是相同的。例如:数据结构

所以关系型数据库完美契合结构化数据的特色,关系型数据库也是关系型数据最主要的存储与管理引擎。架构

非结构化数据,指的是数据结构不规则或不完整,没有任何预约义的数据模型,不方便用二维逻辑表来表现的数据,例如办公文档(Word)、文本、图片、HTML、各种报表、视频音频等。并发

介于结构化与非结构化数据之间的数据就是半结构化数据了,它是结构化数据的一种形式,虽然不符合二维逻辑这种数据模型结构,可是包含相关标记,用来分割语义元素以及对记录和字段进行分层。常见的半结构化数据有XML和JSON,例如:负载均衡

<person>
    <name>张三</name>
    <age>18</age>
    <phone>12345</phone>
</person>

这种结构也被成为自描述的结构。运维

 

以关系型数据库的方式作存储的架构演进

首先,咱们看一下使用关系型数据库的方式,企业一个系统发展的几个阶段的架构演进(因为本文写的是Sql与NoSql,所以只以存储方式做为切入点,不会涉及相似MQ、ZK这些中间件内容):

阶段一:企业刚发展的阶段,最简单,一个应用服务器配一个关系型数据库,每次读写数据库。

阶段二:不管是使用MySQL仍是Oracle仍是别的关系型数据库,数据库一般不会先成为性能瓶颈,一般随着企业规模的扩大,一台应用服务器扛不住上游过来的流量且一台应用服务器会产生单点故障的问题,所以加应用服务器而且在流量入口使用Nginx作一层负载均衡,保证把流量均匀打到应用服务器上。

阶段三:随着企业规模的继续扩大,此时因为读写都在同一个数据库上,数据库性能出现必定的瓶颈,此时简单地作一层读写分离,每次写主库,读备库,主备库之间经过binlog同步数据,就能很大程度上解决这个阶段的数据库性能问题

阶段四:企业发展愈来愈好了,业务愈来愈大了,作了读写分离数据库压力仍是愈来愈大,这时候怎么办呢,一台数据库扛不住,那咱们就分几台吧,作分库分表,对表作垂直拆分,对库作水平拆分。以扩数据库为例,扩出两台数据库,以必定的单号(例如交易单号),以必定的规则(例如取模),交易单号对2取模为0的丢到数据库1去,交易单号对2取模为1的丢到数据库2去,经过这样的方式将写数据库的流量均分到两台数据库上。通常分库分表会使用Shard的方式,经过一个中间件,便于链接管理、数据监控且客户端无需感知数据库ip

 

关系型数据库的优势

上面的方式,看似能够解决问题(实际上确实也能解决不少问题),正常对关系型数据库作一下读写分离 + 分库分表,支撑个1W+的读写QPS仍是问题不大的。可是受限于关系型数据库自己,这套架构方案依然有着明显的不足,下面对利用关系型数据库方式作存储的方案的优势先进行一下分析,后一部分再分析一下缺点,对某个技术的优缺点的充分理解是技术选型的前提。

  • 易理解

  由于行 + 列的二维表逻辑是很是贴近逻辑世界的一个概念,关系模型相对网状、层次等其余模型更加容易被理解

  • 操做方便

  通用的SQL语言使得操做关系型数据库很是方便,支持join等复杂查询,Sql + 二维关系是关系型数据库最无可比拟的优势,这种易用性很是贴近开发者

  • 数据一致性

  支持ACID特性,能够维护数据之间的一致性,这是使用数据库很是重要的一个理由之一,例如同银行转帐,张三转给李四100元钱,张三扣100元,李四加100元,并且必须同时成功或者同时失败,不然就会形成用户的资损

  • 数据稳定

  数据持久化到磁盘,没有丢失数据风险,支持海量数据存储

  • 服务稳定

  最经常使用的关系型数据库产品MySql、Oracle服务器性能卓越,服务稳定,一般不多出现宕机异常

 

关系型数据库的缺点

紧接着的,咱们看一下关系型数据库的缺点,也是比较明显的。

  • 高并发下IO压力大

  数据按行存储,即便只针对其中某一列进行运算,也会将整行数据从存储设备中读入内存,致使IO较高

  • 为维护索引付出的代价大

  为了提供丰富的查询能力,一般热点表都会有多个二级索引,一旦有了二级索引,数据的新增必然伴随着全部二级索引的新增,数据的更新也必然伴随着全部二级索引的更新,这不可避免地下降了关系型数据库的读写能力,且索引越多读写能力越差。有机会的话能够看一下本身公司的数据库,除了数据文件不可避免地占空间外,索引占的空间其实也并很多

  • 为维护数据一致性付出的代价大

  数据一致性是关系型数据库的核心,可是一样为了维护数据一致性的代价也是很是大的。咱们都知道SQL标准为事务定义了不一样的隔离级别,从低到高依次是读未提交、读已提交、可重复度、串行化,事务隔离级别越低,可能出现的并发异常越多,可是一般而言能提供的并发能力越强。那么为了保证事务一致性,数据库就须要提供并发控制与故障恢复两种技术,前者用于减小并发异常,后者能够在系统异常的时候保证事务与数据库状态不会被破坏。对于并发控制,其核心思想就是加锁,不管是乐观锁仍是悲观锁,只要提供的隔离级别越高,那么读写性能必然越差

  • 水平扩展后带来的种种问题难处理

  前文提过,随着企业规模扩大,一种方式是对数据库作分库,作了分库以后,数据迁移(1个库的数据按照必定规则打到2个库中)、跨库join(订单数据里有用户数据,两条数据不在同一个库中)、分布式事务处理都是须要考虑的问题,尤为是分布式事务处理,业界当前都没有特别好的解决方案

  • 表结构扩展不方便

  因为数据库存储的是结构化数据,所以表结构schema是固定的,扩展不方便,若是须要修改表结构,须要执行DDL(data definition language)语句修改,修改期间会致使锁表,部分服务不可用

  • 全文搜索功能弱

  例如like "%中国真伟大%",只能搜索到"2019年中国真伟大,爱祖国",没法搜索到"中国真是太伟大了"这样的文本,即不具有分词能力,且like查询在"%中国真伟大"这样的搜索条件下,没法命中索引,将会致使查询效率大大下降

写了这么多,个人理解核心仍是前三点,它反映出的一个问题是关系型数据库在高并发下的能力是有瓶颈的,尤为是写入/更新频繁的状况下,出现瓶颈的结果就是数据库CPU高、Sql执行慢、客户端报数据库链接池不够等错误,所以例如万人秒杀这种场景,咱们绝对不可能经过数据库直接去扣减库存。

可能有朋友说,数据库在高并发下的能力有瓶颈,我公司有钱,加CPU、换固态硬盘、继续买服务器加数据库作分库不就行了,问题是这是一种性价比很是低的方式,花1000万达到的效果,换其余方式可能100万就达到了,不考虑人员、服务器投入产出比的Leader就是个不合格的Leader,且关系型数据库的方式,受限于它自己的特色,可能花了钱都未必能达到想要的效果。至于什么是花100万就能达到花1000万效果的方式呢?能够继续往下看,这就是咱们要说的NoSql。

 

结合NoSql的方式作存储的架构演进

像上文分析的,数据库做为一种关系型数据的存储引擎,存储的是关系型数据,它有优势,同时也有明显的缺点,所以一般在企业规模不断扩大的状况下,不会一味期望经过加强数据库的能力来解决数据存储问题,而是会引入其余存储,也就是咱们说的NoSql。

NoSql的全称为Not Only SQL,泛指非关系型数据库,是对关系型数据库的一种补充,特别注意补充这两个字,这意味着NoSql与关系型数据库并非对立关系,两者各有优劣,取长补短,在合适的场景下选择合适的存储引擎才是正确的作法。

比较简单的NoSql就是缓存:

针对那些读远多于写的数据,引入一层缓存,每次读从缓存中读取,缓存中读取不到,再去数据库中取,取完以后再写入到缓存,对数据作好失效机制一般就没有大问题了。一般来讲,缓存是性能优化的第一选择也是见效最明显的方案。

可是,缓存一般都是KV型存储且容量有限(基于内存),没法解决全部问题,因而再进一步的优化,咱们继续引入其余NoSql:

数据库、缓存与其余NoSql并行工做,充分发挥每种NoSql的特色。固然NoSql在性能方面大大优于关系挺数据库的同时,每每也伴随着一些特性的缺失,比较常见的就是事务功能的缺失。

下面看一下经常使用的NoSql及他们的表明产品,并对每种NoSql的优缺点和适用场景作一下分析,便于熟悉每种NoSql的特色,方便技术选型。

 

KV型NoSql(表明----Redis)

KV型NoSql顾名思义就是以键值对形式存储的非关系型数据库,是最简单、最容易理解也是你们最熟悉的一种NoSql,所以比较快地带过。Redis、MemCache是其中的表明,Redis又是KV型NoSql中应用最普遍的NoSql,KV型数据库以Redis为例,最大的优势我总结下来就两点:

  • 数据基于内存,读写效率高
  • KV型数据,时间复杂度为O(1),查询速度快

所以,KV型NoSql最大的优势就是高性能,利用Redis自带的BenchMark作基准测试,TPS可达到10万的级别,性能很是强劲。一样的Redis也有全部KV型NoSql都有的比较明显的缺点:

  • 只能根据K查V,没法根据V查K
  • 查询方式单一,只有KV的方式,不支持条件查询,多条件查询惟一的作法就是数据冗余,但这会极大的浪费存储空间
  • 内存是有限的,没法支持海量数据存储
  • 一样的,因为KV型NoSql的存储是基于内存的,会有丢失数据的风险

综上所述,KV型NoSql最合适的场景就是缓存的场景:

  • 读远多于写
  • 读取能力强
  • 没有持久化的需求,能够容忍数据丢失,反正丢了再查询一把写入就是了

例如根据用户id查询用户信息,每次根据用户id去缓存中查询一把,查到数据直接返回,查不到去关系型数据库里面根据id查询一把数据写到缓存中去。

 

搜索型NoSql(表明----ElasticSearch)

传统关系型数据库主要经过索引来达到快速查询的目的,可是在全文搜索的场景下,索引是无能为力的,like查询一来没法知足全部模糊匹配需求,二来使用限制太大且使用不当容易形成慢查询,搜索型NoSql的诞生正是为了解决关系型数据库全文搜索能力较弱的问题,ElasticSearch是搜索型NoSql的表明产品。

全文搜索的原理是倒排索引,咱们看一下什么是倒排索引。要说倒排索引咱们先看下什么是正排索引,传统的正排索引是文档-->关键字的映射,例如"Tom is my friend"这句话,会将其切分为"Tom"、"is"、"my"、"friend"四个单词,在搜索的时候对文档进行扫描,符合条件的查出来。这种方式原理很是简单,可是因为其检索效率过低,基本没什么实用价值。

倒排索引则彻底相反,它是关键字-->文档的映射,我用张表格展现一下就比较清楚了:

意思是我如今这里有四个短句:

  • "Tom is Tom"
  • "Tom is my friend"
  • "Thank you, Betty"
  • "Tom is Betty's husband"

 

搜索引擎会根据必定的切分规则将这句话切成N个关键字,并以关键字的维度维护关键字在每一个文本中的出现次数。这样下次搜索"Tom"的时候,因为Tom这个词语在"Tom is Tom"、"Tom is my friend"、"Tom is Betty's husband"三句话中都有出现,所以这三条记录都会被检索出来,且因为"Tom is Tom"这句话中"Tom"出现了2次,所以这条记录对"Tom"这个单词的匹配度最高,最早展现。这就是搜索引擎倒排索引的基本原理,假设某个关键字在某个文档中出现,那么倒排索引中有两部份内容:

  • 文档ID
  • 在该文档中出现的位置状况

 

能够触类旁通,咱们搜索"Betty Tom"这两个词语也是同样,搜索引擎将"Betty Tom"切分为"Tom"、"Betty"两个单词,根据开发者指定的知足率,好比知足率=50%,那么只要记录中出现了两个单词之一的记录都会被检索出来,再按照匹配度进行展现。

搜索型NoSql以ElasticSearch为例,它的优势为:

  • 支持分词场景、全文搜索,这是区别于关系型数据库最大特色
  • 支持条件查询,支持聚合操做,相似关系型数据库的Group By,可是功能更增强大,适合作数据分析
  • 数据写文件无丢失风险,在集群环境下能够方便横向扩展,可承载PB级别的数据
  • 高可用,自动发现新的或者失败的节点,重组和从新平衡数据,确保数据是安全和可访问的

一样,ElasticSearch也有比较明显的缺点:

  • 性能全靠内存来顶,也是使用的时候最须要注意的点,很是吃硬件资源、吃内存,大数据量下64G + SSD基本是标配,算得上是数据库中的爱马仕了。为何要专门提一下内存呢,由于内存这个东西是很值钱的,相同的配置多一倍内存,一个月差很少就要多花几百块钱,至于ElasticSearch内存用在什么地方,大概有以下这些:
    • Indexing Buffer----ElasticSearch基于Luence,Lucene的倒排索引是先在内存里生成,而后按期以Segment File的方式刷磁盘的,每一个Segment File实际就是一个完整的倒排索引
    • Segment Memory----倒排索引前面说过是基于关键字的,Lucene在4.0后会将全部关键字以FST这种数据结构的方式将全部关键字在启动的时候全量加载到内存,加快查询速度,官方建议至少留系统一半内存给Lucene
    • 各种缓存----Filter Cache、Field Cache、Indexing Cache等,用于提高查询分析性能,例如Filter Cache用于缓存使用过的Filter的结果集
    • Cluter State Buffer----ElasticSearch被设计为每一个Node均可以响应用户请求,所以每一个Node的内存中都包含有一份集群状态的拷贝,一个规模很大的集群这个状态信息可能会很是大
  • 读写之间有延迟,写入的数据差很少1s样子会被读取到,这也正常,写入的时候自动加入这么多索引确定影响性能
  • 数据结构灵活性不高,ElasticSearch这个东西,字段一旦创建就无法修改类型了,假如创建的数据表某个字段没有加全文索引,想加上,那么只能把整个表删了再重建

所以,搜索型NoSql最适用的场景就是有条件搜索尤为是全文搜索的场景,做为关系型数据库的一种替代方案。

另外,搜索型数据库还有一种特别重要的应用场景。咱们能够想,一旦对数据库作了分库分表后,原来能够在单表中作的聚合操做、统计操做是否通通失效?例如我把订单表分16个库,1024张表,那么订单数据就散落在1024张表中,我想要统计昨天浙江省单笔成交金额最高的订单是哪笔如何作?我想要把昨天的全部订单按照时间排序分页展现如何作?这就是搜索型NoSql的另外一大做用了,咱们能够把分表以后的数据统一打在搜索型NoSql中,利用搜索型NoSql的搜索与聚合能力完成对全量数据的查询

至于为何把它放在KV型NoSql后面做为第二个写呢,由于一般搜索型NoSql也会做为一层前置缓存,来对关系型数据库进行保护。

 

列式NoSql(表明----HBase)

列式NoSql,大数据时代最具表明性的技术之一了,以HBase为表明。

列式NoSql是基于列式存储的,那么什么是列式存储呢,列式NoSql和关系型数据库同样都有主键的概念,区别在于关系型数据库是按照行组织的数据:

看到每行有name、phone、address三个字段,这是行式存储的方式,且能够观察id = 2的这条数据,即便phone字段没有,它也是占空间的。

列式存储彻底是另外一种方式,它是按每一列进行组织的数据:

这么作有什么好处呢?大体有如下几点:

  • 查询时只有指定的列会被读取,不会读取全部列
  • 存储上节约空间,Null值不会被存储,一列中有时候会有不少重复数据(尤为是枚举数据,性别、状态等),这类数据可压缩,行式数据库压缩率一般在3:1~5:1之间,列式数据库的压缩率通常在8:1~30:1左右
  • 列数据被组织到一块儿,一次磁盘IO能够将一列数据一次性读取到内存中

第二点说到了数据压缩,什么意思呢,以比较常见的字典表压缩方式举例:

本身看图理解一下,应该就懂了。 

接着继续讲讲优缺点,列式NoSql,以HBase为表明的,优势为:

  • 海量数据无限存储,PB级别数据随便存,底层基于HDFS(Hadoop文件系统),数据持久化
  • 读写性能好,只要没有滥用形成数据热点,读写基本随便玩
  • 横向扩展在关系型数据库及非关系型数据库中都是最方便的之一,只须要添加新机器就能够实现数据容量的线性增加,且可用在廉价服务器上,节省成本
  • 自己没有单点故障,可用性高
  • 可存储结构化或者半结构化的数据
  • 列数理论上无限,HBase自己只对列族数量有要求,建议1~3个

说了这么多HBase的优势,又到了说HBase缺点的时候了:

  • HBase是Hadoop生态的一部分,所以它自己是一款比较重的产品,依赖不少Hadoop组件,数据规模不大不必用,运维仍是有点复杂的
  • KV式,不支持条件查询,或者说条件查询很是很是弱吧,HBase在Scan扫描一批数据的状况下仍是提供了前缀匹配这种API的,条件查询除非定义多个RowKey作数据冗余
  • 不支持分页查询,由于统计不了数据总数

所以HBase比较适用于那种KV型的且将来没法预估数据增加量的场景,另外HBase使用仍是须要必定的经验,主要体如今RowKey的设计上。

 

文档型NoSql(表明----MongoDB)

坦白讲,根据个人工做经历,文档型NoSql我只有比较浅的使用经验,所以这部分只能结合以前的使用与网上的文章大体给你们介绍一下。

什么是文档型NoSql呢,文档型NoSql指的是将半结构化数据存储为文档的一种NoSql,文档型NoSql一般以JSON或者XML格式存储数据,所以文档型NoSql是没有Schema的,因为没有Schema的特性,咱们能够随意地存储与读取数据,所以文档型NoSql的出现是解决关系型数据库表结构扩展不方便的问题的

MongoDB是文档型NoSql的表明产品,同时也是全部NoSql产品中的明星产品之一,所以这里以MongoDB为例。按个人理解,做为文档型NoSql,MongoDB是一款彻底和关系型数据库对标的产品,就咱们从存储上来看:

看到,关系型数据库是循序渐进地每一个字段一列存,在MongDB里面就是一个JSON字符串存储。关系型数据能够为name、phone创建索引,MongoDB使用createIndex命令同样能够为列创建索引,创建索引以后能够大大提高查询效率。其余方面而言,就大的基本概念,两者之间基本也是相似的:

所以,对于MongDB,咱们只要理解成一个Free-Schema的关系型数据库就完事了,它的优缺点比较一目了然,优势:

  • 没有预约义的字段,扩展字段容易
  • 相较于关系型数据库,读写性能优越,命中二级索引的查询不会比关系型数据库慢,对于非索引字段的查询则是全面胜出

缺点在于:

  • 不支持事务操做,虽然Mongodb4.0以后宣称支持事务,可是效果待观测
  • 多表之间的关联查询不支持(虽然有嵌入文档的方式),join查询仍是须要屡次操做
  • 空间占用较大,这个是MongDB的设计问题,空间预分配机制 + 删除数据后空间不释放,只有用db.repairDatabase()去修复才能释放
  • 目前没发现MongoDB有关系型数据库例如MySql的Navicat这种成熟的运维工具

总而言之,MongDB的使用场景很大程度上能够对标关系型数据库,可是比较适合处理那些没有join、没有强一致性要求且表Schema会常变化的数据。

 

总结:数据库与NoSql及各类NoSql间的对比

最后一部分,作一个总结,本文归根究竟是两个话题:

  • 什么时候选用关系型数据库,什么时候选用非关系型数据库
  • 选用非关系型数据库,使用哪一种非关系型数据库

首先是第一个话题,关系型数据库与非关系型数据库的选择,在我理解里面无非就是两点考虑:

第一点,很少解释应该都理解,非关系型数据库都是经过牺牲了ACID特性来获取更高的性能的,假设两张表之间有比较强的一致性需求,那么这类数据是不适合放在非关系型数据库中的。

第二点,核心数据不走非关系型数据库,例如用户表、订单表,可是这有一个前提,就是这一类核心数据会有多种查询模式,例如用户表有ABCD四个字段,可能根据AB查,可能根据AC查,可能根据D查,假设核心数据,可是就是个KV形式,好比用户的聊天记录,那么HBase一存就完事了。

这几年的工做经验来看,非核心数据尤为是日志、流水一类中间数据千万不要写在关系型数据库中,这一类数据一般有两个特色:

  • 写远高于读
  • 写入量巨大

一旦使用关系型数据库做为存储引擎,将大大下降关系型数据库的能力,正常读写QPS不高的核心服务会受这一类数据读写的拖累。

接着是第二个问题,若是咱们使用非关系型数据库做为存储引擎,那么如何选型?其实上面的文章基本都写了,这里只是作一个总结(全部的缺点都不会体现事务这个点,由于这是全部NoSql相比关系型数据库共有的一个问题):

可是这里特别说明,选型必定要结合实际状况而不是照本宣科,好比:

  • 企业发展之初,明明一个关系型数据库就能搞定且支撑一年的架构,搞一套大而全的技术方案出来
  • 有一些数据条件查询多,更适合使用ElasticSearch作存储下降关系型数据库压力,可是公司成本有限,这种状况下这类数据能够尝试继续使用关系型数据库作存储
  • 有一类数据格式简单,就是个KV类型且增加量大,可是公司没有HBase这方面的人才,运维上可能会有必定难度,出于实际状况考虑,可先用关系型数据库顶一阵子

因此,若是不考虑实际状况,虽然合适有些存储引擎更加合适,可是强行使用反而拔苗助长,总而言之,适合本身的才是最好的。