從另外一個角度看什麼是數據庫

  • 2019 年 10 月 7 日
  • 筆記

作者:SexyCode

數據庫是什麼?

是 Mysql?Oracle?HBase?

或許你還能想到 Redis、Zookeeper,甚至是 Elasticsearch ……

讓我們從一個文件系統開始。

數據庫 1.0 —— 文件系統

我們正在做一個電子書的小程序。

一開始,我們把所有圖書信息都放在 csv 文件中:

Book.csv ( title , author , year )

"Gone with the Wind","Margaret Mitchell",1936  "Hamlet","William Shakespeare",1602  "活着","余華",1993  "三體","劉慈欣",2006

這種存儲方式,實現起來簡單,似乎很完美。

接下來,我們要查詢《三體》的作者,於是寫了這段代碼:

for line in file:    record = parse(line)        if "三體" == record[0]:            print record[1]

我們用了「遍歷」,這是非常糟糕的查詢方式。

一旦後面數據量上去了,數據被存放在多個文件里,每次查詢,我們就得打開很多個文件,打開後還要遍歷裏面的數據,「磁盤 IO」 和「時間複雜度」都很高。

問題癥結在於:我們的數據,是沒有無規律的。

一旦數據沒有規律,我們查找數據時,就不知道數據在哪個文件,就只能一個個文件打開來看,靠蠻力去遍歷。

所以,讓數據規律存儲,是優化這個文件系統的第一步。

數據庫 2.0 —— 規律存儲

讓數據有規律的存儲,一旦數據有規律,我們就可以使用各種算法去高效地查找它們。

讓書籍,按照「字典排序」升序存儲,於是我們可以進行「二分查找」,時間複雜度從 O(n) -> O(log2n),缺點是每次插入都要排序;

讓書籍,按照「Hash 表」的結構進行存儲,於是我們可以進行「Hash 查找」,用空間換時間,時間複雜度 O(1);

讓書籍,按照「二叉樹」的結果進行存儲,於是我們可以進行「二叉查找」,時間複雜度 O(log2n);

二叉樹極端情況下會退化成 O(n),於是有了「平衡二叉樹」;

平衡二叉樹終究還是「二叉」,只有兩個子節點,一次從磁盤 load 的數據太少,於是有了可以有多於 2 個子節點的 B 樹;

B 樹找出來的數據,是無序的,如果你要求數據排好序返回,還要在內存手動排一次序,於是有了葉子節點是一個雙向鏈表的 B+ 樹;

……

看到沒,不斷規律化你的存儲結構,你就能得到越來越牛逼的查找性能。

當然你會發現,按照「作者」查詢,我建一個 B+ 樹,按照「年份」查詢,我也建一個 B+ 樹,這樣每增加一個字段查詢,我都要建一個 B+ 樹,如果 B+ 樹裏面放的是全部數據的信息,那會很冗餘、很佔用空間;

於是我讓 B+ 樹只記錄數據的唯一標識,按照索引找到數據的唯一標識後,再去 load 全量的數據。

這就是 Mysql 裏面的「二級索引」和「聚簇索引」:

  • 「二級索引」只存儲對應字段和唯一標識,查找時利用「二級索引」,可以快速找到數據的「唯一標識」;
  • 「聚簇索引」是數據實際存儲的位置,它也是有序的,按照「唯一標識」有序存儲;
  • 所以你在「二級索引」里拿到「唯一標識」後,可以快速地在「聚簇索引」找到數據的位置,大大減少了磁盤 IO;

Mysql 有一句話,「索引即數據」,指的就是「聚簇索引」,當然,如果用到了「覆蓋索引」,那「二級索引」也能提供數據。

我們經常說,「索引」提高了查找性能,其實不完全正確。

還是以 Mysql 為例,二級索引只是告訴了你數據的「唯一標識」,但是你還要拿着這個「唯一標識」去數據里查找,如果這些數據本身不是有序的,那你還是得找大半天。於是 Mysql 再弄了個 B+ 樹來存儲數據,讓這些數據有序,也就是「聚簇索引」。

這就像你在字典里查一個單詞 incredible ,你在目錄,也就是索引里,找到這個單詞在第 256 頁,然而,這本書在裝訂的時候,頁面訂亂了,不是按遞增來裝訂的,完全無序,於是乎,就算你知道了 incredible 在第 256 頁,你還是得海底撈針般的,把整本書翻一遍。

「索引」僅僅幫助你快速找到數據的標識,輔之以「數據規律的存儲」,才能「減少磁盤 IO」,才能「加速查詢」:

索引 + 規律存儲 = 快速查詢

不過對於 Mysql 來說,它的規律存儲,是通過「聚簇索引」來實現的,所以說是「索引」讓它查詢變快也對。

數據結構帶來了規律存儲和快速查詢,也帶來了操作的複雜度。

你再也不能隨意插入數據,因為你要維護數據的規律性,不管你是順序存儲還是 B+ 樹,都要找到正確的位置進行插入;

可能你還想做個緩存來進一步減少磁盤 IO,那你得維護好緩存的生命周期,等等 ……

這麼多複雜的邏輯,如果都要讓用戶感知到,自己手動操作,那使用成本太高,每次插入都要寫一大段代碼,於是我們要給用戶提供簡潔的操作方式。

數據庫 3.0 —— 簡單操控

幾乎你用過的所有數據庫,都會提供讓你很方便的操控它的方式。

像 Mysql、Oracle 等關係型數據庫,操作它們的語言,都是 SQL(Structured Query Language,結構化查詢語言),這是結構化數據領域的通用語言,於是我們稱之為 DSL(domain-specific language,領域特定語言):

INSERT INTO Customer (FirstName, LastName, City, Country, Phone) VALUES (『Craig』, 『Smith』, 『New York』, 『USA』, 1-01-993 2800)

而像 redis,它也定義了自己的一套語言,但是它比較謙虛,自稱為 Command :

redis> SET mykey 「Hello」 「OK」 redis> GET mykey 「Hello」

也有像 Elasticsearch 一樣直接提供 Restful API 的:

curl -X GET 「localhost:9200/twitter/_doc/0?_source=false&pretty」

DSL、Command、API,其實都是為了方便你使用,降低了你的使用成本,不至於插入個數據,都要寫一堆代碼。

但是對於你的學習成本,卻不一定降低了,反之,可能加大了你的學習成本,因為它屏蔽了背後的實現細節。

看似簡簡單單的語句背後,觸發的可能是一連串複雜的邏輯。

數據庫 4.0 —— 隱藏技能

這些複雜的邏輯,就是數據庫的隱藏技能。

一個數據庫在隱藏技能上下的功夫,決定了它是 Mysql,還是 Microsoft Access,決定了它能在高性能高可靠的道路上走多遠,決定了它能否被廣泛用到生產環境。

而對一個數據庫隱藏技能了解的程度,也成了衡量一個人對這項知識掌握程度的標準。

在你一行指令的背後,觸發的隱藏技能,包括但不限於:

  • 事務:事務具有四個屬性:ACID,當然數據庫不會完全滿足這四個屬性,有的數據庫甚至還不支持事務,比如 Mysql 在 「讀未提交」的隔離級別下,就不滿足「C 隔離性」,對數據可靠性要求不高的,比如 redis,它也無需實現事務(當然你可以用各種方法來近似實現)。
  • 鎖:和 Java 一樣,有並發訪問,就有並發安全,就需要鎖,比如 Mysql 的 MVCC.
  • 集群:這是實現一個高性能高可靠系統的標配,你需要對數據進行冗餘和分片存儲,所以,在插入一條數據時,你的數據庫可能需要判斷要插入到哪一台機器,插入後,還有判斷要冗餘到哪些個機器。
  • 緩存:數據不能每次都去磁盤 load,放到緩存,緩存失效了再去磁盤拿,數據一旦被更新,緩存就失效嗎?不,數據更新時,更新的是緩存的數據,同時記錄日誌,然後再去刷磁盤,Mysql 和 Elasticsearch 都這麼做。
  • ……

所以數據庫到底是什麼?

上文從「文件系統」開始,一步一步演化成一個常用的「數據庫」。

這裡我用「三個關鍵字」 + 「三句話」,來給「數據庫」下一個演進式的、通俗易懂的定義:

  • 規律存儲的文件系統:數據庫,是一個把數據進行「規律存儲」的文件系統;
  • 簡單訪問:它給使用者提供了簡單的操控方式,去訪問(插入、修改、查詢)它的數據;
  • 隱藏技能:為了做到高性能高可靠,它實現了一系列複雜的邏輯,這些邏輯對一般使用者來說無需關心。

我們再來看看維基百科上給「Database」和「DBMS」的定義:

Database A database is an organized collection of data, generally stored and accessed electronically from a computer system. Where databases are more complex they are often developed using formal design and modeling techniques. Database management system Connolly and Begg define Database Management System (DBMS) as a 「software system that enables users to define, create, maintain and control access to the database」

這是學術上的定義。

學術定義,目的是「給一個通用的解釋」,「劃定邊界」,所以一般會比較抽象。

它告訴你:

  • 數據庫是數據的有組織的集合,用到了一些設計和技巧;
  • 數據庫管理系統(DBMS),則是給你去訪問數據庫的;

它不會告訴你數據庫具體怎麼組織,用到怎麼個技巧,也不會告訴你 DBMS 是怎麼去訪問數據庫的,因為它只是一個「通用的解釋」,只是給「Database」和「DBMS」劃定邊界。

所以只看定義,是看不出什麼的,只有學習了具體的知識,然後再反過來看定義,才能看懂、看透,才能摸索出通用的規律。

你會發現,通常我們在聊「數據庫」時,聊得不只是個普通的數據,而是規律存儲的數據,而且還有一個 DBMS,讓我們去訪問它:

數據庫,是你和數據打交道的媒介,你的所有對數據的操作,都會通過「數據庫」來實現。

於是,從「使用角度」,我再給數據庫下另一個通俗的定義:

數據庫,是你訪問數據的中間件。

選擇哪個中間件,取決於你的使用場景;而選擇哪種數據庫,則取決於你對數據的使用場景:

  • 如果你需要數據安全可靠,最好是用 Mysql 這樣的關係型數據庫;
  • 如果你只是緩存一些臨時數據,需要快速查詢,不妨用 Redis 這樣的 Key-Value 內存數據庫;
  • 如果你想放一些文檔,並且還可以支持「相關性搜索」,那像 Elasticsearch 這樣的搜索引擎,則是你的首選。

如何學習一個數據庫

接上面一節給數據庫下的定義,我嘗試給數據庫學習分三個層級:

  • 接觸:了解這個數據庫的使用場景,為什麼需要它,在什麼場合下使用它
  • 使用:如何通過這個數據庫操控數據,了解它的 API/Command/DSL
  • 深入理解:它是如何存儲和索引數據的?它是如何做集群和分佈式的?還有什麼其他讓它高性能高可靠的隱藏技能?

隨便找幾個數據庫驗證上面的學習模型:

Zookeeper:

  • 為什麼需要 Zookeeper?
  • 如何往 ZK 里插入數據、查找數據、更新數據 ……
  • ZK 是如何存儲數據、如何查找數據的?ZK 集群中各個節點如何配合?

Redis:

  • Redis是做緩存的,這個基本都知道,於是你可以了解下什麼時候要用到緩存,它相比其他緩存中間件具有的優勢
  • 如何往 Redis 插入數據、更新數據、查詢數據 ……
  • Redis 各種數據類型的數據都是怎麼存儲的?為什麼可以那麼快找到數據?Redis 的分片和主從是如何實現的?

Elasticsearch:

  • 為什麼需要 Elasticsearch ?什麼情況下需要用到搜索引擎?
  • 如何往 Elasticsearch 插入數據、搜索數據、分析數據?
  • Elasticsearch 如何存儲數據?如何索引?集群結構長什麼樣?

……

實際使用中,經常會遇到的問題是:

到底用哪一種數據庫?

通常我們會在「關係型數據庫」和各種各樣的「Nosql」之間糾結。

其實在關係型數據庫(Relational Database)出現之前,還出現過層次結構(hierarchical)和網絡結構(network)數據庫。

從數據庫的起源講起,一直聊到各種 Nosql,這樣就弄明白到底要怎麼選數據庫,為什麼會有 Nosql了。

Exit mobile version