從另外一個角度看什麼是資料庫
- 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了。