又一門國(guó)產(chǎn)數(shù)據(jù)庫(kù)語(yǔ)言誕生了,比SQL還好用!
時(shí)間:2023-03-13 09:50:02 | 來源:電子商務(wù)
時(shí)間:2023-03-13 09:50:02 來源:電子商務(wù)
數(shù)據(jù)庫(kù)語(yǔ)言的目的
為了明確這個(gè)目標(biāo),我們必須首先了解數(shù)據(jù)庫(kù)是做什么的。
這個(gè)數(shù)據(jù)庫(kù)軟件,名字里帶著“庫(kù)”字,會(huì)讓人覺得它主要是用來存儲(chǔ)的。其實(shí)數(shù)據(jù)庫(kù)有兩個(gè)重要的功能:
計(jì)算和事務(wù)!也就是我們常說的OLAP和OLTP,數(shù)據(jù)庫(kù)的存儲(chǔ)是為這兩樣?xùn)|西服務(wù)的,簡(jiǎn)單的存儲(chǔ)不是數(shù)據(jù)庫(kù)的目標(biāo)。
我們知道SQL是目前數(shù)據(jù)庫(kù)的主流語(yǔ)言。那么,用SQL做這兩件事方便嗎?
事務(wù)函數(shù)主要解決寫和讀的數(shù)據(jù)一致性問題。實(shí)現(xiàn)這個(gè)并不難,但是應(yīng)用程序的界面很簡(jiǎn)單,操縱數(shù)據(jù)庫(kù)讀寫的代碼也很簡(jiǎn)單。假設(shè)目前關(guān)系數(shù)據(jù)庫(kù)的邏輯存儲(chǔ)方式是合理的(即使用表和記錄來存儲(chǔ)數(shù)據(jù),其合理性是另一個(gè)復(fù)雜的問題,這里就不展開了),那么SQL在描述事務(wù)類的功能方面沒有大的問題,因?yàn)椴恍枰枋鰪?fù)雜的動(dòng)作,復(fù)雜性在數(shù)據(jù)庫(kù)中就解決了。
但是計(jì)算類的功能不一樣。
這里說的計(jì)算是一個(gè)更寬泛的概念,不僅僅是簡(jiǎn)單的加減乘除,搜索和關(guān)聯(lián)都可以看作是某種計(jì)算。
什么樣的計(jì)算系統(tǒng)好?
兩點(diǎn):簡(jiǎn)單、快速。
寫的簡(jiǎn)單易懂,就是程序員寫代碼快,單位時(shí)間就能做更多的工作;跑得越快,越容易理解。當(dāng)然,我們希望在更短的時(shí)間內(nèi)得到計(jì)算結(jié)果。
其實(shí)SQL中的Q就是查詢的意思。發(fā)明它的初衷是做查詢(也就是計(jì)算),這是SQL的主要目標(biāo)。但是SQL在描述計(jì)算任務(wù)的時(shí)候很難說是稱職的。
為什么說SQL不稱職呢?
我們經(jīng)??吹絊QL只有兩三行的代碼,這些SQL確實(shí)是寫著簡(jiǎn)單的,但是如果嘗試一些復(fù)雜化的問題呢?比如:一支股票最長(zhǎng)連續(xù)上漲了多少天?用SQL寫是這樣的:
這個(gè)語(yǔ)句的工作原理就不解釋了,反正有點(diǎn)繞。
這是潤(rùn)乾公司的招聘考題,通過率不足 20%;因?yàn)樘y,后來被改成另一種方式:把 SQL 語(yǔ)句寫出來讓應(yīng)聘者解釋它在算什么,通過率依然不高。
這說明什么?說明情況稍有復(fù)雜,SQL 就變得即難懂又難寫!再看跑得快的問題,還是一個(gè)經(jīng)常拿出來的簡(jiǎn)單例子:1 億條數(shù)據(jù)中取前 10 名。這個(gè)任務(wù)用 SQL 寫出來并不復(fù)雜:
但是這個(gè)語(yǔ)句對(duì)應(yīng)的執(zhí)行邏輯是先把所有數(shù)據(jù)大排序,然后把前10個(gè)拿出來,把后10個(gè)留出來。眾所周知,排序是一個(gè)緩慢的動(dòng)作,會(huì)多次遍歷數(shù)據(jù)。如果數(shù)據(jù)量太大,內(nèi)存裝不下,就需要外部存儲(chǔ)作為緩存,性能會(huì)進(jìn)一步急劇下降。如果嚴(yán)格遵循這句話SQL所體現(xiàn)的邏輯,這個(gè)操作無(wú)論如何也跑不快。但是很多程序員都知道,這個(gè)操作不需要大排序,也不需要外存緩存,用一點(diǎn)內(nèi)存就可以完成一次遍歷,也就是有更高性能的算法。可惜用SQL寫不出這樣的算法。我們只能希望數(shù)據(jù)庫(kù)的優(yōu)化器足夠聰明,把這句話SQL轉(zhuǎn)換成高性能的算法來執(zhí)行,但是情況復(fù)雜的時(shí)候數(shù)據(jù)庫(kù)的優(yōu)化器不一定可靠。
看來SQL兩方面都不夠好。這兩個(gè)不復(fù)雜的問題都是這樣的。在現(xiàn)實(shí)中,幾千行SQL代碼充滿了難以編寫和快速運(yùn)行的情況。
為什么SQL為什么不行?要回答這個(gè)問題,我們需要分析我們?cè)谟贸绦虼a做什么。本質(zhì)上,編程的過程就是將解決問題的思想轉(zhuǎn)化為計(jì)算機(jī)可執(zhí)行的精確形式語(yǔ)言的過程。舉個(gè)例子,就像一個(gè)小學(xué)生解一道應(yīng)用題,分析完問題,想出解決方法后,要列出四個(gè)運(yùn)算表達(dá)式。程序計(jì)算也是如此。不僅需要制定出問題的解決方案,而且需要在解決方案完成之前將其轉(zhuǎn)化為計(jì)算機(jī)能夠理解和執(zhí)行的動(dòng)作。
用于描述計(jì)算方法的形式語(yǔ)言的核心在于所采用的代數(shù)系統(tǒng)。所謂代數(shù)系統(tǒng),簡(jiǎn)單來說就是一些數(shù)據(jù)類型及其運(yùn)算規(guī)則,比如小學(xué)學(xué)過的算術(shù),也就是整數(shù)、加減乘除。有了這套東西,我們就可以用這個(gè)代數(shù)系統(tǒng)約定的符號(hào),也就是代碼,寫出我們想要做的運(yùn)算,然后計(jì)算機(jī)就可以執(zhí)行了。
如果這個(gè)代數(shù)系統(tǒng)的設(shè)計(jì)不周到,提供的數(shù)據(jù)類型和運(yùn)算不方便,就會(huì)使描述算法非常困難。這時(shí)候就會(huì)出現(xiàn)一個(gè)奇怪的現(xiàn)象:將解翻譯成代碼的難度遠(yuǎn)遠(yuǎn)超過解問題本身。
比如從小就學(xué)會(huì)用阿拉伯?dāng)?shù)字做日常計(jì)算,做加減乘除非常方便。大家都想當(dāng)然的認(rèn)為數(shù)值運(yùn)算應(yīng)該是這樣的。其實(shí)不一定!估計(jì)很多人都知道有個(gè)東西叫羅馬數(shù)字。你知道羅馬數(shù)字的加減乘除嗎?古羅馬人是怎么去購(gòu)物的?
寫代碼的難度很大程度上是一個(gè)代數(shù)問題。
看看你跑不快的原因。
軟件改變不了硬件的性能,CPU和硬盤能多快就多快。但是我們可以設(shè)計(jì)一個(gè)低復(fù)雜度的算法,也就是計(jì)算量更少的算法,這樣計(jì)算機(jī)執(zhí)行的動(dòng)作會(huì)更少,自然會(huì)更快。但是光想出算法是不夠的,還需要用某種形式的語(yǔ)言把算法寫出來,否則計(jì)算機(jī)是不會(huì)執(zhí)行的。而且寫起來比較簡(jiǎn)單,耗時(shí)又麻煩,也沒人會(huì)用。所以,對(duì)于一個(gè)程序來說,跑得快和寫得簡(jiǎn)單其實(shí)是同一個(gè)問題,背后是這種形式語(yǔ)言使用的代數(shù)。如果這個(gè)代數(shù)不好,那么高性能的算法就很難甚至不可能實(shí)現(xiàn),也就不可能快速運(yùn)行。如上所述,SQL寫不出我們期待的小內(nèi)存單遍歷算法,只能寄希望于優(yōu)化器能跑得快。
我們?cè)俅騻€(gè)比方:
上過小學(xué)的同學(xué)大概都知道高斯計(jì)算1+2+3+…+100的故事。普通人只是一步一步推100次。高斯的孩子很聰明。他們發(fā)現(xiàn)1+100=101,2+99=101,…,50+51=101,結(jié)果是50乘以101,很快就吃完了家里的午飯。
聽完這個(gè)故事,我們都覺得高斯很聰明,能想到這么巧妙的辦法,簡(jiǎn)單又快捷。這沒有錯(cuò),但是很容易忽略一點(diǎn):在高斯時(shí)代,人類的算術(shù)體系(也是一種代數(shù))中就已經(jīng)有乘法了!前面說過,我們從小學(xué)習(xí)四則運(yùn)算的時(shí)候,會(huì)覺得乘法是理所當(dāng)然的,其實(shí)不是!乘法是加法之后發(fā)明的。如果高斯的年齡沒有相乘,即使有聰明的高斯,也沒有辦法快速解決這個(gè)問題。
目前主流的數(shù)據(jù)庫(kù)是關(guān)系數(shù)據(jù)庫(kù),之所以這么叫是因?yàn)樗臄?shù)學(xué)基礎(chǔ)叫關(guān)系代數(shù),SQL是從關(guān)系代數(shù)理論發(fā)展而來的形式語(yǔ)言。
現(xiàn)在我們可以回答了,為什么SQL在預(yù)期的兩個(gè)方面做得不夠好?問題出在關(guān)系代數(shù)上。關(guān)系代數(shù)就像一個(gè)只有加法沒有乘法的算術(shù)系統(tǒng)。很多事情做不好是必然的。
關(guān)系代數(shù)已經(jīng)發(fā)明了50年了。50年前的應(yīng)用需求和硬件環(huán)境與今天大不相同。如果繼續(xù)套用50年前的理論來解決今天的問題,聽起來是不是太老套了?然而,這就是現(xiàn)實(shí)。因?yàn)榇媪坑脩籼啵壳斑€沒有成熟的新技術(shù)出現(xiàn),所以基于關(guān)系代數(shù)的SQL仍然是當(dāng)今最重要的數(shù)據(jù)庫(kù)語(yǔ)言。雖然近幾十年有了一些改進(jìn)和提高,但根子沒有變。面對(duì)當(dāng)代復(fù)雜的需求和硬件環(huán)境,SQL是無(wú)法勝任的。
而且,很遺憾,這個(gè)問題是理論上的,工程上再怎么優(yōu)化也無(wú)濟(jì)于事。只能有限的改善,無(wú)法根除。但是,大多數(shù)數(shù)據(jù)庫(kù)開發(fā)人員并沒有想到這一層,或者說為了照顧現(xiàn)有用戶的兼容性,不打算想到這一層。于是,主流數(shù)據(jù)庫(kù)社區(qū)一直在這個(gè)圈子里徘徊。
SPL為什么能行
那么如何讓計(jì)算變得更簡(jiǎn)單更快捷呢?
發(fā)明新的代數(shù)!代數(shù)與乘法。然后在此基礎(chǔ)上設(shè)計(jì)新的語(yǔ)言。
這就是SPL的起源。它的理論基礎(chǔ)不再是關(guān)系代數(shù),而是離散數(shù)據(jù)集。基于這種新的代數(shù)設(shè)計(jì)的形式語(yǔ)言被命名為SPL(結(jié)構(gòu)化過程語(yǔ)言)。
SPL創(chuàng)新了/kloc-0的缺點(diǎn)/(更準(zhǔn)確的說,離散數(shù)據(jù)集是針對(duì)關(guān)系代數(shù)的各種缺陷)。SPL重新定義和擴(kuò)展了許多結(jié)構(gòu)化數(shù)據(jù)中的運(yùn)算,增加離散性,加強(qiáng)有序計(jì)算,實(shí)現(xiàn)徹底聚合,支持對(duì)象引用,提倡分步運(yùn)算。
由于篇幅所限,我們無(wú)法在此介紹SPL(離散數(shù)據(jù)集)的全貌。這里我們列出了SQL(關(guān)系代數(shù))的SPL(離散數(shù)據(jù)集)的一些差分改進(jìn):
游離記錄離散數(shù)據(jù)集中的記錄是一種基本的數(shù)據(jù)類型,可以獨(dú)立存在,不依賴于數(shù)據(jù)表。數(shù)據(jù)表是記錄的集合,組成一個(gè)數(shù)據(jù)表的記錄也可以用來組成其他數(shù)據(jù)表。比如過濾操作就是利用原數(shù)據(jù)表中符合條件的記錄組成新的數(shù)據(jù)表,這樣無(wú)論是空間占用還是操作性能都更有優(yōu)勢(shì)。
關(guān)系代數(shù)沒有表示記錄的操作數(shù)據(jù)類型。單個(gè)記錄實(shí)際上是只有一行的數(shù)據(jù)表,不同數(shù)據(jù)表中的記錄不能共享。比如過濾操作時(shí),會(huì)復(fù)制新的記錄,形成新的數(shù)據(jù)表,增加了空間和時(shí)間的成本。
特別是因?yàn)橛凶杂捎涗?,離散數(shù)據(jù)集的字段值允許記錄是某一條記錄,這樣可以更方便地實(shí)現(xiàn)外鍵連接。
有序性關(guān)系代數(shù)是基于無(wú)序集合設(shè)計(jì)的,集合成員沒有序號(hào)的概念,也不提供位置計(jì)算和相鄰引用的機(jī)制。SQL在實(shí)踐中做了一些工程上的局部改進(jìn),讓現(xiàn)代SQL可以方便的進(jìn)行一些有序的操作。
離散數(shù)據(jù)集中的集合是有序的,集合的成員都有序號(hào)的概念,可以用來訪問成員,定義定位操作返回集合中成員的序號(hào)。離散數(shù)據(jù)集提供符號(hào)實(shí)現(xiàn)集合運(yùn)算中的相鄰引用,支持集合中某個(gè)序數(shù)位置的計(jì)算。
有序操作很常見,但一直是SQL的難題,即使有窗口函數(shù)也還是很繁瑣。SPL已經(jīng)大大改善了這種情況,以前股票上漲的例子就能說明問題。
離散性和集合化富集合運(yùn)算是在關(guān)系代數(shù)中定義的,即集合可以作為一個(gè)整體參與運(yùn)算,如聚合、分組等。這是SQL比Java等高級(jí)語(yǔ)言更方便的地方。
但是關(guān)系代數(shù)的離散性很差,沒有自由記錄。而Java等高級(jí)語(yǔ)言在這方面沒有問題。
離散數(shù)據(jù)集相當(dāng)于離散性和聚集性的結(jié)合。不僅有聚合數(shù)據(jù)類型和相關(guān)操作,還有聚合成員在聚合之外獨(dú)立操作或重新組合成其他聚合??梢哉fSPL結(jié)合了SQL和Java兩者的優(yōu)點(diǎn)。
有序操作是離散性和聚集性的典型結(jié)合。秩序的概念只有在集合中才有意義,個(gè)體成員沒有秩序,體現(xiàn)了集體化。有序計(jì)算需要對(duì)某個(gè)成員及其相鄰成員進(jìn)行計(jì)算,需要離散性。
在離散性的支持下,可以得到更徹底的聚合,解決有序計(jì)算類型等問題。
離散數(shù)據(jù)集是一個(gè)既有離散性又有聚集性的代數(shù)系統(tǒng),關(guān)系代數(shù)只是聚集。
分組理解分組運(yùn)算的初衷是將一個(gè)大的集合按照一定的規(guī)則劃分成若干個(gè)子集。關(guān)系代數(shù)中沒有可以表示集合的集合的數(shù)據(jù)類型,所以分組后被迫做聚集運(yùn)算。
離散數(shù)據(jù)集中的一組允許集可以代表合理的分組運(yùn)算結(jié)果。分組和分組聚合分為獨(dú)立的兩步操作,這樣可以對(duì)分組子集進(jìn)行更復(fù)雜的操作。
關(guān)系代數(shù)中只有一種等價(jià)分組,即根據(jù)分組鍵值對(duì)集合進(jìn)行劃分,等價(jià)分組是完全劃分。
離散數(shù)據(jù)集認(rèn)為任何拆分大集合的方法都是分組運(yùn)算。除了傳統(tǒng)的等價(jià)分組,它還提供了與有序性相結(jié)合的有序分組,以及可能導(dǎo)致不完全劃分的準(zhǔn)分組。
聚合理解關(guān)系代數(shù)中沒有明確的集合數(shù)據(jù)類型,聚集計(jì)算的結(jié)果都是單值,分組聚集運(yùn)算也是如此,只有SUM、COUNT、MAX、MIN等等。尤其是關(guān)系代數(shù)不能把TOPN運(yùn)算看成是聚合。用于成套的TOPN只能取結(jié)果集排序后的前n項(xiàng),而用于分組子集的TOPN很難實(shí)現(xiàn),需要改變思路,拼出序號(hào)才能完成。
離散數(shù)據(jù)集提倡泛集,聚合運(yùn)算的結(jié)果不一定是單個(gè)值,也可能仍然是一個(gè)集合。在離散數(shù)據(jù)集中,TOPN運(yùn)算具有與求和計(jì)數(shù)相同的地位,也就是說,它可以用于完整集或分組子集。
SPL將TOPN理解為聚合運(yùn)算后,還可以避免工程實(shí)現(xiàn)中總數(shù)據(jù)的排序,從而實(shí)現(xiàn)高性能。但是SQL的TOPN總是伴隨著ORDER BY的動(dòng)作,理論上需要大排序才能實(shí)現(xiàn),需要寄希望于在項(xiàng)目實(shí)現(xiàn)中對(duì)數(shù)據(jù)庫(kù)進(jìn)行優(yōu)化。
有序支持的高性能離散數(shù)據(jù)集特別強(qiáng)調(diào)有序集,很多高性能算法都可以利用有序特征來實(shí)現(xiàn)。這是因?yàn)榛跓o(wú)序集的關(guān)系代數(shù)什么都做不了,只能寄希望于工程優(yōu)化。
以下是在部分使用有序特征后可以實(shí)現(xiàn)的低復(fù)雜度操作:
1)數(shù)據(jù)表對(duì)主鍵是有序的,相當(dāng)于自然有了索引。關(guān)鍵字段的過濾通??梢钥焖俣ㄎ?,以減少外部存儲(chǔ)器的遍歷量。二進(jìn)制方法也可以用來定位隨機(jī)鍵值,當(dāng)同時(shí)檢索多個(gè)鍵值時(shí),索引信息可以重用。
2)通常的分組操作是通過哈希算法實(shí)現(xiàn)的。如果確定知道數(shù)據(jù)對(duì)的鍵值都是有序的,就可以只做鄰居比較,避免計(jì)算哈希值,不會(huì)有哈希沖突,非常容易并行。
3)數(shù)據(jù)表是鍵對(duì)齊有序的,兩個(gè)大表之間的對(duì)齊連接可以執(zhí)行更高性能的合并算法,只需要遍歷一次數(shù)據(jù),不需要緩存,占用內(nèi)存少;然而,傳統(tǒng)的哈希值堆方法不僅復(fù)雜,而且需要大內(nèi)存和外部緩存,還可能因哈希函數(shù)不當(dāng)造成二次哈希重新緩存。
4)大型表作為外鍵表的連接。當(dāng)事實(shí)表很小時(shí),可以從外鍵表中快速提取關(guān)聯(lián)鍵值對(duì)應(yīng)的數(shù)據(jù),以實(shí)現(xiàn)連接,而不需要HASH堆動(dòng)作。當(dāng)事實(shí)表也很大時(shí),可以用分位數(shù)將外鍵表分成多個(gè)邏輯段,然后將事實(shí)表按照邏輯段進(jìn)行堆疊。這樣只需要堆疊一張表,哈希堆的時(shí)候不會(huì)有可能二次堆,計(jì)算復(fù)雜度可以大大降低。
其中,3和4利用了離散數(shù)據(jù)集的變換來進(jìn)行連接操作。如果仍然擴(kuò)展關(guān)系代數(shù)的定義(可能會(huì)產(chǎn)生多對(duì)多的結(jié)果),這種低復(fù)雜度的算法是很難實(shí)現(xiàn)的。
除了理論上的差異,SPL還有很多工程上的優(yōu)勢(shì),比如更容易編寫并行代碼,大內(nèi)存預(yù)相關(guān)提高外鍵連接的性能,獨(dú)特的列存儲(chǔ)機(jī)制支持任意分段并行。
和SPL一起把之前的問題重寫一遍,直接感受一下。
一只股票能連續(xù)上漲多少天?
計(jì)算思路和前面的 SQL 相同,但因?yàn)橐肓擞行蛐院?,表達(dá)起來容易多了,不再繞了。
1 億條數(shù)據(jù)中取前 10 名:
SPL 有更豐富的集合數(shù)據(jù)類型,容易描述單次遍歷上實(shí)施簡(jiǎn)單聚合的高效算法,不涉及大排序動(dòng)作。
關(guān)鍵詞:誕生,語(yǔ)言,國(guó)產(chǎn),數(shù)據(jù)