數(shù)據(jù)庫是如何獲取并處理數(shù)據(jù)的
SQL是結(jié)構(gòu)化查詢語言(Structured Query Language)的簡稱,用戶只需要通過SQL告訴數(shù)據(jù)庫需要什么,至于怎么去做就是數(shù)據(jù)庫管理系統(tǒng)(DBMS)需要考慮的問題。
上篇文章《SQL語句處理》介紹了數(shù)據(jù)庫執(zhí)行的四個(gè)階段,每個(gè)階段都做了什么工作。今天這篇文章繼續(xù)探討數(shù)據(jù)庫是如何獲取到用戶所需的數(shù)據(jù)。
如下所探討的內(nèi)容僅適用于Oracle單機(jī)環(huán)境,RAC環(huán)境稍有不同,不在本篇討論范圍內(nèi)。
數(shù)據(jù)庫的所有操作增、刪、改、查都是在內(nèi)存中完成的,為了處理數(shù)據(jù),數(shù)據(jù)庫首先需要將數(shù)據(jù)從磁盤讀取到內(nèi)存中,然后進(jìn)行相應(yīng)的操作。但是內(nèi)存和磁盤的讀寫速度有著天壤之別。DDR4內(nèi)存讀寫速度大概50G每秒(50000M),固態(tài)硬盤速度是300M每秒,是內(nèi)存的二百分之一,機(jī)械硬盤的速度是100M每秒,是內(nèi)存的五百分之一。為了解決兩者之間的速度差的問題,誕生了緩存的概念。緩存的作用就是避免每次獲取數(shù)據(jù)時(shí)都從緩慢的磁盤讀取,而是將之前訪問過的數(shù)據(jù)緩存在內(nèi)存中,后續(xù)操作如果需要相同數(shù)據(jù)時(shí),直接從內(nèi)存獲取,大大提升讀取速度。
Oracle內(nèi)存結(jié)構(gòu)中有一個(gè)很重要的結(jié)構(gòu)叫做DB Buffer Cache,DB Buffer Cache位于SGA中,正常系統(tǒng)中此部分內(nèi)存占整個(gè)Oracle內(nèi)存結(jié)構(gòu)大絕大部分。如果DB Buffer Cache很大,幾十G,甚至是幾百G,每次在其中搜索所需的塊是否存在,也需要很長的時(shí)間,因此,為了提高效率,Oracle將DB Buffer Cache劃分為多個(gè)區(qū)域,每個(gè)區(qū)域稱為一個(gè)工作集(workset),每個(gè)工作集又被劃分為多個(gè)hash buckets,一個(gè)hash buckets管理著一個(gè)或多個(gè)數(shù)據(jù)塊。為了保證數(shù)據(jù)的一致性,hash buckets訪問是串行的,由相應(yīng)的latch保護(hù),只有獲取到相應(yīng)的latch的會(huì)話才能到hash buckets上搜索數(shù)據(jù)塊。
1、查詢語句的執(zhí)行
假設(shè)有如下查詢語句:
SELECT * FROM employees WHERE employee_id=199;
表employees表結(jié)構(gòu)如下,employee_id是表的主鍵。
先看下上述sql語句的執(zhí)行計(jì)劃
首先根據(jù)索引EMP_EMP_ID_PK查找employee_id=199鍵,獲取到鍵所對應(yīng)的值ROWID,ROWID指向數(shù)據(jù)塊的真實(shí)地址。通過ROWID獲取數(shù)據(jù)塊,獲取到數(shù)據(jù)塊后,再從數(shù)據(jù)塊中獲取符合條件的數(shù)據(jù)。在此過程中至少需要訪問兩個(gè)數(shù)據(jù)塊(為了簡化過程,忽略其他數(shù)據(jù)塊),一個(gè)是鍵為199索引塊,假設(shè)為100號塊;另一個(gè)是索引塊所指向的數(shù)據(jù)塊,假設(shè)為1000號塊。
前面緩存介紹中提到,為了提升數(shù)據(jù)的訪問速度,Oracle會(huì)將訪問過的數(shù)據(jù)塊緩存到DB Buffer cache中,以備重復(fù)使用。因此上面獲取100號索引塊的時(shí)候,需要先到DB Buffer cache中查找100號索引塊是否已存在,以減少昂貴的磁盤讀。在搜索DB Buffer cache前,必須獲取相應(yīng)的latch后才能進(jìn)行搜索。如果未獲取到latch,此時(shí)會(huì)進(jìn)入等待。待獲取到latch后便可以對DB Buffer cache搜索。
如果100號索引塊已存在于內(nèi)存中,且沒有會(huì)話正在修改數(shù)據(jù)塊內(nèi)容,直接訪問內(nèi)存中的數(shù)據(jù)塊,獲取鍵為199的數(shù)據(jù)。如果有會(huì)話正在修改此數(shù)據(jù)塊內(nèi)容,則需要通過undo數(shù)據(jù)和當(dāng)前的數(shù)據(jù)塊構(gòu)造一個(gè)一致性讀版本的數(shù)據(jù)塊,來讀取會(huì)話修改前的歷史版本數(shù)據(jù)。
如果100號索引塊不在內(nèi)存中,首先判斷是否有其他會(huì)話正在將100號索引塊讀入內(nèi)存,如果是,則等待。否則通知后臺(tái)進(jìn)程將100號索引塊從磁盤讀入內(nèi)存,在將100號索引塊讀入內(nèi)存前,需要在內(nèi)存中找到一塊合適的空閑塊,以便保存即將讀入的100號索引塊。搜索空閑塊時(shí)同樣需要獲得latch。
如果內(nèi)存中沒有空閑的空間,則通知后臺(tái)刷新進(jìn)程刷新臟頁,以騰出空閑空間。
如果內(nèi)存中有空閑的空間,直接將100號索引塊讀入內(nèi)存。獲取到ROWID后,再去讀取數(shù)據(jù)塊,讀取數(shù)據(jù)塊的過程同讀取索引塊,讀取到數(shù)據(jù)塊后再從數(shù)據(jù)塊中獲取符合條件的行,返回給客戶端。
2、DML語句執(zhí)行
假設(shè)有如下更新語句:
UPDATE employees SET first_name='Hello',last_name='Kitty' WHERE employee_id=199;
update語句執(zhí)行計(jì)劃如下:
DML語句都存在相應(yīng)的查詢過程,因?yàn)橹挥胁樵兊搅怂枰薷牡臄?shù)據(jù),才能進(jìn)行相應(yīng)的操作。查詢的過程大致與查詢語句執(zhí)行過程相同。但也有如下不同點(diǎn):
獲取latch時(shí),如果是只讀操作,多個(gè)會(huì)話可以同時(shí)獲取相同的latch,如果是DML操作,則不可以同時(shí)獲得。
只讀操作多個(gè)會(huì)話可以同時(shí)讀取同一個(gè)數(shù)據(jù)塊,DML操作則不可以多個(gè)會(huì)話同時(shí)修改同一個(gè)數(shù)據(jù)塊。
獲取到數(shù)據(jù)塊后進(jìn)行相應(yīng)的操作,并記錄對應(yīng)的undo信息和redo log信息,完成update操作。