從另外一個角度看什么是數(shù)據(jù)庫
本文公眾號來源:柳樹的絮叨叨
作者:SexyCode
數(shù)據(jù)庫是什么?
是 Mysql?Oracle?HBase?
或許你還能想到 Redis、Zookeeper,甚至是 Elasticsearch ……
讓我們從一個文件系統(tǒng)開始。
數(shù)據(jù)庫 1.0 —— 文件系統(tǒng)
我們正在做一個電子書的小程序。
一開始,我們把所有圖書信息都放在 csv 文件中:
Book.csv ( title , author , year )
"Gone with the Wind","Margaret Mitchell",1936"Hamlet","William Shakespeare",1602"活著","余華",1993"三體","劉慈欣",2006
這種存儲方式,實現(xiàn)起來簡單,似乎很完美。
接下來,我們要查詢《三體》的作者,于是寫了這段代碼:
for line in file:record = parse(line)if "三體" == record[0]:print record[1]
我們用了「遍歷」,這是非常糟糕的查詢方式。
一旦后面數(shù)據(jù)量上去了,數(shù)據(jù)被存放在多個文件里,每次查詢,我們就得打開很多個文件,打開后還要遍歷里面的數(shù)據(jù),「磁盤 IO」 和「時間復(fù)雜度」都很高。
問題癥結(jié)在于:我們的數(shù)據(jù),是沒有無規(guī)律的。
一旦數(shù)據(jù)沒有規(guī)律,我們查找數(shù)據(jù)時,就不知道數(shù)據(jù)在哪個文件,就只能一個個文件打開來看,靠蠻力去遍歷。
所以,讓數(shù)據(jù)規(guī)律存儲,是優(yōu)化這個文件系統(tǒng)的第一步。
數(shù)據(jù)庫 2.0 —— 規(guī)律存儲
讓數(shù)據(jù)有規(guī)律的存儲,一旦數(shù)據(jù)有規(guī)律,我們就可以使用各種算法去高效地查找它們。
讓書籍,按照「字典排序」升序存儲,于是我們可以進(jìn)行「二分查找」,時間復(fù)雜度從 O(n) -> O(log2n),缺點是每次插入都要排序;
讓書籍,按照「Hash 表」的結(jié)構(gòu)進(jìn)行存儲,于是我們可以進(jìn)行「Hash 查找」,用空間換時間,時間復(fù)雜度 O(1);
讓書籍,按照「二叉樹」的結(jié)果進(jìn)行存儲,于是我們可以進(jìn)行「二叉查找」,時間復(fù)雜度 O(log2n);
二叉樹極端情況下會退化成 O(n),于是有了「平衡二叉樹」;
平衡二叉樹終究還是“二叉”,只有兩個子節(jié)點,一次從磁盤 load 的數(shù)據(jù)太少,于是有了可以有多于 2 個子節(jié)點的 B 樹;
B 樹找出來的數(shù)據(jù),是無序的,如果你要求數(shù)據(jù)排好序返回,還要在內(nèi)存手動排一次序,于是有了葉子節(jié)點是一個雙向鏈表的 B+ 樹;
……
看到?jīng)],不斷規(guī)律化你的存儲結(jié)構(gòu),你就能得到越來越牛逼的查找性能。
當(dāng)然你會發(fā)現(xiàn),按照「作者」查詢,我建一個 B+ 樹,按照「年份」查詢,我也建一個 B+ 樹,這樣每增加一個字段查詢,我都要建一個 B+ 樹,如果 B+ 樹里面放的是全部數(shù)據(jù)的信息,那會很冗余、很占用空間;
于是我讓 B+ 樹只記錄數(shù)據(jù)的唯一標(biāo)識,按照索引找到數(shù)據(jù)的唯一標(biāo)識后,再去 load 全量的數(shù)據(jù)。
這就是 Mysql 里面的「二級索引」和「聚簇索引」:
「二級索引」只存儲對應(yīng)字段和唯一標(biāo)識,查找時利用「二級索引」,可以快速找到數(shù)據(jù)的「唯一標(biāo)識」;
「聚簇索引」是數(shù)據(jù)實際存儲的位置,它也是有序的,按照「唯一標(biāo)識」有序存儲;
所以你在「二級索引」里拿到「唯一標(biāo)識」后,可以快速地在「聚簇索引」找到數(shù)據(jù)的位置,大大減少了磁盤 IO;

Mysql 有一句話,“索引即數(shù)據(jù)”,指的就是「聚簇索引」,當(dāng)然,如果用到了「覆蓋索引」,那「二級索引」也能提供數(shù)據(jù)。
我們經(jīng)常說,「索引」提高了查找性能,其實不完全正確。
還是以 Mysql 為例,二級索引只是告訴了你數(shù)據(jù)的「唯一標(biāo)識」,但是你還要拿著這個「唯一標(biāo)識」去數(shù)據(jù)里查找,如果這些數(shù)據(jù)本身不是有序的,那你還是得找大半天。于是 Mysql 再弄了個 B+ 樹來存儲數(shù)據(jù),讓這些數(shù)據(jù)有序,也就是「聚簇索引」。
這就像你在字典里查一個單詞 incredible ,你在目錄,也就是索引里,找到這個單詞在第 256 頁,然而,這本書在裝訂的時候,頁面訂亂了,不是按遞增來裝訂的,完全無序,于是乎,就算你知道了 incredible 在第 256 頁,你還是得海底撈針般的,把整本書翻一遍。
「索引」僅僅幫助你快速找到數(shù)據(jù)的標(biāo)識,輔之以「數(shù)據(jù)規(guī)律的存儲」,才能「減少磁盤 IO」,才能「加速查詢」:
索引 + 規(guī)律存儲 = 快速查詢
不過對于 Mysql 來說,它的規(guī)律存儲,是通過「聚簇索引」來實現(xiàn)的,所以說是「索引」讓它查詢變快也對。
數(shù)據(jù)結(jié)構(gòu)帶來了規(guī)律存儲和快速查詢,也帶來了操作的復(fù)雜度。
你再也不能隨意插入數(shù)據(jù),因為你要維護數(shù)據(jù)的規(guī)律性,不管你是順序存儲還是 B+ 樹,都要找到正確的位置進(jìn)行插入;
可能你還想做個緩存來進(jìn)一步減少磁盤 IO,那你得維護好緩存的生命周期,等等 ……
這么多復(fù)雜的邏輯,如果都要讓用戶感知到,自己手動操作,那使用成本太高,每次插入都要寫一大段代碼,于是我們要給用戶提供簡潔的操作方式。
數(shù)據(jù)庫 3.0 —— 簡單操控
幾乎你用過的所有數(shù)據(jù)庫,都會提供讓你很方便的操控它的方式。
像 Mysql、Oracle 等關(guān)系型數(shù)據(jù)庫,操作它們的語言,都是?SQL(Structured Query Language,結(jié)構(gòu)化查詢語言),這是結(jié)構(gòu)化數(shù)據(jù)領(lǐng)域的通用語言,于是我們稱之為?DSL(domain-specific language,領(lǐng)域特定語言):
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 “l(fā)ocalhost:9200/twitter/_doc/0?_source=false&pretty”
DSL、Command、API,其實都是為了方便你使用,降低了你的使用成本,不至于插入個數(shù)據(jù),都要寫一堆代碼。
但是對于你的學(xué)習(xí)成本,卻不一定降低了,反之,可能加大了你的學(xué)習(xí)成本,因為它屏蔽了背后的實現(xiàn)細(xì)節(jié)。
看似簡簡單單的語句背后,觸發(fā)的可能是一連串復(fù)雜的邏輯。
數(shù)據(jù)庫 4.0 —— 隱藏技能
這些復(fù)雜的邏輯,就是數(shù)據(jù)庫的隱藏技能。
一個數(shù)據(jù)庫在隱藏技能上下的功夫,決定了它是 Mysql,還是 Microsoft Access,決定了它能在高性能高可靠的道路上走多遠(yuǎn),決定了它能否被廣泛用到生產(chǎn)環(huán)境。
而對一個數(shù)據(jù)庫隱藏技能了解的程度,也成了衡量一個人對這項知識掌握程度的標(biāo)準(zhǔn)。
在你一行指令的背后,觸發(fā)的隱藏技能,包括但不限于:
事務(wù):事務(wù)具有四個屬性:ACID,當(dāng)然數(shù)據(jù)庫不會完全滿足這四個屬性,有的數(shù)據(jù)庫甚至還不支持事務(wù),比如 Mysql 在 「讀未提交」的隔離級別下,就不滿足「C 隔離性」,對數(shù)據(jù)可靠性要求不高的,比如 redis,它也無需實現(xiàn)事務(wù)(當(dāng)然你可以用各種方法來近似實現(xiàn))。
鎖:和 Java 一樣,有并發(fā)訪問,就有并發(fā)安全,就需要鎖,比如 Mysql 的 MVCC.
集群:這是實現(xiàn)一個高性能高可靠系統(tǒng)的標(biāo)配,你需要對數(shù)據(jù)進(jìn)行冗余和分片存儲,所以,在插入一條數(shù)據(jù)時,你的數(shù)據(jù)庫可能需要判斷要插入到哪一臺機器,插入后,還有判斷要冗余到哪些個機器。
緩存:數(shù)據(jù)不能每次都去磁盤 load,放到緩存,緩存失效了再去磁盤拿,數(shù)據(jù)一旦被更新,緩存就失效嗎?不,數(shù)據(jù)更新時,更新的是緩存的數(shù)據(jù),同時記錄日志,然后再去刷磁盤,Mysql 和 Elasticsearch 都這么做。
……
上文從「文件系統(tǒng)」開始,一步一步演化成一個常用的「數(shù)據(jù)庫」。
這里我用「三個關(guān)鍵字」 + 「三句話」,來給「數(shù)據(jù)庫」下一個演進(jìn)式的、通俗易懂的定義:
規(guī)律存儲的文件系統(tǒng):數(shù)據(jù)庫,是一個把數(shù)據(jù)進(jìn)行「規(guī)律存儲」的文件系統(tǒng);
簡單訪問:它給使用者提供了簡單的操控方式,去訪問(插入、修改、查詢)它的數(shù)據(jù);
隱藏技能:為了做到高性能高可靠,它實現(xiàn)了一系列復(fù)雜的邏輯,這些邏輯對一般使用者來說無需關(guān)心。
我們再來看看維基百科上給「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”
這是學(xué)術(shù)上的定義。
學(xué)術(shù)定義,目的是「給一個通用的解釋」,「劃定邊界」,所以一般會比較抽象。
它告訴你:
數(shù)據(jù)庫是數(shù)據(jù)的有組織的集合,用到了一些設(shè)計和技巧;
數(shù)據(jù)庫管理系統(tǒng)(DBMS),則是給你去訪問數(shù)據(jù)庫的;
它不會告訴你數(shù)據(jù)庫具體怎么組織,用到怎么個技巧,也不會告訴你 DBMS 是怎么去訪問數(shù)據(jù)庫的,因為它只是一個「通用的解釋」,只是給「Database」和「DBMS」劃定邊界。
所以只看定義,是看不出什么的,只有學(xué)習(xí)了具體的知識,然后再反過來看定義,才能看懂、看透,才能摸索出通用的規(guī)律。
你會發(fā)現(xiàn),通常我們在聊「數(shù)據(jù)庫」時,聊得不只是個普通的數(shù)據(jù),而是規(guī)律存儲的數(shù)據(jù),而且還有一個 DBMS,讓我們?nèi)ピL問它:

數(shù)據(jù)庫,是你和數(shù)據(jù)打交道的媒介,你的所有對數(shù)據(jù)的操作,都會通過「數(shù)據(jù)庫」來實現(xiàn)。
于是,從「使用角度」,我再給數(shù)據(jù)庫下另一個通俗的定義:
數(shù)據(jù)庫,是你訪問數(shù)據(jù)的中間件。
選擇哪個中間件,取決于你的使用場景;而選擇哪種數(shù)據(jù)庫,則取決于你對數(shù)據(jù)的使用場景:
如果你需要數(shù)據(jù)安全可靠,最好是用 Mysql 這樣的關(guān)系型數(shù)據(jù)庫;
如果你只是緩存一些臨時數(shù)據(jù),需要快速查詢,不妨用 Redis 這樣的 Key-Value 內(nèi)存數(shù)據(jù)庫;
如果你想放一些文檔,并且還可以支持「相關(guān)性搜索」,那像 Elasticsearch 這樣的搜索引擎,則是你的首選。
接上面一節(jié)給數(shù)據(jù)庫下的定義,我嘗試給數(shù)據(jù)庫學(xué)習(xí)分三個層級:
接觸:了解這個數(shù)據(jù)庫的使用場景,為什么需要它,在什么場合下使用它
使用:如何通過這個數(shù)據(jù)庫操控數(shù)據(jù),了解它的 API/Command/DSL
深入理解:它是如何存儲和索引數(shù)據(jù)的?它是如何做集群和分布式的?還有什么其他讓它高性能高可靠的隱藏技能?
隨便找?guī)讉€數(shù)據(jù)庫驗證上面的學(xué)習(xí)模型:
Zookeeper:
為什么需要 Zookeeper?
如何往 ZK 里插入數(shù)據(jù)、查找數(shù)據(jù)、更新數(shù)據(jù) ……
ZK 是如何存儲數(shù)據(jù)、如何查找數(shù)據(jù)的?ZK 集群中各個節(jié)點如何配合?

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

Elasticsearch:
為什么需要 Elasticsearch ?什么情況下需要用到搜索引擎?
如何往 Elasticsearch 插入數(shù)據(jù)、搜索數(shù)據(jù)、分析數(shù)據(jù)?
Elasticsearch 如何存儲數(shù)據(jù)?如何索引?集群結(jié)構(gòu)長什么樣?

……
實際使用中,經(jīng)常會遇到的問題是:
到底用哪一種數(shù)據(jù)庫?
通常我們會在「關(guān)系型數(shù)據(jù)庫」和各種各樣的「Nosql」之間糾結(jié)。
其實在關(guān)系型數(shù)據(jù)庫(Relational Database)出現(xiàn)之前,還出現(xiàn)過層次結(jié)構(gòu)(hierarchical)和網(wǎng)絡(luò)結(jié)構(gòu)(network)數(shù)據(jù)庫。
從數(shù)據(jù)庫的起源講起,一直聊到各種 Nosql,這樣就弄明白到底要怎么選數(shù)據(jù)庫,為什么會有 Nosql了。
公眾號文章導(dǎo)航:兩年嘔心瀝血的文章!
長按掃碼可關(guān)注獲取?
在看和分享對我非常重要!
