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這方面的人才,運維上可能會有必定難度,出於實際狀況考慮,可先用關係型數據庫頂一陣子

因此,若是不考慮實際狀況,雖然合適有些存儲引擎更加合適,可是強行使用反而拔苗助長,總而言之,適合本身的纔是最好的。

相關文章
相關標籤/搜索