數據庫,你好!

大學時就開始學習數據庫,到工做2年多了,接觸過不少數據庫相關的產品,如Oracle/MySQL這樣的RDBMS,Redis/Mongodb這樣的NoSQL,還有Hive/HBase這樣的大數據技術。雖說招式學了不少,而好的程序員應當知道數據庫基本原理,索引基本原理,常見優化手段等。花了近一個月的時間對數據庫的這些知識進行學習,收穫不少,之前數據庫在我眼中就是個增刪改查的東西,而如今想法發生了不少改變!程序員


組成數據庫的2個要素數據庫


wKiom1ajFA-BXSznAAALRpdIvyo206.png


數據庫中的數據存儲在哪裏?緩存

能夠是磁盤,或者磁盤組成的陣列,或者內存,或者某些存儲設備,總之數據應該有地方能夠存儲。併發


然而僅僅存儲數據是不夠的,一塊儲存有數據的硬盤,可以稱之爲數據庫嗎?ide

應該有這樣的程序:封裝對存儲的操做細節,提供給外界簡單的API。這樣的程序就是實例。高併發


所以,數據庫的啓動,應當是實例對存儲的服務啓動,提供給外界簡單的API來完成數據操做。性能


那麼進一步思考下,存儲和實例是什麼關係呢?是一對一,又或者是多對多呢?學習


若是,存儲有多份,甚至這多份之間還不在一個機房,一個地方,這不就是冗餘備份,異地容災嗎?大數據

若是,一個存儲對應多個實例,那麼將會提升外界對這個存儲的負載能力以及達到高可用。一個實例掛了,不用擔憂,還有其餘的實例來確保這個存儲對外界的可用性。優化



數據庫的物理與邏輯轉換


塊的概念


數據庫的數據是如何存儲的,這很是重要,由於如何存儲決定了如何訪問數據。

試想,若是按行存儲,按行訪問,或者咱們採用JAVA相似的隨機訪問的方式,先定位位置後在讀取一段內容,數據庫會這樣作嗎?

行,是抽象的,能夠很大,也能夠很小,粒度是不可控的,並且行仍是邏輯上的概念,並不利於數據庫進行物理上的調度和管理。那麼數據庫的數據到底如何存儲呢?


wKiom1ajIN7iHctMAAAKDNBv8QQ046.png

數據庫會對存儲進行物理大小的劃分(好比16K),叫作Block(或者Page)。


一個Block內會存儲不少行,數據庫在加載一個Block時,一般並不會僅僅只加載這個Block,而是會將這個Block周圍的Block都加載至內存,由於會顯著的提高內存命中率以及減小IO次數。爲了方便管理,應該要對Block進行編號,以及Block內的行進行編號。(也許編號並非惟一的,可是與某些高層次的編號組合起來就是惟一能夠定位了,例如Oracle的rowid就是表空間、對象編號,塊編號,塊內行編號共同組成,所以根據rowid就能夠快速定位到數據。)


要知道在文件系統上,並無Block的概念,Block不過是數據庫自認爲的東西,那麼數據庫如何定位以及讀取一個Block呢?


wKioL1ajKIKRhN0_AAAE1K14aVk134.png


若是咱們有一個LIST,它記錄了塊的ID,以及塊在文件系統中的偏移量,那麼咱們定位一個塊就很簡單了。只須要先找到它在文件系統中的偏移量,而後讀取固定大小的數據,就至關於讀取了該塊的內容了。這個LIST,如同不少數據庫的元數據信息同樣,即數據字典。至於,讀取塊的內容,固然就是磁盤IO了,這也是數據庫提高性能的一大障礙。要知道一塊磁盤只有一個磁頭,全部的請求都串行化,自己讀取的速度並不慢,慢就慢在須要磁頭定位以及尋道。所以,咱們但願加大內存,加大內存命中率,減小磁盤IO,可是畢竟內存是有限的,必然存在一些數據被加載內存,一些數據從內存中OUT,從新寫回磁盤。數據庫的這種對內存的管理策略,可能相似LRU最近最少使用,或者其餘更加「智能」點的策略。通常數據量較小,而且並不頻繁更新的信息,好比數據字典這類數據就能夠常駐內存中。


塊的層次


wKiom1ajLaawmSa6AABC6JCTsWs620.png

咱們知道每一個學生都有一個學號,可是僅僅有一個學號是不夠的,一般學生都是XXX年級YYY班級的,對學生進行一個層次的抽象,這樣將方便學生的管理!而數據庫中塊也是同樣的,若是將多個塊組成一個group,多個group組成一個segment,多個segment組成......當塊造成了層次後,天然方便了更高層次的數據管理,好比一個表的多個分區的管理。


表的修改以及刪除


若是一個塊的大小是16K,一行數據100Byte,也就是塊內至多能夠儲存160行數據。

假設咱們要對錶結構進行修改,增長一個字段,而且修改後,當即填充此字段的值,那麼會發生什麼?


若是塊內數據存儲是滿的,那麼天然要在新塊中進行存儲,也就是說一行數據被分在了不一樣塊中進行存儲,那麼原本讀取一個塊就能夠知道一行數據的,如今變成了2個塊,也便是增長了IO的次數。也就是在這種狀況下,出現了這樣的狀況:修改表的結構居然使得讀取變慢了!


若是塊內數據存儲不是滿的,即自己塊內是預留了一部分空間,那麼可能增長一個較小的字段,還能存放下,增長較大的字段,那麼仍是會出現上面假設的狀況。


假設咱們要刪除一個表的數據呢?

表的數據存在哪裏呢?塊裏面有,內存裏面也有吧!

若逐行刪除數據,會很慢,爲什麼?


既然要按照行去刪除數據,那麼必需要先找到數據,那麼天然涉及到塊的定位,行的定位等;而若是基於塊的體系結構去刪除,那麼確定會快不少;若是數據對應的就是文件的話,那麼直接刪除文件好了。

固然有些數據庫的刪除表數據,是一種「假刪除」,會更加快,並且刪錯了還能夠「吃後悔藥」。這種「假刪除」,只須要將原表的元數據的狀態置爲不可用,讓外界訪問不到它,若是想恢復,那麼很簡單,只須要重置它的狀態爲可用就能夠了。


那麼內存中的數據如何處理呢?

爲了使得每次訪問數據,不至於都發生磁盤IO,所以內存中會有一部分數據存在,若是將數據庫的內存配置很大,好比48G,會裝入不少數據,這種狀況下若是進行內存區域的所有掃描,也會很花費時間,那麼如何快速定位一個表在內存中的數據呢?


wKioL1ajQwvAvzIGAAA4FmTTAhg488.png



預編譯SQL


做爲Java開發,咱們常常說對數據庫的SQL應該要採起PreparedStatement這種預編譯的方式,不要採用字符串拼接的方式,由於能夠防止SQL注入以及更加快。那麼爲何預編譯SQL會快呢?


數據庫拿到SQL後,應該要對其進行解析操做,要將SQL轉變成操做,是讀,是寫,什麼表,表與表什麼關係,什麼字段,什麼條件等等。


若是每來一個SQL,都進行解析操做,那麼在高併發的系統當中,是否能夠優化呢?對於像Oracle這樣的數據庫,對於SQL有「軟解析」和「硬解析」。若是將有些SQL的解析結果緩存起來,當下次還有這樣的SQL請求時,就能夠不用作解析操做,直接拿取緩存中的解析結果就能夠了,這樣的SQL解析便是「軟解析」。而對於採用字符串拼接的SQL,數據庫會認爲每次SQL都不一樣,所以都會進行解析操做,這就是「硬解析」。要知道SQL解析結果的緩存也是存放在內存中,所以也有大小限制,對於硬SQL,會頻繁對SQL解析緩存IN/OUT,致使本該留在SQL解析緩存的SQL被替換,而軟解析很好的避免了這種狀況。


爲什麼要小表驅動大表?


之前常常遇到這樣的狀況:有2張表T1,T2,T1數據量較小(好比幾十條),T2數據量很大(好比千萬級別)。咱們說應該讓T1做爲驅動表,這樣會快不少。


爲何會快不少呢?誰做爲驅動表有關係嗎?M*N和N*M大小不是同樣的嗎?這根本上說不通啊。


其實是這樣的,咱們但願小表驅動大表,是想外層循環用小表,這樣外層循環的次數不多,內層循環用大表,但願大表的查詢去走索引,即便大表很大,走索引的話,也會很快。所以這纔是小表驅動大表快的根本緣由。反之,若是大表做爲外層循環,那麼即便小表走索引,因爲外層循環次數太大,也會很慢的。



索引的基本原理


索引就像是一本書的目錄同樣,能讓咱們快速的找到具體的章節,可是若是增長了書的內容後,也得更新目錄。目錄一般應該是有序的,目錄讓查找變快,也讓增長變慢。


那麼爲何索引有這樣的做用呢?


索引,是一種結構,好比主流的B+樹索引,非主流的HASH索引;索引是一種數據,也須要儲存起來。下面咱們就來簡單分析下B+樹索引的基本原理。


索引,也是儲存在Block上,好比對於A表的F字段上創建了索引的話,那麼索引一般會存儲該字段的值,以及對應的數據位置,以便和數據內容聯繫起來。要知道,索引存儲的寬度要比數據存儲區域的寬度小的多,所以一個索引Block每每會存儲上萬級別的數據,比數據Block要多不少。

wKiom1akQ7aQtAR5AAAWyv6Vxjo789.png

若是select F from A where F = 1,那麼會怎麼樣呢?


要知道F上創建了索引,若是咱們直接走索引查找的話,那麼遍歷索引存儲區域,只須要讀數量很小的Block就能夠獲得了,並且在索引上就有F值,所以查找到就能夠直接返回了。而若是走數據存儲區域查找的話,那麼要遍歷數量巨大的Block。


咱們再來看一條SQL:select F , P from A where F = 1,又會怎麼樣呢?(P只是普通字段)

固然咱們能夠先走索引肯定數據範圍,可是因爲索引上並無P值,此時就得「回表」查找,也就是利用索引查找到的位置,到源表中取得數據。


此時此刻,咱們應該對索引有所體會了,可是好像沒有什麼樹的概念?


隨着數據量的增大,固然索引也會變大,會變成多層次的結構,如同數據塊同樣!


wKioL1akSKjjFH9MAAA97M_DuMY207.png


到這裏,好像有點樹的形象了,實際上出現了索引管理塊的概念,也許隨着索引數據的增多,會出現多層次的索引管理塊。真正存放索引數據的塊,稱爲葉子塊,葉子塊存有雙向指針,主要是爲了快速遍歷而設計的。好比:

select count(F) from A

F在A表上是創建了索引的,那麼找出F的數量,很簡單,就是遍歷葉子塊大小而已,而經過雙向指針又加速了遍歷,因此很快咯。


結束語

之前,咱們所相信的,認爲的那些事情,是有緣由的,有場景的,我想只有懂一點數據庫基本原理,才能更好的運用到工做上。

相關文章
相關標籤/搜索