在結(jié)構(gòu)化數(shù)據(jù)處理領(lǐng)域,SQL無(wú)疑是應(yīng)用最廣泛的工作語(yǔ)言,不僅被所有關(guān)系數(shù)據(jù)庫(kù)采用,許多新進(jìn)的大數(shù)據(jù)平臺(tái)也將實(shí)現(xiàn)SQL作為目標(biāo)。但現(xiàn)實(shí)是,面對(duì)當(dāng)前紛雜的計(jì)算查詢需求,SQL在很多方面并不夠好用。一般為人詬病的SQL的過(guò)程性問(wèn)題,其實(shí)并不是最關(guān)鍵的問(wèn)題,SQL的更大困難來(lái)源于其理論基礎(chǔ),即關(guān)系代數(shù)。
關(guān)系代數(shù)是一種代數(shù)體系。拋開嚴(yán)格定義代數(shù)體系的概念,通俗地解釋。人們?yōu)榻鉀Q某種運(yùn)算問(wèn)題,定義了一些數(shù)據(jù)對(duì)象及針對(duì)這些數(shù)據(jù)對(duì)象的一套運(yùn)算規(guī)則,確保這些運(yùn)算的封閉性和自洽性,就可以稱為一種代數(shù)體系了。比如說(shuō)我們熟悉的有理數(shù)及其上的四則運(yùn)算就是一種用于解決日常生活中數(shù)值計(jì)算需求的代數(shù)體系。封閉性是指計(jì)算結(jié)果必須仍然是定義過(guò)的數(shù)據(jù)對(duì)象,比如有理數(shù)的四則運(yùn)算結(jié)果仍然是有理數(shù)。而自洽性指這些運(yùn)算不能出現(xiàn)矛盾的結(jié)果,比如我們要約定不能除以0,否則把某個(gè)數(shù)除以0規(guī)定成任何數(shù)都會(huì)推出邏輯矛盾來(lái)。
注意,這里說(shuō)的數(shù)據(jù)對(duì)象,和程序設(shè)計(jì)面向?qū)ο罄碚撝袛?shù)據(jù)對(duì)象不太一樣。前者主要強(qiáng)調(diào)數(shù)據(jù)上的運(yùn)算,而后者更多強(qiáng)調(diào)對(duì)象的封裝性、繼承性和重載能力。前者是為了更好的描述和實(shí)施數(shù)據(jù)運(yùn)算,后者則主要是為了代碼復(fù)用。
代數(shù)體系設(shè)計(jì)得好與不好,嚴(yán)重影響我們實(shí)施計(jì)算的方便度和效率!
舉兩個(gè)例子:
1. 我們從小學(xué)過(guò)的算術(shù)體系都采用阿拉伯?dāng)?shù)字,用來(lái)表示數(shù)值和做四則運(yùn)算都很方便,但試想如果換成羅馬數(shù)字會(huì)是個(gè)什么感覺?
2. 所有整數(shù)乘法都可以用加法表示,我們?cè)谒阈g(shù)體系中引入了乘法來(lái)表示若干個(gè)相同數(shù)相加這種運(yùn)算,就可以發(fā)明九九表來(lái)做而不必硬加,效率顯著提高。
要讓計(jì)算機(jī)實(shí)施計(jì)算,還需要一套基于代數(shù)體系的形式化語(yǔ)言,用戶把計(jì)算目標(biāo)按約定的語(yǔ)法符號(hào)寫成代碼,就可以由計(jì)算機(jī)執(zhí)行了。而用計(jì)算機(jī)解決問(wèn)題的過(guò)程,也可以理解為把題目解法翻譯成某種形式化語(yǔ)言的過(guò)程。如果代數(shù)體系及其形式化語(yǔ)言設(shè)計(jì)得不好,就可能發(fā)生翻譯問(wèn)題解法的難度大于解決問(wèn)題本身的現(xiàn)象!用羅馬數(shù)字來(lái)實(shí)施四則運(yùn)算就是這個(gè)結(jié)果。
關(guān)系代數(shù)就是用來(lái)實(shí)現(xiàn)批量結(jié)構(gòu)化數(shù)據(jù)計(jì)算的代數(shù)體系,其形式化語(yǔ)言就是SQL。講述關(guān)系代數(shù)和SQL原理的資料很多,這里就不再贅述了。
那么用SQL解決結(jié)構(gòu)化數(shù)據(jù)運(yùn)算的效果如何呢?
人們通常關(guān)心兩個(gè)效率問(wèn)題。一是運(yùn)算的描述效率,二是運(yùn)算的執(zhí)行效率。這兩個(gè)效率很容易理解,如果描述效率太低,就意味著開發(fā)成本太高,很難寫出程序進(jìn)行計(jì)算;而如果執(zhí)行效率低,則需要運(yùn)行很久才能得到結(jié)果,那實(shí)用價(jià)值也就大打折扣了。實(shí)際上,執(zhí)行高效在本質(zhì)上也是個(gè)描述問(wèn)題,在軟件層面不可能提高硬件性能,但可能設(shè)計(jì)出更高效的算法,那么這個(gè)語(yǔ)言不能限制我們寫出高效算法。
遺憾的是,面對(duì)較復(fù)雜的大數(shù)據(jù)運(yùn)算,SQL在這兩方面表現(xiàn)都很差,我們分別舉兩個(gè)并不復(fù)雜的例子說(shuō)明:
1. 找出一支股票最長(zhǎng)連續(xù)上漲了多少天
這個(gè)問(wèn)題對(duì)于Java或C++程序員來(lái)講非常簡(jiǎn)單:做個(gè)初值為0的計(jì)數(shù)器,把數(shù)據(jù)按日期排序后遍歷,發(fā)現(xiàn)上漲就將計(jì)數(shù)器加1,下跌則清0,最后看這個(gè)計(jì)數(shù)器出現(xiàn)過(guò)的最大值,這是個(gè)很自然的思路。但是用SQL實(shí)現(xiàn)就太困難了。關(guān)系代數(shù)延用了數(shù)學(xué)上的無(wú)序集合概念,數(shù)據(jù)排序只在輸出時(shí)有效,無(wú)法規(guī)定數(shù)據(jù)的遍歷次序,也就無(wú)法實(shí)施上述的自然思路。需要人為地制造出日期的序號(hào)后,再產(chǎn)生一個(gè)分組標(biāo)志,把上漲的日期和前一天分成一組,下跌的日期分到另一組,然后計(jì)算最大的分組COUNT()值,實(shí)現(xiàn)思路很難理解。這就發(fā)生前面所說(shuō)的翻譯問(wèn)題解法的難度大于解決問(wèn)題本身的現(xiàn)象了。
?2. 從10億條數(shù)據(jù)中找出最大的前10名
我們知道,這樣的問(wèn)題是不需要把10億行數(shù)據(jù)全部排序的。先產(chǎn)生一個(gè)有10個(gè)成員的空集合,然后遍歷數(shù)據(jù),過(guò)程中始終保持這個(gè)小集合是當(dāng)前已遍歷過(guò)數(shù)據(jù)中最大的10個(gè),這樣整個(gè)10億行數(shù)據(jù)只要遍歷一次,內(nèi)存占用很小,運(yùn)算性能很好。但關(guān)系代數(shù)中沒有顯式的集合數(shù)據(jù)類型,無(wú)法描述這個(gè)算法,只能把10億行數(shù)據(jù)大排序再取出前10后,剩下的已排序的數(shù)據(jù)沒有用了。10億行大排序的成本很高,如果內(nèi)存裝不下則還會(huì)設(shè)計(jì)多次外存數(shù)據(jù)倒換的問(wèn)題,性能會(huì)嚴(yán)重下降。這就會(huì)發(fā)生我們明知有好的算法卻無(wú)計(jì)可施的尷尬局面。這種情況常常就只能寄希望于數(shù)據(jù)庫(kù)在工程上的優(yōu)化,但情況復(fù)雜的SQL會(huì)超出數(shù)據(jù)庫(kù)的優(yōu)化能力(比如在分組中取每組的前10名)。
SQL中類似的問(wèn)題還很多,遠(yuǎn)遠(yuǎn)不像傳說(shuō)中的那么強(qiáng)悍。限于篇幅我們不能一一羅列,以后會(huì)逐步撰文深入剖析。
在運(yùn)算簡(jiǎn)單的情況,并且性能要求不高時(shí),用SQL還是比較方便的,畢竟掌握者眾多,相關(guān)軟件也很豐富。但現(xiàn)代應(yīng)用中的數(shù)據(jù)需求越來(lái)越復(fù)雜,數(shù)據(jù)量也越來(lái)越大,繼續(xù)采用SQL就會(huì)嚴(yán)重影響工作效率了。而且,SQL的不適應(yīng)并非實(shí)現(xiàn)層面的問(wèn)題,而是其基礎(chǔ)理論的問(wèn)題,這不是在工程上進(jìn)行優(yōu)化就能解決的。面臨當(dāng)前的數(shù)據(jù)運(yùn)算需求,關(guān)系代數(shù)顯得過(guò)于簡(jiǎn)單了,需要從數(shù)學(xué)上進(jìn)行徹底的革新。