僅用幾行代碼就擼了個數(shù)據(jù)庫!
本文轉(zhuǎn)載自微信公眾號「小菜學編程」,作者fasionchan 。轉(zhuǎn)載本文請聯(lián)系小菜學編程公眾號。
最近重讀《數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計》這本書,看到第三章《數(shù)據(jù)存儲與檢索》,主要講數(shù)據(jù)庫內(nèi)部的索引技術(shù)。
從本質(zhì)上講,數(shù)據(jù)庫主要是做兩件事情:
- 當你給它數(shù)據(jù)時,它幫你保存數(shù)據(jù)(存儲);
- 當你查詢數(shù)據(jù)時,它幫你取回數(shù)據(jù)(檢索);
這兩件事情看似簡單,背后卻暗含玄機。那么,數(shù)據(jù)庫內(nèi)部到底是如何存儲數(shù)據(jù)的呢?又是如何檢索數(shù)據(jù)的呢?
你可能會有這樣的疑問:我作為一個開發(fā)人員,為什么需要知道數(shù)據(jù)庫內(nèi)部是怎么工作的呢?
我們顯然不會自己從零開始,擼一個存儲引擎。但由于市面上有太多數(shù)據(jù)庫產(chǎn)品,我們需要從中挑一個最適合自己應(yīng)用場景的。為了提高性能,我們也需要根據(jù)應(yīng)用負載特征,對數(shù)據(jù)庫進行調(diào)優(yōu)。因此,必須對數(shù)據(jù)庫底層技術(shù)有一些了解,知道它在背后都干了什么。
根據(jù)應(yīng)用負載特征,數(shù)據(jù)庫大致可以分為兩種:
- 一種針對 在線事務(wù)型 業(yè)務(wù),多采用 行式存儲 方式;
- 一種針對 離線分析型 業(yè)務(wù),多采用 列式存儲 方式;
為了提高檢索速度,索引必不可少。不同存儲引擎采用的索引技術(shù)也不太一樣,大致可以分為兩種:
- 日志式存儲引擎( log-structured ),比如 LSM樹 ;
- 頁式存儲引擎( page-oriented ),比如 B樹 ;
為了引出這些概念,作者用幾行 Shell 代碼,寫了一個玩具數(shù)據(jù)庫拋磚引玉:
- #!/bin/bash
- db_set() {
- echo "$1,$2" >> database
- }
- db_get() {
- grep "^$1," database | sed -e "s/^$1,//" | tail -n 1
- }
這兩個簡短的 Shell 函數(shù)實現(xiàn)了一個 KV 式數(shù)據(jù)庫引擎。麻雀雖小,五臟俱全!它提供了兩個基本操作:
- db_set ,保存鍵值對數(shù)據(jù),即將鍵值用逗號分隔追加到名為 database 的數(shù)據(jù)文件;
- db_get ,按鍵取出對應(yīng)的數(shù)據(jù),即從 database 文件中過濾出最后一行包含指定鍵的數(shù)據(jù);
這個玩具數(shù)據(jù)庫可以這樣用,比如保存鍵值對數(shù)據(jù):
- fasion@SmartPro [ ~ ] ➜ db_set 1 fasionchan.com
- fasion@SmartPro [ ~ ] ➜ db_set 2 小菜學編程
- fasion@SmartPro [ ~ ] ➜ db_set 3 Python源碼剖析
數(shù)據(jù)最終保存在 database 文件中,每個鍵值對一行,格式大致長這樣:
- fasion@SmartPro [ ~ ] ➜ cat database
- 1,fasionchan.com
- 2,小菜學編程
- 3,Python源碼剖析
隨后可以這樣查詢數(shù)據(jù):
- fasion@SmartPro [ ~ ] ➜ db_get 1
- fasionchan.com
- fasion@SmartPro [ ~ ] ➜ db_get 3
- Python源碼剖析
修改數(shù)據(jù)時,新記錄被追加到 database 文件末尾,舊數(shù)據(jù)不會刪:
- fasion@SmartPro [ ~ ] ➜ db_set 3 Python源碼深度剖析
- fasion@SmartPro [ ~ ] ➜ cat database
- 1,fasionchan.com
- 2,小菜學編程
- 3,Python源碼剖析
- 3,Python源碼深度剖析
因此,我們查詢數(shù)據(jù)時,db_set 最后需要執(zhí)行 tail 命令,以最新的那條為準:
- fasion@SmartPro [ ~ ] ➜ db_get 3
- Python源碼深度剖析
這個玩具看上去挺有意思的,但它的性能到底如何呢?
db_set 操作的性能非常好,因為它只是將數(shù)據(jù)記錄追加到 database 文件的末尾,這通常都很快。
跟 db_set 類似,很多數(shù)據(jù)庫內(nèi)部也有一個只追加的數(shù)據(jù)文件,一般叫做操作日志。雖然實際數(shù)據(jù)庫需要考慮更多因素,包括并發(fā)控制、磁盤空間重用、錯誤處理等等,但基本原理都是一樣的。
然而,如果數(shù)據(jù)庫中有大量數(shù)據(jù),db_get 操作的性能會非常差。因為每次你查詢一個鍵,db_get 都必須掃描整個數(shù)據(jù)庫文件!這是一個典型的 操作,數(shù)據(jù)庫記錄數(shù)增加一倍,查詢開銷就會增大一倍!這可不太好!
為了在數(shù)據(jù)庫中快速檢索數(shù)據(jù),我們還需要另一個數(shù)據(jù)結(jié)構(gòu):索引 。索引是一些額外的元數(shù)據(jù)( metadata ),就像路標一樣,可以幫我們快速定位數(shù)據(jù)。如果你的數(shù)據(jù)有多種查詢條件,則可能需要建多個索引。
索引可以加快查詢,但也會帶來額外的開銷,特別是對寫操作。任何索引都會降低寫性能,因為每次數(shù)據(jù)寫入后,都要更新索引。因此,每個存儲系統(tǒng)都需要根據(jù)實際情況做出權(quán)衡:
- 索引選得好可以加快查詢;
- 索引一定會降低寫性能;
因此,數(shù)據(jù)庫通常不會默認建好索引,需要開發(fā)人員或數(shù)據(jù)庫管理員自行選擇。想要以最小的代價換取最大收益,不僅需要對應(yīng)用的查詢模式有準確把握,還需要對數(shù)據(jù)庫內(nèi)部索引技術(shù)有一定了解。