關係型數據庫進階(二)全局概覽

 

  上篇文章 關係型數據庫進階(一)數據庫基礎,咱們已經瞭解了數據庫基礎,如今咱們須要回來看看數據庫的全貌了。html

  數據庫是一個易於訪問和修改的信息集合。不過簡單的一堆文件也能達到這個效果。事實上,像SQLite這樣最簡單的數據庫也只是一堆文件而已,但SQLite是精心設計的一堆文件,由於它容許你:sql

1  使用事務來確保數據的安全和一致性數據庫

2  快速處理百萬條以上的數據緩存

3  數據庫通常能夠用以下圖形來理解:安全

 

本篇文章,我不會特別關注如何組織數據庫或者如何命名各類進程,由於我選擇了本身的方式來描述這些概念以適應本文。區別就是不一樣的組件,整體思路爲:數據庫是由多種互相交互的組件構成的。服務器

核心組件:

進程管理器(process manager):不少數據庫具有一個須要妥善管理的進程/線程池。再者,爲了實現納秒級操做,一些現代數據庫使用本身的線程而不是操做系統線程。網絡

網絡管理器(network manager):網路I/O是個大問題,尤爲是對於分佈式數據庫。因此一些數據庫具有本身的網絡管理器。架構

文件系統管理器(File system manager):磁盤I/O是數據庫的首要瓶頸。具有一個文件系統管理器來完美地處理OS文件系統甚至取代OS文件系統,是很是重要的。分佈式

內存管理器(memory manager):爲了不磁盤I/O帶來的性能損失,須要大量的內存。可是若是你要處理大容量內存你須要高效的內存管理器,尤爲是你有不少查詢同時使用內存的時候。函數

安全管理器(Security Manager):用於對用戶的驗證和受權。

客戶端管理器(Client manager):用於管理客戶端鏈接。

……

工具:

備份管理器(Backup manager):用於保存和恢復數據。

復原管理器(Recovery manager):用於崩潰後重啓數據庫到一個一致狀態。

監控管理器(Monitor manager):用於記錄數據庫活動信息和提供監控數據庫的工具。

Administration管理器(Administration manager):用於保存元數據(好比表的名稱和結構),提供管理數據庫、模式、表空間的工具。【譯者注:好吧,我真的不知道Administration manager該翻譯成什麼,有知道的麻煩告知,不勝感激……】

……

查詢管理器:

查詢解析器(Query parser):用於檢查查詢是否合法

查詢重寫器(Query rewriter):用於預優化查詢

查詢優化器(Query optimizer):用於優化查詢

查詢執行器(Query executor):用於編譯和執行查詢

數據管理器:

事務管理器(Transaction manager):用於處理事務

緩存管理器(Cache manager):數據被使用以前置於內存,或者數據寫入磁盤以前置於內存

數據訪問管理器(Data access manager):訪問磁盤中的數據

在本文剩餘部分,我會集中探討數據庫如何經過以下進程管理SQL查詢的:

客戶端管理器

查詢管理器

數據管理器(含復原管理器)

 

客戶端管理器

 

客戶端管理器是處理客戶端通訊的。客戶端能夠是一個(網站)服務器或者一個最終用戶或最終應用。客戶端管理器經過一系列知名的API(JDBC, ODBC, OLE-DB …)提供不一樣的方式來訪問數據庫。

客戶端管理器也提供專有的數據庫訪問API。

當你鏈接到數據庫時:

1  管理器首先檢查你的驗證信息(用戶名和密碼),而後檢查你是否有訪問數據庫的受權。這些權限由DBA分配。

2  而後,管理器檢查是否有空閒進程(或線程)來處理你對查詢。

3  管理器還會檢查數據庫是否負載很重。

4  管理器可能會等待一下子來獲取須要的資源。若是等待時間達到超時時間,它會關閉鏈接並給出一個可讀的錯誤信息。

5  而後管理器會把你的查詢送給查詢管理器來處理。

6  由於查詢處理進程不是『不全則無』的,一旦它從查詢管理器獲得數據,它會把部分結果保存到一個緩衝區而且開始給你發送。

7  若是遇到問題,管理器關閉鏈接,向你發送可讀的解釋信息,而後釋放資源。

查詢管理器

 

這部分是數據庫的威力所在,在這部分裏,一個寫得糟糕的查詢能夠轉換成一個快速執行的代碼,代碼執行的結果被送到客戶端管理器。這個多步驟操做過程以下:

1 查詢首先被解析並判斷是否合法

2 而後被重寫,去除了無用的操做而且加入預優化部分

3 接着被優化以便提高性能,並被轉換爲可執行代碼和數據訪問計劃。

4 而後計劃被編譯

5 最後,被執行

這裏我不會過多探討最後兩步,由於它們不過重要。

查詢解析器

每一條SQL語句都要送到解析器來檢查語法,若是你的查詢有錯,解析器將拒絕該查詢。好比,若是你寫成」SLECT …」 而不是 「SELECT …」,那就沒有下文了。

但這還不算完,解析器還會檢查關鍵字是否使用正確的順序,好比 WHERE 寫在 SELECT 以前會被拒絕。

而後,解析器要分析查詢中的表和字段,使用數據庫元數據來檢查:

1 表是否存在

2 表的字段是否存在

3 對某類型字段的運算是可能(好比,你不能將整數和字符串進行比較,你不能對一個整數使用 substring() 函數)

接着,解析器檢查在查詢中你是否有權限來讀取(或寫入)表。再強調一次:這些權限由DBA分配。

在解析過程當中,SQL 查詢被轉換爲內部表示(一般是一個樹)。

若是一切正常,內部表示被送到查詢重寫器。

查詢重寫器

在這一步,咱們已經有了查詢的內部表示,重寫器的目標是:

1 預優化查詢

2 避免沒必要要的運算

3 幫助優化器找到合理的最佳解決方案

重寫器按照一系列已知的規則對查詢執行檢測。若是查詢匹配一種模式的規則,查詢就會按照這條規則來重寫。下面是(可選)規則的非詳盡的列表:

視圖合併:若是你在查詢中使用視圖,視圖就會轉換爲它的 SQL 代碼。

子查詢扁平化:子查詢是很難優化的,所以重寫器會嘗試移除子查詢

例如:

SELECT PERSON.* FROM PERSON
WHERE PERSON.person_key IN 
(SELECT MAILS.person_key FROM MAILS
WHERE MAILS.mail LIKE 'christophe%');

會轉換爲:

SELECT PERSON.*

FROM PERSON, MAILS

WHERE PERSON.person_key = MAILS.person_key

and MAILS.mail LIKE 'christophe%';

1 去除沒必要要的運算符:好比,若是你用了 DISTINCT,而其實你有 UNIQUE 約束(這自己就防止了數據出現重複),那麼 DISTINCT 關鍵字就被去掉了。

2 排除冗餘的聯接:若是相同的 JOIN 條件出現兩次,好比隱藏在視圖中的 JOIN 條件,或者因爲傳遞性產生的無用 JOIN,都會被消除。

3 常數計算賦值:若是你的查詢須要計算,那麼在重寫過程當中計算會執行一次。好比 WHERE AGE > 10+2 會轉換爲 WHERE AGE > 12 , TODATE(「日期字符串」) 會轉換爲 datetime 格式的日期值。

4 (高級)分區裁剪(Partition Pruning):若是你用了分區表,重寫器可以找到須要使用的分區。

5 (高級)物化視圖重寫(Materialized view rewrite):若是你有個物化視圖匹配查詢謂詞的一個子集,重寫器將檢查視圖是否最新並修改查詢,令查詢使用物化視圖而不是原始表。

6 (高級)自定義規則:若是你有自定義規則來修改查詢(就像 Oracle policy),重寫器就會執行這些規則。

7 (高級)OLAP轉換:分析/加窗 函數,星形聯接,ROLLUP 函數……都會發生轉換(但我不肯定這是由重寫器仍是優化器來完成,由於兩個進程聯繫很緊,必須看是什麼數據庫)。

物化視圖 。謂詞,predicate,條件表達式的求值返回真或假的過程】

重寫後的查詢接着送到優化器,這時候好玩的就開始了。

統計

研究數據庫如何優化查詢以前咱們須要談談統計,由於沒有統計的數據庫是愚蠢的。除非你明確指示,數據庫是不會分析本身的數據的。沒有分析會致使數據庫作出(很是)糟糕的假設。

可是,數據庫須要什麼類型的信息呢?

我必須(簡要地)談談數據庫和操做系統如何保存數據。二者使用的最小單位叫作頁或塊(默認 4 或 8 KB)。這就是說若是你僅須要 1KB,也會佔用一個頁。要是頁的大小爲 8KB,你就浪費了 7KB。

回來繼續講統計!當你要求數據庫收集統計信息,數據庫會計算下列值:

1表中行和頁的數量

2表中每一個列中的:

惟一值
數據長度(最小,最大,平均)
數據範圍(最小,最大,平均)

3表的索引信息

這些統計信息會幫助優化器估計查詢所需的磁盤 I/O、CPU、和內存使用

對每一個列的統計很是重要。
好比,若是一個表 PERSON 須要聯接 2 個列: LAST_NAME, FIRST_NAME。
根據統計信息,數據庫知道FIRST_NAME只有 1,000 個不一樣的值,LAST_NAME 有 1,000,000 個不一樣的值。
所以,數據庫就會按照 LAST_NAME, FIRST_NAME 聯接。
由於 LAST_NAME 不大可能重複,多數狀況下比較 LAST_NAME 的頭 2 、 3 個字符就夠了,這將大大減小比較的次數。

不過,這些只是基本的統計。你可讓數據庫作一種高級統計,叫直方圖。直方圖是列值分佈狀況的統計信息。例如:

出現最頻繁的值

分位數

  這些額外的統計會幫助數據庫找到更佳的查詢計劃,尤爲是對於等式謂詞(例如: WHERE AGE = 18 )或範圍謂詞(例如: WHERE AGE > 10 and AGE < 40),由於數據庫能夠更好的瞭解這些謂詞相關的數字類型數據行(注:這個概念的技術名稱叫選擇率)。

統計信息保存在數據庫元數據內,例如(非分區)表的統計信息位置:

Oracle: USER / ALL / DBA_TABLES 和 USER / ALL / DBA_TAB_COLUMNS

DB2: SYSCAT.TABLES 和 SYSCAT.COLUMNS

  統計信息必須及時更新。若是一個表有 1,000,000 行而數據庫認爲它只有 500 行,沒有比這更糟糕的了。統計惟一的不利之處是須要時間來計算,這就是爲何數據庫大多默認狀況下不會自動計算統計信息。數據達到百萬級時統計會變得困難,這時候,你能夠選擇僅作基本統計或者在一個數據庫樣本上執行統計。

  舉個例子,我參與的一個項目須要處理每表上億條數據的庫,我選擇只統計10%,結果形成了巨大的時間消耗。本例證實這是個糟糕的決定,由於有時候 Oracle 10G 從特定表的特定列中選出的 10% 跟所有 100% 有很大不一樣(對於擁有一億行數據的表,這種狀況極少發生)。此次錯誤的統計致使了一個本應 30 秒完成的查詢最後執行了 8 個小時,查找這個現象根源的過程簡直是個噩夢。這個例子顯示了統計的重要性。

注:固然了,每一個數據庫還有其特定的更高級的統計。若是你想了解更多信息,讀讀數據庫的文檔。話雖然這麼說,我已經盡力理解統計是如何使用的了,並且我找到的最好的官方文檔來自PostgreSQL

查詢優化器

 

全部的現代數據庫都在用基於成本的優化(即CBO)來優化查詢。道理是針對每一個運算設置一個成本,經過應用成本最低廉的一系列運算,來找到最佳的下降查詢成本的方法。

爲了理解成本優化器的原理,我以爲最好用個例子來『感覺』一下這個任務背後的複雜性。這裏我將給出聯接 2 個表的 3 個方法,咱們很快就能看到即使一個簡單的聯接查詢對於優化器來講都是個噩夢。以後,咱們會了解真正的優化器是怎麼作的。

對於這些聯接操做,我會專一於它們的時間複雜度,可是,數據庫優化器計算的是它們的 CPU 成本、磁盤 I/O 成本、和內存需求。時間複雜度和 CPU 成本的區別是,時間成本是個近似值(給我這樣的懶傢伙準備的)。而 CPU 成本,我這裏包括了全部的運算,好比:加法、條件判斷、乘法、迭代……還有呢:

每個高級代碼運算都要特定數量的低級 CPU 運算。

對於 Intel Core i七、Intel Pentium 四、AMD Opteron…等,(就 CPU 週期而言)CPU 的運算成本是不一樣的,也就是說它取決於 CPU 的架構。

使用時間複雜度就容易多了(至少對我來講),用它我也能瞭解到 CBO 的概念。因爲磁盤 I/O 是個重要的概念,我偶爾也會提到它。請牢記,大多數時候瓶頸在於磁盤 I/O 而不是 CPU 使用。

索引

在研究 B+樹的時候咱們談到了索引,要記住一點,索引都是已經排了序的。

僅供參考:還有其餘類型的索引,好比位圖索引,在 CPU、磁盤I/O、和內存方面與B+樹索引的成本並不相同。

另外,不少現代數據庫爲了改善執行計劃的成本,能夠僅爲當前查詢動態地生成臨時索引。

存取路徑

在應用聯接運算符(join operators)以前,你首先須要得到數據。如下就是得到數據的方法。

注:因爲全部存取路徑的真正問題是磁盤 I/O,我不會過多探討時間複雜度。

四種類型的Oracle索引掃描介紹

1 全掃描

若是你讀過執行計劃,必定看到過『全掃描』(或只是『掃描』)一詞。簡單的說全掃描就是數據庫完整的讀一個表或索引。就磁盤 I/O 而言,很明顯全表掃描的成本比索引全掃描要高昂。

2 範圍掃描

其餘類型的掃描有索引範圍掃描,好比當你使用謂詞 」 WHERE AGE > 20 AND AGE < 40 」 的時候它就會發生。

固然,你須要在 AGE 字段上有索引才能用到索引範圍掃描。

在第一部分咱們已經知道,範圍查詢的時間成本大約是 log(N)+M,這裏 N 是索引的數據量,M 是範圍內估測的行數。多虧有了統計咱們才能知道 N 和 M 的值(注: M 是謂詞 「 AGE > 20 AND AGE < 40 」 的選擇率)。另外範圍掃描時,你不須要讀取整個索引,所以在磁盤 I/O 方面沒有全掃描那麼昂貴。

3 惟一掃描

若是你只須要從索引中取一個值你能夠用惟一掃描。

4 根據 ROW ID 存取

多數狀況下,若是數據庫使用索引,它就必須查找與索引相關的行,這樣就會用到根據 ROW ID 存取的方式。

例如,假如你運行:

SELECT LASTNAME, FIRSTNAME
from
PERSON
WHERE
AGE = 28

若是 person 表的 age 列有索引,優化器會使用索引找到全部年齡爲 28 的人,而後它會去表中讀取相關的行,這是由於索引中只有 age 的信息而你要的是姓和名。

可是,假如你換個作法:

SELECT TYPE_PERSON.CATEGORY from PERSON ,TYPE_PERSON

WHERE PERSON.AGE = TYPE_PERSON.AGE

PERSON 表的索引會用來聯接 TYPE_PERSON 表,可是 PERSON 表不會根據行ID 存取,由於你並無要求這個表內的信息。

雖然這個方法在少許存取時表現很好,這個運算的真正問題實際上是磁盤 I/O。假如須要大量的根據行ID存取,數據庫也許會選擇全掃描。

其它路徑

我並無列舉全部的存取路徑,若是感興趣,能夠查看各個數據庫相關文檔。

相關文章
相關標籤/搜索