目錄
第十二章 查詢處理
12.1 概述
查詢處理(query processing)是指從數據庫中提取數據時涉及的一系列活動。這些活動包括:
- 將用高層數據庫語言表示的查詢語句翻譯成能在文件系統的物理層上使用的表達式
- 爲優化查詢而進行各種轉換
- 查詢的實際執行。
查詢處理步驟如圖12-1所示,基本步驟包括:
- 語法分析和翻譯
- 優化
- 執行
12.2 查詢代價的度量
查詢處理的代價包括:磁盤存取,執行一個查詢所需要的CPU時間,在並行/分佈式數據庫系統中的通信代價。
然而,在大型數據庫中,在磁盤上存取數據的代價通常是最主要的代價。
用傳送磁盤塊數以及搜索磁盤次數來度量查詢計劃的代價:
- 假設瓷盤子系統傳輸一個塊的數據平均消耗t_T秒,磁盤平均訪問時間(磁盤搜索時間+旋轉延遲)爲t_S秒,則一次傳輸b個塊,以及執行S次磁盤搜索的操作將消耗(b*t_T+S*t_S)秒。
- 其中t_T和t_S必須針對所使用的磁盤系統來計算。
- 當今高端磁盤的典型數值通常是t_S = 4毫秒,t_T=0.1毫秒(假定磁盤塊的大小是4KB,傳輸率爲40MB/s)
通果把讀磁盤塊和寫磁盤塊區分開,可以進一步細化磁盤存取代價的估算。
- 因爲寫磁盤塊的代價通常是讀磁盤塊的兩倍(這是由於磁盤系統在寫完扇區後還會重新讀取該扇區以驗證寫操作是否成功) ==> 本文忽略這個細節
本書給出的代價沒有包括將操作最終寫回磁盤的代價,當需要時需要單獨考慮。
- 本書所考慮的算法代價依賴於主存中緩衝區的大小
- 最好的情形是所有的數據都可以讀入到緩衝區中,不必再訪問磁盤
- 最壞的情形是假定緩衝區只能容納數目不多的塊——大約每隔關係一塊
- 在代價估算時,通常假定最壞的情形。
本書假定開始時數據必須從磁盤中讀取出來,但是很可能這個磁盤塊已經在內存緩衝區中了。
- 本書忽略這個細節
- 因此,一個查詢計劃中的實際磁盤存取代價可能會比估算代價小
假設計算機沒有其他活動在進行,那麼一個查詢計劃的響應時間(response time)就是所有的這些開銷,並可以作爲計劃的代價度量。遺憾的是很難估計響應時間,因爲:
- 當計劃開始執行,響應時間依賴於緩存區的內容;在對查詢進行優化時,該信息無法獲取,而且即便可以獲取也很難應用於計算。
- 在具有很多張磁盤的系統中,響應時間依賴於訪問如何分佈在各磁盤上,沒有對分佈在磁盤中的數據的詳細瞭解這是很難估計的。
需要注意的是,可以以額外的資源消耗爲代價,獲取計劃更好的響應時間。例如:
- 假如一個系統有多張磁盤,一個計劃A需要額外的磁盤讀取,但它並行的跨多張磁盤執行讀,它可能比另一個計劃B完成更快,儘管計劃B有較少的磁盤讀取,但是它從一張磁盤讀。
- 但是,如果一個查詢的多個實例同時使用計劃A來運行,整體的響應時間可能比相同的實例使用B計劃來執行要長,這是因爲計劃A有更多的磁盤負載。
綜上:優化器通常努力去儘可能降低查詢計劃總的資源消耗(resource consumption),而不是儘可能降低響應時間。
估計總的磁盤訪問時間(包括尋道和數據傳輸)的模型,就是一個基於資源消耗的查詢代價的模型的實例。
12.3 選擇運算
在查詢處理中,文件掃描(file scan)是存取數據最低級的操作。
文件掃描是用於定位、檢索滿足選擇條件的記錄的搜索算法。
在關係系統中,若關係保存在單個專用的文件中,採用文件掃描就可以讀取整個關係。
12.3.1 使用文件掃描和索引的選擇
12.3.2 涉及比較的選擇
A1-A6
索引結構稱爲存取路徑(access path),他們提供了定位和存取數據的一條路徑。
使用索引的搜索算法稱爲 索引掃描(index scan)。
考慮所有元組都存儲在一個文件中,對其進行選擇運算,可以有如下方式:
如11.4.2節所述,輔助索引通常不存儲指向記錄的指針,而是存儲主索引的屬性值。
這種方式下,通過輔助索引存取一條記錄的代價更大:首先必須搜索輔助索引以找到主索引的搜索碼值;然後查找主索引來找到記錄。如果考慮到這樣的實現,需要對錶中的估計進行調整。
如果檢索到的記錄數很大時,使用輔助索引的代建甚至比線性搜索還打。因此輔助索引應該僅在選擇得到的記錄很少時使用。
12.3.3 複雜選擇的實現
A7-A10
A7 利用一個索引的合取選擇 |
首先判斷是否存在某個簡單條件的某個屬性上存在一條存取路徑。若存在,則可以用算法A2-A6中的一個來檢索滿足條件的記錄。然後再內存緩衝區中,測試檢索到的記錄是否滿足其他條件。
代價爲:θ_i和A1-A6組合的最小代價 |
A8 使用組合索引的合取選擇 |
可能存在合適的組合索引(composite index) 如果選擇指定的是兩個或多個屬性上的等值條件,並且這些屬性字段的組合又存在組合索引,則可以直接搜索索引。索引的類型可以從A2-A4中選擇。 |
A9通過標識符的交實現合取選擇 |
利用記錄指針或記錄標識符的方式實現合取選擇。 該算法要求各個條件所涉及的字段上帶有記錄指針的索引。該算法對每個索引進行掃描,獲取那些指向滿足單個條件的記錄的指針。所有檢索到的指針的交集就是那些滿足合取條件的指針的集合。然後算法利用該指針集合獲取實際的記錄。如果並非所有條件上都存在索引,則該算法還需要用剩餘條件對所檢索到的記錄進行測試。
代價:掃描各個索引的代價的總和+獲取檢索到的指針列表的交集中的記錄的待機
優化:對指針列表進行排序並按照排序順序檢索記錄能夠減少該算法的代價。因此: (1)應把指向一個磁盤塊中所有記錄的指針歸併到一起,這樣只需一次I/O操作就可以獲取到該磁盤塊中選擇的所有記錄 並且(2)磁盤塊的讀取也可以按照存儲次序執行,這樣磁盤臂的移動最少。 |
A10 通過標識符的並實現析取選擇 |
如果在析取選擇中,所有條件上均有相應的存取路徑存在,則逐個掃描索引獲取滿足單個條件的元組指針。檢索到的所有指針的並集就是指向滿足析取條件的所有元組的指針集。然後利用這些指針檢索實際的記錄。
然而,即使只有一個條件不存在存取路徑,就不得不對整個關係進行一次線性掃描找出滿足該條件的元組。因此,只要析取式中有一個這樣的條件,最有效的存取方式就是線性掃描。 |
12.4 排序
數據排序在數據庫系統中有很重要的作用,主要原因有兩個:
- SQL查詢會指明對結果進行排序
- 當輸入的關係已排序時,關係運算中的一些運算(如連接運算)能夠得到高效實現
在邏輯上排序和在物理上排序:
邏輯排序:通過在排序碼上建立索引,然後使用該索引按序讀取關係,可以完成對關係的邏輯排序。
- 缺點:因爲該種方式沒有實現物理排序,因此順序讀取元組可能導致每次讀取有一個元組就要訪問一次磁盤(磁盤搜索+磁盤塊傳輸),這樣做的代價很大。
出於這個原因,有時需要在物理上進行排序。
12.4.1 外部排序歸併算法
外排序(external sorting):對不能完全放在內存中的關係的排序稱爲外排序。
外排序中最常用的技術是:外部歸併排序(external sort-merge)算法。
外部歸併排序算法介紹:設M表示內存緩衝區中可以用於排序的塊數,即內存緩衝區可以容納的磁盤塊數。
- 第一階段:建立多個排序好的歸併段(run)。每個歸併段內部是排序好的,但僅包含關係中的部分記錄。
12.4.2 外部排序歸併的代價分析
磁盤塊傳輸代價:
12.5 連接運算
12.5.1 嵌套循環連接
12.5.2 塊嵌套循環連接
嵌套循環和塊嵌套循環的進一步改進:
- 如果自然連接或等值連接的連接屬性是內層關係的碼,則對每個外層關係元組/塊,內層循環一旦找到首條匹配的元組就可以終止
- 塊嵌套循環連接算法中,外層關係可以以內存中最多能容納的磁盤塊的大小 減去 預留給內層關係及輸出結果的緩衝空間 作爲單位進行循環。即:使用內存能夠容納的最大塊數M-2爲單位讀取外層關係做循環。
- 磁盤塊傳輸次數:b_r*[b_r/(M-2)] + b_r
- 磁盤搜索:2*[b_r/(M-2)]
- 對內層循環輪流做向前、向後的掃描。該掃描方法對磁盤塊讀寫請求進行排序,使得上一次掃描時留在緩衝區的數據可以重用,從而減少磁盤存取次數。
- 若內層循環的連接屬性上有索引,可以用更有效的索引查找法替代文件掃描法,見下節
12.5.3 索引嵌套循環連接
在嵌套循環連接中,如果在內層循環的連接屬性上有索引,則可以用索引查找替代文件掃描。這稱爲索引嵌套循環連接(indexed nested-loop join)。
12.5.4 歸併連接
12.5.4.1 歸併算法
12.5.4.2 代價分析
12.5.4.3 混合歸併連接
當兩個關係的連接屬性上存在輔助索引時,可以對未排序的元組執行歸併連接越算的變種:
- 通過索引掃描記錄,從而按順序檢索記錄
- 缺點:記錄可能分散存儲在文件的多個塊中,從而每個元組的讀取都需要一次磁盤訪問,代價很大。
改進:混合歸併-連接技術(hybrid merge-join algorithm),把索引和歸併連接結合起來
- 假設兩個關係,有一個排序了,一個沒有排序,但是未排序的關係在連接屬性上存在B+樹輔助索引
- 混合歸併-連接算法:
- 把已經排序的關係 與 B+樹輔助索引葉結點進行歸併,所得結果包含了已排序關係的元組 及 未排序關係的元組地址
- 將該文件按未排序關係元組的地址進行排序,從而能夠對相關元組按照物理存儲順序進行有效的檢索,最終完成連接運算。
12.5.5 散列連接
也可用於自然連接和等值連接
12.5.5.1 基本思想
12.5.5.2 遞歸劃分
遞歸劃分(recursive partitioning):
如果n_h的值大於或者等於內存塊數M,因爲沒有足夠的緩衝塊,所以劃分不能一趟完成。
這時,完成關係的劃分要重複多趟。
每一趟中,輸入的最大劃分不超過用於輸出的緩衝塊數目。
每一趟產生的存儲桶在下一趟中分別被讀入並再次劃分,產生更小的劃分。
每次劃分採用與上一次不同的散列函數。
系統不斷重複輸入的分裂過程直到構造用輸入關係的每個劃分都能被內存容納爲止。
12.5.5.3 溢出處理
12.5.5.4 散列連接的代價
例如:takes和students連接。內存有20塊,student劃分成5個劃分,每個劃分佔20塊,則劃分只需要一趟。
takes類似也劃分成5個劃分,每個劃分80塊。忽略部分滿的代價,共需3*(100+400)=1500次塊傳輸;
在劃分過程中有足夠的內存分配3個輸入緩衝區和輸出緩衝區,共計2([100/3]+[400/3])次磁盤搜索。
若主存較大,散列連接性能可以提高。當主存中容納整個構造用輸入關係時,n_h可以設置成0
此時,不管探查用輸入的大小如何,不必將關係劃分爲臨時文件,因而散列連接算法可以快速執行。
其估計代價降爲:b_r+b_s次磁盤塊傳輸和兩次磁盤搜索。
12.5.5.5 混合散列連接
12.5.5.6 複雜連接
嵌套循環連接與塊嵌套循環連接可以在任何連接條件下使用,其他的連接技術比它們的效率更高,但是隻能處理簡單的連接條件,如自然連接或等值連接。
複雜連接:
合取:可以用前面的連接技術 計算按照單個連接條件的連接結果 作爲中間結果==> 再從中選取滿足其它條件的元組
析取:計算每個連接的結果,再取並集。
12.6 其他運算
去處重複、投影、集合運算、外連接、聚集
12.6.1 去除重複
- 可以用排序的方法去除重複,如:外排
- 創建歸併段時,如果發現重複,可以在將歸併段寫回磁盤時去處重複
- 在歸併時,去除歸併段與歸併段之間的重複元組
- 最壞情形:與排序的代價估計是一樣的
- 可以用散列來實現去除重複
- 基於整個元組上的一個散列函數對整個關係進行劃分
- 每個劃分被讀入內存,並建立內存索引。在這個過程中,只插入不在索引中存在的元組。
- 劃分中的所有元組處理完後,散列索引中的元組被寫到結果中
- 代價估算:與散列連接中的構造用輸入關係的處理(劃分,以及讀入每個劃分)的代價一樣。
12.6.2 投影
在每個元組上進行投影,再去除結果集中的重複
12.6.3 集合運算
並、交、差
- 方法一:先排序,再對每個排序的關係掃描一次,得到所需結果。
- 兩個輸入關係都要掃描一次,如果已經按相同順序排序,其代價爲b_r+b_s此塊傳輸
- 最壞的情況下,只有一個緩衝塊,則需要b_r+b_s此塊傳輸和磁盤搜索。
- 分配額外的緩衝塊可以減少磁盤搜索的次數
- 若一開始沒排序,則還需考慮排序的代價
- 方法二:散列的方式。使用相同的散列函數對每個關係進行劃分,由此得到對應的劃分。對每對劃分執行如下操作:
12.6.4 外連接
左外連接、全外連接、右外連接
外連接的計算策略:
- 方法1:計算響應的自然連接/等值連接,然後將未連接的結果加入到結果集中作爲最終的連接結果。
- 方法2:擴展連接算法
- 擴展嵌套循環連接、塊嵌套循環連接==> 較簡單的實現左外連接和右外連接,但是全外連接很難
- 擴展歸併連接==> 可以計算全外連接,左外連接,和右外連接
- 計算全外連接:當兩個關係的歸併完成後,將兩個關係中那些與另外一個關係的任何元組都不匹配的元組填充空值後寫到結果中。
- 擴展散列連接算法 ==> 可計算全外連接、左外連接和右外連接
12.6.5 聚集
avg, min, max, sum, count
使用排序或散列,將元組進行分組;再在每個分組上進行聚集運算得到結果。
12.7 表達式計算
如何計算包含多個運算的表達式:
- 物化materialized:以適當的順序每次執行一次操作;每次計算的結果被物化(materialized)到一個臨時關係以備後用
- 缺點:需要構造臨時關係,這些臨時關係必須寫到磁盤上
- 流水線pipeline法:同時計算多個運算,運算的結果傳遞給下一個,而不必保存臨時關係
12.7.1 物化
12.7.2 流水線
12.7.2.1 流水線的實現
流水線的兩種實現方式:
- 需求驅動的流水線(demand-driven pipeline):消極的lazily
- 思想:
- 系統不停地向位於流水線頂端的操作發出需要元組的請求
- 每當一個操作收到需要元組的請求,它就計算下一個(若干個)元組並返回
- 如果該操作的輸入不是來自流水線,則返回的下一個(若干個)元組可以由輸入關係計算得到,同時系統記載目前爲止已經返回了哪些元組。
- 如果該操作的某些輸入來自流水線,則該操作也發出請求以獲得來自流水線輸入的元組,並使用該元組計算輸出元組,返回給父層。
- 具體實現:
- 流水線中的每個操作可以用迭代算子(iterator)來實現,迭代算子提供:open(), next(), close()函數。
- 調用open()後,對next()的每次調用返回該操作的下一個輸出元組
- close()告訴迭代算子不在需要元組了。
- 迭代算子維護兩次調用之間的狀態,使得下一個next()調用記錄可以獲取下面的結果元組。
- 流水線中的每個操作可以用迭代算子(iterator)來實現,迭代算子提供:open(), next(), close()函數。
- 思想:
- 生產者驅動的流水線(producer-driven pipeline):積極的eagerly
- 思想:
- 各操作不等待元組請求,而是積極的eagerly產生元組。
- 生產者驅動的流水線中的每一個操作作爲系統中一個單獨的進程或線程建模,以處理流水線輸入的元組流,併產生相應的輸出元組流。
- 具體實現:
- 系統爲每一對相鄰的操作創建一個緩衝區,來保存從上一個操作傳遞到下一個操作的元組
- 對應不同操作的進程或者線程會併發執行。
- 流水線底部的每個操作會不斷產生輸出元組,並將其存放至緩衝區,直到緩衝區已滿。
- 當緩衝區滿時,該操作會等待,直到其父操作取出緩衝區元組進行消費,爲其提供空間。
- 思想:
12.7.2.2 流水線的執行算法
阻塞操作blocking operation:直到所有的輸入元組都被檢查完之前,不能輸出任何結果,這類操作稱爲阻塞操作。
例如:排序。
其他操作(例如連接)本身不阻塞,但具體的計算算法可能會阻塞。
例如:
- 散列連接算法是一個阻塞操作,又因爲在輸出任何元組之前,它要求兩個輸入都被完全取回並劃分。
- 索引嵌套連接算法隨着得到外部關係的元組就可以輸出結果元組。因此,它成爲在外部(左邊)關係上的流水線化(pipelined),儘管在索引嵌套連接算法執行之前,必須充分構建索引所導致在其索引(右邊)輸入的阻塞。
- 混合散列連接可以看做是在被探查關係上部分流水線的。
- 因爲,當它從探查關係中接收元組時,它可以輸出來自第一個劃分中的元組
- 然而,不位於第一個劃分的元組只有在接收整個流水線輸入關係後才能輸出。
- 如果構造用輸入可以全部存儲在內存中,則混合散列連接可以再探查用輸入上提供流水線計算
- 如果構造用輸入可以大部分存放在內存中,則混合散列連接可以再探查用輸入上近似流水線。
- 歸併連接:
- 如果兩個輸入在連接屬性上均是有序的,並且是等值連接,則可以使用歸併連接,並且兩個輸入都可流水線化。
- 如果兩個輸入在連接屬性上沒有排序,==》 採用雙流水線連接(double-pipelined join)技術
總結
- 對於一個查詢,系統首先要做的事就是將之翻譯成系統內部表示形式。對於關係系統而言,內部形式通常是基於關係代數的。在產生查詢的內部形式過程中,語法分析器檢查用戶查詢語句的語法,驗證出現在查詢語句中的關係名是否是數據庫中的關係名等。如果查詢語句是用視圖表達的,語法分析器就把所有對視圖名的引用替換成計算該視圖的關係代數表達式
- 給定一個查詢,通常由很多計算它的方法。將用戶輸入的查詢語句轉換成等價的,執行效率更高的查詢語句,是優化器的責任。
- 對於包含簡單選擇的查詢語句,可以通過線性掃描或利用索引來處理。通過計算簡單選擇結果的並和交,可以處理複雜選擇操作。
- 可以利用外排對大於內存的關係進行排序
- 涉及自然連接的查詢語句可以有多種處理方法,如何處理取決於是否有索引可用以及關係的物理存儲形式。
- 若連接的結果大小几乎與兩個連接的笛卡爾積相當,則採用塊嵌套循環連接策略最好。
- 若存在索引,則可以用索引嵌套循環連接。
- 若關係已經排序,則採用歸併連接比較可取。爲了能夠使用歸併連接,在連接計算前對關係排序是可取的。
- 散列連接算法吧關係劃分成多個部分,使每個部分都能被內存容納。劃分過程是通過連接屬性上的散列函數進行的,這樣相應的劃分可以獨立的進行連接
- 去除重複、投影、集合操作(並、交、差)、聚集操作都可以用排序和散列實現
- 外連接操作可以通過對連接算法的簡單擴展來實現
- 散列與排序在某種意義下是對偶的。因爲任何能用散列實現的操作(如:去除重複、投影、聚集、連接、外連接操作)都可以用排序來實現,反之亦然。
- 可以採用物化的方式進行表達式的計算。系統計算每個子表達式的結果並將其存儲到磁盤上,然後用它進行父表達式的計算。
- 流水線方法在子表達式產生輸出的同時就在父表達式的計算中使用其輸出結果,幫助我們避免了許多子查詢的結果寫到磁盤的操作。