只會寫代碼的是碼農(nóng);學好數(shù)據(jù)庫,基本能混口飯吃;在此基礎(chǔ)上再學好操作系統(tǒng)和計算機網(wǎng)絡(luò),就能當一個不錯的程序員。如果能再把離散數(shù)學、數(shù)字電路、體系結(jié)構(gòu)、數(shù)據(jù)結(jié)構(gòu)/算法、編譯原理學通透,再加上豐富的實踐經(jīng)驗與領(lǐng)域特定知識,就能算是一個優(yōu)秀的工程師了。
計算機其實就是存儲/IO/CPU三大件; 而計算說穿了就是兩個東西:數(shù)據(jù)與算法(狀態(tài)與轉(zhuǎn)移函數(shù))。常見的軟件應(yīng)用,除了各種模擬仿真、模型訓練、視頻游戲這些屬于計算密集型應(yīng)用外,絕大多數(shù)都屬于數(shù)據(jù)密集型應(yīng)用。從最抽象的意義上講,這些應(yīng)用干的事兒就是把數(shù)據(jù)拿進來,存進數(shù)據(jù)庫,需要的時候再拿出來。
抽象是應(yīng)對復(fù)雜度的最強武器。操作系統(tǒng)提供了對存儲的基本抽象:內(nèi)存尋址空間與磁盤邏輯塊號。文件系統(tǒng)在此基礎(chǔ)上提供了文件名到地址空間的KV存儲抽象。而數(shù)據(jù)庫則在其基礎(chǔ)上提供了對應(yīng)用通用存儲需求的高級抽象。
互聯(lián)網(wǎng)應(yīng)用大多屬于數(shù)據(jù)密集型應(yīng)用,對于真實世界的數(shù)據(jù)密集型應(yīng)用而言,除非你準備從基礎(chǔ)組件的輪子造起,不然根本沒那么多機會去擺弄花哨的數(shù)據(jù)結(jié)構(gòu)和算法。甚至寫代碼的本事可能也沒那么重要:可能只會有那么一兩個Ad Hoc算法需要在應(yīng)用層實現(xiàn),大部分需求都有現(xiàn)成的輪子可以使用,主要的創(chuàng)造性工作往往在數(shù)據(jù)模型與數(shù)據(jù)流設(shè)計上。實際生產(chǎn)中,數(shù)據(jù)表就是數(shù)據(jù)結(jié)構(gòu),索引與查詢就是算法。而應(yīng)用代碼往往扮演的是膠水的角色,處理IO與業(yè)務(wù)邏輯,其他大部分工作都是在數(shù)據(jù)系統(tǒng)之間搬運數(shù)據(jù)。
在最寬泛的意義上,有狀態(tài)的地方就有數(shù)據(jù)庫。它無所不在,網(wǎng)站的背后、應(yīng)用的內(nèi)部,單機軟件,區(qū)塊鏈里,甚至在離數(shù)據(jù)庫最遠的Web瀏覽器中,也逐漸出現(xiàn)了其雛形:各類狀態(tài)管理框架與本地存儲。“數(shù)據(jù)庫”可以簡單地只是內(nèi)存中的哈希表/磁盤上的日志,也可以復(fù)雜到由多種數(shù)據(jù)系統(tǒng)集成而來。關(guān)系型數(shù)據(jù)庫只是數(shù)據(jù)系統(tǒng)的冰山一角(或者說冰山之巔),實際上存在著各種各樣的數(shù)據(jù)系統(tǒng)組件:
數(shù)據(jù)庫:存儲數(shù)據(jù),以便自己或其他應(yīng)用程序之后能再次找到(PostgreSQL,MySQL,Oracle)緩存:記住開銷昂貴操作的結(jié)果,加快讀取速度(Redis,Memcached)搜索索引:允許用戶按關(guān)鍵字搜索數(shù)據(jù),或以各種方式對數(shù)據(jù)進行過濾(ElasticSearch)流處理:向其他進程發(fā)送消息,進行異步處理(Kafka,F(xiàn)link,Storm)批處理:定期處理累積的大批量數(shù)據(jù)(Hadoop)架構(gòu)師最重要的能力之一,就是了解這些組件的性能特點與應(yīng)用場景,能夠靈活地權(quán)衡取舍、集成拼接這些數(shù)據(jù)系統(tǒng)。絕大多數(shù)工程師都不會去從零開始編寫存儲引擎,因為在開發(fā)應(yīng)用時,數(shù)據(jù)庫已經(jīng)是足夠完美的工具了。關(guān)系型數(shù)據(jù)庫則是目前所有數(shù)據(jù)系統(tǒng)中使用最廣泛的組件,可以說是程序員吃飯的主要家伙,重要性不言而喻。
對玩具應(yīng)用而言,使用內(nèi)存變量與文件來保存狀態(tài)也許已經(jīng)綽綽有余了。但隨著系統(tǒng)的增長,我們會遇到越來越多的挑戰(zhàn):軟硬件故障把數(shù)據(jù)搞成一團漿糊(可靠性);狀態(tài)太多而內(nèi)存太小放不下(可伸縮性);并發(fā)訪問控制導(dǎo)致代碼復(fù)雜度發(fā)生爆炸(可維護性),諸如此類。這些問題相當棘手,卻又相當普遍,數(shù)據(jù)庫就是用來解決這些問題的。分拆是架構(gòu)演化的重要方法論,數(shù)據(jù)庫將狀態(tài)管理的職能從應(yīng)用程序中分拆出來,即所謂的“狀態(tài)與計算相分離”。數(shù)據(jù)庫將程序員從重復(fù)造輪子的泥潭中解救出來,極大地解放了生產(chǎn)力。
每個系統(tǒng)都服務(wù)于一個目的,解決一類問題。問題比方法更重要。但現(xiàn)實很遺憾,以大多數(shù)學生,甚至相當一部分公司能接觸到的現(xiàn)實問題而言,拿幾個文件甚至在內(nèi)存里放著估計都能應(yīng)付大多數(shù)場景了(需求簡單到低級抽象就可以Handle)。沒什么機會接觸到數(shù)據(jù)庫真正要解決的問題,也就難有真正使用與學習數(shù)據(jù)庫的驅(qū)動力,更別提數(shù)據(jù)庫原理了。
所以我也理解當前這種填鴨教學現(xiàn)狀的苦衷:工作之后很難有這么大把的完整時間來學習原理了,所以老師只好先使勁灌輸,多少讓學生對這些知識有個印象。等學生參加工作后真正遇到這些問題,也許會想起大學好像還學了個叫數(shù)據(jù)庫的東西,這些知識就會開始反芻。
數(shù)據(jù)庫,尤其是關(guān)系型數(shù)據(jù)庫,非常重要。那為什么要學習其原理呢?
對優(yōu)秀的工程師來說,只會用數(shù)據(jù)庫是遠遠不夠的。學習原理對于當CRUD BOY搬磚收益并不大,但當通用組件真的無解需要自己擼起袖子上時,沒有金坷垃怎么種莊稼?設(shè)計系統(tǒng)時,理解原理能讓你以最少的復(fù)雜度代價寫出更可靠高效的代碼;遇到疑難雜癥需要排查時,理解原理能帶來精準的直覺與深刻的洞察。
數(shù)據(jù)庫是一個博大精深的領(lǐng)域,存儲I/O計算無所不包。其主要原理也可以粗略分為幾個部分:數(shù)據(jù)模型設(shè)計原理(應(yīng)用)、存儲引擎原理(基礎(chǔ))、索引與查詢優(yōu)化器的原理(性能)、事務(wù)與并發(fā)控制的原理(正確性)、故障恢復(fù)與復(fù)制系統(tǒng)的原理(可靠性)。 所有的原理都有其存在意義:為了解決實際問題。
例如數(shù)據(jù)模型設(shè)計中的范式理論,就是為了解決數(shù)據(jù)冗余這一問題而提出的,它是為了把事情做漂亮(可維護)。它是模型設(shè)計中一個很重要的設(shè)計權(quán)衡:通常而言,冗余少則復(fù)雜度小/可維護性強,冗余高則性能好。具體來說,冗余字段能加快特定類型的讀取(通過消除連接),但在寫入時就需要做更多的工作:維護多對象副本間的一致性,避免多對象事務(wù)并發(fā)執(zhí)行時發(fā)生踩踏。這就需要仔細權(quán)衡利弊,選擇合適的規(guī)范化等級。數(shù)據(jù)模型設(shè)計,就是生產(chǎn)中的數(shù)據(jù)結(jié)構(gòu)設(shè)計。不了解這些原理,就難以提取良好的抽象,其他工作也就無從談起。
而關(guān)系代數(shù)與索引的原理,則在查詢優(yōu)化中扮演重要的角色,它是為了把事情做得快(性能,可擴展)。當數(shù)據(jù)量越來越大,SQL寫的越來越復(fù)雜時,它的意義就會體現(xiàn)出來:怎樣寫出等價但是更高效的查詢?當查詢優(yōu)化器沒那么智能時,就需要人來干這件事。這種優(yōu)化往往有四兩撥千斤的效果,比如一個需要幾秒的KNN查詢,如果知道R樹索引的原理,就可以通過改寫查詢,創(chuàng)建GIST索引優(yōu)化到1毫秒內(nèi),千倍的性能提升。不了解索引與查詢設(shè)計原理,就難以充分發(fā)揮數(shù)據(jù)庫的性能。
事務(wù)與并發(fā)控制的原理,是為了把事情做正確。事務(wù)是數(shù)據(jù)處理領(lǐng)域最偉大的抽象之一,它提供了很多有用的保證(ACID),但這些保證到底意味著什么?事務(wù)的原子性讓你在提交前能隨時中止事務(wù)并丟棄所有寫入,相應(yīng)地,事務(wù)的持久性則承諾一旦事務(wù)成功提交,即使發(fā)生硬件故障或數(shù)據(jù)庫崩潰,寫入的任何數(shù)據(jù)也不會丟失。這讓錯誤處理變得無比簡單,所有可能的結(jié)果被歸結(jié)為兩種情況:要么成功完事,要么失敗了事(或重試)。有了后悔藥,程序員不用再擔心半路翻車會留下慘不忍睹的車禍現(xiàn)場了。
另一方面,事務(wù)的隔離性則保證同時執(zhí)行的事務(wù)無法相互影響(在可序列化隔離等級下)。更進一步,數(shù)據(jù)庫提供了不同的隔離等級保證,以供程序員在性能與正確性之間進行權(quán)衡。編寫并發(fā)程序并不容易,在幾萬TPS的負載下,各種極低概率,匪夷所思的問題都會出現(xiàn):事務(wù)之間相互踩踏,丟失更新,幻讀與寫入偏差,慢查詢拖慢快查詢導(dǎo)致連接堆積,單表數(shù)據(jù)庫并發(fā)增大后的性能急劇惡化,比如我遇到的一個最靈異的例子是:快慢查詢總量都減少,但因相對比例變化導(dǎo)致數(shù)據(jù)庫被壓垮。這些問題,在低負載的情況下會潛伏著,隨著規(guī)模量級增長突然跳出來,給你一個大大的驚喜。現(xiàn)實中真正可能出現(xiàn)的各類異常,也絕非SQL標準中簡單的幾種異常能說清的。 不理解事務(wù)的原理,意味著應(yīng)用的正確性與數(shù)據(jù)的完整性可能遭受不必要的損失。
故障恢復(fù)與復(fù)制的原理,可能對于普通程序員沒有那么重要,但架構(gòu)師與DBA必須清楚。高可用是很多應(yīng)用的追求目標,但什么是高可用,高可用怎么保證?讀寫分離?快慢分離?異地多活?x地x中心?說穿了底下的核心技術(shù)其實就是復(fù)制(Replication)(或再加上自動故障切換(Failover))。這里有無窮無盡的坑:復(fù)制延遲帶來的各種靈異現(xiàn)象,網(wǎng)絡(luò)分區(qū)與腦裂,存疑事務(wù) ,諸如此類。 不理解復(fù)制的原理,高可用就無從談起。
對于一些程序員而言,可能數(shù)據(jù)庫就是“增刪改查”,包一包接口,原理似乎屬于“屠龍之技”。如果止步于此,那原理確實沒什么好學的,但有志者應(yīng)當打破砂鍋問到底的精神。私認為只了解自己本領(lǐng)域知識是不夠的,只有把當前領(lǐng)域賴以建立的上層領(lǐng)域摸清楚,才能稱為專家。在數(shù)據(jù)庫面前,后端也是前端;對于程序員的知識棧而言,數(shù)據(jù)庫是一個合適的棧底。
上面講了WHY,下面就說一下 HOW
數(shù)據(jù)庫教學的一個矛盾是:如果連數(shù)據(jù)庫都不會用,那學數(shù)據(jù)庫原理有個卵用呢?
學數(shù)據(jù)庫的原則是學以致用。只有實踐,才能帶來對問題的深刻理解;只有先知其然,才有條件去知其所以然。教材可以先草草的過一遍,然后直接去看數(shù)據(jù)庫文檔,上手去把數(shù)據(jù)庫用起來,做個東西出來。通過實踐掌握數(shù)據(jù)庫的使用,再去學習原理就會事半功倍(以及充滿動力)。對于學習而言,有條件去實習當然最好,沒有條件那最好的辦法就是自己創(chuàng)造場景,自己挖掘需求。
比如,從解決個人需求開始:管理個人密碼,體重跟蹤,記賬,做個小網(wǎng)站、在線聊天App,實用微信小程序。當它演化的越來越復(fù)雜,開始有多個用戶,出現(xiàn)各種蛋疼問題之后,你就會開始意識到事務(wù)的意義。
再比如,結(jié)合爬蟲,抓一些房價、股價、地理、社交網(wǎng)絡(luò)的數(shù)據(jù)存在數(shù)據(jù)庫里,做一些挖掘與分析。當你積累的數(shù)據(jù)越來越多,分析查詢越來越復(fù)雜;SQL長得沒法讀,跑起來慢出豬叫,這時候關(guān)系代數(shù)的理論就能指導(dǎo)你進一步進行優(yōu)化。
當你意識到這些設(shè)計都是為了解決現(xiàn)實生產(chǎn)中的問題,并親自遇到過這些問題之后,再去學習原理,才能相互印證,并知其所以然。當你發(fā)現(xiàn)查詢時間隨數(shù)據(jù)增長而指數(shù)增長時;當你遇到成千上萬的用戶同時讀寫為并發(fā)控制焦頭爛額時;當你碰上軟硬件故障把數(shù)據(jù)攪得稀巴爛時;當你發(fā)現(xiàn)數(shù)據(jù)冗余讓代碼復(fù)雜度快速爆炸時;你就會發(fā)現(xiàn)這些設(shè)計存在的意義。
教材、書籍、文檔、視頻、郵件組、博客都是很好的學習資源。教材的話華章的黑皮系列教材都還不錯,《數(shù)據(jù)庫系統(tǒng)概念》這本就挺好的。但我推薦先看看這本書:《設(shè)計數(shù)據(jù)密集型應(yīng)用》 ,寫的非常好,我覺得不錯就義務(wù)翻譯了一下。
紙上得來終覺淺,絕知此事要躬行。寫了這么多,不帶點“私貨”也不合適哈?實踐方能出真知,新手上路選哪家?我個人推薦PostgreSQL,如果能再選一樣就加個Redis。對開發(fā)而言,這是相當實用的組合。PostgreSQL號稱世界上最先進的開源關(guān)系型數(shù)據(jù)庫,源代碼寫的非常漂亮,有很多值得學習的地方。很多國外的數(shù)據(jù)庫課程與教科書都使用PostgreSQL作為教學樣例。
PostgreSQL在現(xiàn)實世界中也表現(xiàn)不俗,在我們的實踐中,在250WTPS與200TB數(shù)據(jù)的量級下,單一PostgreSQL選型依然能穩(wěn)如狗地支撐業(yè)務(wù)。而且其功能豐富到不可思議,能在很可觀的規(guī)模內(nèi)做到一專多長,除了本職的OLTP,Pg還在相當長的時間里兼任了緩存,OLAP,批處理,甚至消息隊列的角色。當然如“架構(gòu)演進”一圖所示,神龜雖壽,猶有竟時。最終這些兼職功能還是要逐漸分拆出去由專用組件負責,但那已經(jīng)是近千萬日活時的事了。
所以,關(guān)系型數(shù)據(jù)庫雖然強大,卻絕非數(shù)據(jù)處理的終章。數(shù)據(jù)庫的世界非常精彩,盡可能地去嘗試各種各樣的組件吧~。