說到數據庫這個詞,我只能用愛恨交加這個詞來形容它。兩年前在本身還單純懵懂的時候進了數據庫的課堂,聽完數據庫的課,以爲這是一門再簡單不過的課程,任何一門編程語言都比SQL要晦澀難懂,任何一門理論課程都比數據庫關係要複雜得多。直到從被面試官按在地上摩擦,到工做中那一條條使人髮指的慢查詢SQL,這就已經徹底顛覆了我對數據庫的見解。在有各類數據庫工具的今天,咱們是看不到那簡單到不能再簡單的一張表的背後,隱藏着多少數據結構的支撐,也看不到咱們隨手敲的一條SELECT,背後會有多少算法和數據結構在給咱們作優化,做爲一個有技術熱情的coder,應該須要對咱們每日在用的數據庫作一次深刻了解。面試
這個問題很寬泛,須要咱們對於總體有一個掌控,對咱們平時所用的數據庫要有足夠的瞭解,對整個數據庫作模塊劃分是這道題的關鍵,這就更須要咱們足夠了解數據庫,對數據庫作一個合理的模塊設計。算法
從開始,首先要明白,數據庫的最最根本的做用是什麼——存儲數據,因此咱們須要一個存儲模塊來存儲咱們的數據,它能夠是一個文件系統(機械硬盤?SSD固態硬盤?)。但光有存儲模塊是不夠的,咱們還須要一個程序實例,來組織或者獲取這些數據,在程序實例中咱們須要提供獲取和組織這些數據的方式,因此咱們須要在程序實例中實現存儲管理模塊。咱們還能夠在存儲管理模塊中作一些提高效能的工做,例如同時讀取多行、分塊分頁存儲等。數據庫做爲一款對性能要求極高的軟件,咱們應該加入緩存機制,來提升其速度,當查詢到緩存中已存在的數據,咱們應該直接將其從緩存中讀取,這樣能夠減小硬盤IO次數,提升效能。到這裏爲止,咱們已經完成了對數據庫的存儲方面的功能設計,可是做爲數據庫,應該須要提供給用戶對數據進行增刪改查的接口,即平時所寫的SQL,因此咱們應該提供一個SQL解析模塊,來對平常用戶所寫的SQL語句進行解析,轉換成機器可識別的指令,咱們也能夠直接將編譯過的SQL加入緩存,下次再有一樣的SQL就直接從緩存中讀取,一樣能夠提升效能。做爲一款成熟的數據庫,須要應對各類複雜的環境,要時刻記錄數據庫的狀態,因此咱們還須要一個日誌管理模塊,對操做和錯誤信息進行記錄。數據庫中須要支持多用戶操做,但每一個用戶都能操做全部的數據,這是不現實的,因此還須要權限劃分模塊對數據庫用戶進行權限管理。固然數據庫說到底也只是一款軟件,是軟件就會有bug,就會出故障,故障不可怕,可怕的是在數據庫這種敏感軟件下對故障沒有特殊的處理方式,致使數據丟失,畢竟數據是無價的,因此數據庫應該引入容災機制,在數據庫掛了的時候,對數據進行恢復。還有做爲數據庫最重要的兩個模塊,也是現今任何一個數據庫都須要考慮的問題——併發和查找效率,因此還需引入索引和鎖這兩個模塊,這就是實現一個最基礎的數據庫所必需的幾大模塊。sql
綜上對數據庫設計模塊作一個彙總:
1.存儲模塊
2.程序實例
2.1存儲管理模塊
2.2緩存機制
2.3SQL解析模塊
2.4日誌管理模塊
2.5權限劃分模塊
2.6容災機制
2.7索引模塊
2.8鎖模塊
數據庫
要考慮這個問題,首先要從最基礎的查找表中數據的過程開始提及。一般咱們在查找一個序列中的某一個元素時,用到的最簡單的方式就是遍歷,數據庫也是同樣,在一張表中查找某一行數據時,若是不考慮索引的情況下,也會採用一個逐行掃描的方式,只不過數據庫一般以塊或者頁爲單位,因此它一般將整個塊或者頁加載進內存,而後逐塊輪詢查找到結果並返回。若是數據庫中只有少許數據,那麼進行全表掃描,速度仍是會很快,可是若是在數據量很大的表中,這種方法就再也不適用了,在數據量很大的表中,因爲逐行掃描代價變大,一般須要避免採用這種逐行掃描的方式進行數據查找,數據庫爲了使查詢變得高效,因此引入了索引這種方式對數據進行查找。編程
1.主鍵、惟一鍵、普通鍵緩存
衆所周知,二叉查找樹是每一個節點最多由兩個子樹的樹結構,而其還有一個特色是,在任意一顆樹中,根節點的左孩子永遠小於根節點,根節點的右孩子永遠大於根節點,用二叉查找樹做爲索引,確實能夠提升查找效率,其可使用二分查找將時間複雜度控制在O(lgn),可是二叉查找樹有一個顯而易見的缺陷,當某種特殊狀況(按照某個特定順序插入樹)發生時,二叉查找樹將變爲下圖右側(線性二叉樹)的情況: bash
B-Tree,平衡多路查找樹,若是每一個節點,最多有N個孩子,那麼這樣的樹就叫N階B-Tree, 每一個節點中主要包含關鍵字和指向孩子的指針,最多能有幾個孩子,取決於節點的容量和數據庫的相關配置,一般狀況下這個N是很大的。
B-Tree做爲一種數據結構,有以下特徵:
1.根節點至少包含兩個孩子
2.樹中每一個節點至多含有N個孩子(N>=2)
3.除根節點和葉節點外,其它每一個節點至少有ceil(N/2)個孩子。(ceil表示取上限,例如1.2的上限爲2,1.1的上限也爲2,非四捨五入)
4.全部葉子節點都位於同一層,即葉子節點的高度都是同樣的。
5.假設每一個非終端節點包含n個關鍵字信息(P0,P1...Pn,k1...kn)
網絡
( a )ki(i=1..n)爲關鍵字,且關鍵字按順序升序排序k(i-1)<ki。
( b )關鍵字的個數必須知足:[ceil(m/2)-1]<=n<=m-1]。
( c )非葉子節點的指針:P[1],P[2]...P[M];其中P[1]指向關鍵字小於K[1]的子樹,P[N]指向關鍵字大於K[N-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1],K[i])的子樹。
數據結構
B+ -Tree是B-Tree的一個變體,其定義基本與B樹相同,除了:
1.非葉子節點的子樹指針與關鍵字個數相同,其代表B+樹能存儲更多的關鍵字
2.非葉子節點的子樹指針P[i],指向關鍵字值[K[i],K[i+1])的子樹。
3.非葉子節點僅用來作索引,數據到保存在葉子節點中。(B+樹的全部檢索都是從根部開始,直到搜索到葉子節點結束。)
4.全部葉子節點均有一個鏈指針,指向下一個葉子節點。(方便直接在葉子節點直接作範圍統計)
架構
Hash索引是根據Hash結構的定義,只須要一次運算即可以找到數據所在位置,不像B+樹或者B樹須要從根結點出發尋找數據,因此Hash索引的查詢效率理論上要高於B+樹索引,可是MySQL中並無採用這一種索引,這是因爲這種索引除查詢效率以外的缺陷是十分明顯的。
1.僅僅只能知足"=","IN",不能使用範圍查詢。因爲其是由Hash運算獲取的數據存放位置,每次Hash運算獲取的是一個肯定的值,且這個值並不與數據自己的大小有關係,因此其並不能知足範圍查詢。
2.沒法被用來避免數據的排序操做。和1的意思差很少,Hash的索引值是由Hash運算獲取的,其索引值與數據自己的大小並沒有明顯關係。
3.不能利用部分索引鍵查詢。
4.不能避免表掃描。因爲Hash索引會產生Hash衝突,存在Hash衝突的數據會被鏈接到同一個鏈表上,當大量數據被鏈接到相同鏈表上時,查詢某條數據就變成了掃描該鏈表,時間複雜度並不能保證在O(1)。
5.遇到大量Hash值相等的狀況後性能並不必定就會比B-Tree索引高。
位圖索引,當表中的某個字段只有幾種值的時候,例如:性別,此時用位圖索引是一個最佳的選擇。目前使用位圖索引的比較主流的數據庫有Oracle數據庫。
1.密集索引文件中的每一個搜索碼都對應一個索引值,稀疏索引文件只爲索引碼的某些值創建索引項。
2.密集索引將數據存儲與索引放到了一塊,找到索引也就找到了數據,稀疏索引將數據和索引分開存儲,索引結構的葉子節點指向數據的對應行。
首先先創建一張表
CREATE DATABASE sqltest;
use sqltest;
create table tb_test(
test_id int primary key auto_increment,
test_name varchar(1024),
test_date datetime,
test_desc varchar(1024)
);
複製代碼
在這張表中灌入200w數據。
#查找慢日誌
slow_query_log
複製代碼
設置慢查詢閾值爲1秒,重連數據庫 set global long_query_time = 1;
製造慢查詢:
explain select test_name from tb_test order by test_name desc
extra:能夠用來輔助type幫助咱們進行SQL優化,extra中出現如下兩項,意味着MySQL根本不能使用索引,效率會受到重大影響,應該儘量對此進行優化。
Using filesort:表示MySQL會對結果使用一個外部索引排序,而不是從表裏按索引次序讀到相關內容,可能在內存或者磁盤上進行排序。MySQL中沒法例用索引完成的排序操做稱爲「文件排序」。
Using temporary:表示MySQL在對查詢結果排序時使用臨時表,常見於排序 order by和分組查詢group by。
3.修改SQL,儘可能讓SQL走索引
咱們能夠知道,建立表時,咱們將id設爲主鍵,那麼id也就天然稱爲了索引,因此咱們只要修改排序字段爲id,便可以經過索引排序。
explain select test_id from tb_test order by test_id desc
#加索引
ALTER TABLE tb_test add index index_name(test_name);
#再次分析
explain select test_name from tb_test order by test_name desc;
複製代碼
結果:
上文中只是用了單一索引對錶進行排序,若是使用聯合索引又會是什麼樣的一種情況? 最左匹配原則:假設數據表中有兩列,A and B,咱們將A、B設置爲聯合索引,而後在where語句中調用where A = ? AND B = ?,該查詢語句會使用AB聯合索引,調用where A = ?,該查詢語句也會使用AB聯合索引,但當調用where B = ?時,它將不會使用AB聯合索引。
官方定義:
1.最左前綴匹配原則,MySQL會一直向右匹配直到遇到範圍查詢(>、<、between、like)就中止匹配,好比 a=3 and b=4 and c>5 and d=6,若是創建(a,b,c,d)順序的索引,d是沒法使用索引的,若是創建(a,b,d,c)的索引則均可以使用到,a、b、d的順序能夠任意調整。
2.=和in能夠亂序,好比 a=1 and b=2 and c=3 創建(a,b,c)索引能夠任意順序,MySQL的查詢優化器會幫你優化成索引能夠識別的形式。
答:NO,數據量小的表不須要創建索引,創建會增長額外的索引開銷。另外數據變動須要維護索引,所以更多的索引意味着更多的維護成本。更多的索引還須要消耗更多的空間。
1.MyISAM默認使用表級鎖,不支持行級鎖。 2.InnoDB默認使用行級鎖,也支持表級鎖。 3.InnoDB在使用索引時,默認使用行級鎖,但當其沒有用到索引時,默認使用表級鎖。
事務:做爲單個邏輯單元執行的一個操做,要麼所有完成,要麼所有失敗。
1.更新丟失 —— 一個事務的更新,引發另外一個事務提交的丟失,MySQL全部事務隔離級別在數據庫層面上都可避免。
2.髒讀 —— 一個事務讀到另外一個事務未提交的數據,READ-COMMITTED事務隔離級別以上可避免。(ORACLE默認隔離級別爲READ-COMMITTED)
3.不可重複讀——事務A屢次讀取同一數據時,事務B修改該數據,致使事務A每次讀取到的數據結果不一致,REPEATABLE-READ隔離級別下可避免。
4.幻讀——事務A屢次讀取同一數據時,事務B對事務A的影響區間內進行增刪操做,致使事務A讀取到的數據一會多、一會少,就像產生幻覺了同樣。SERIALIZABLE事務隔離級別可避免。(但實際上MySQL的InnoDB在REPEATABLE-READ隔離級別下避免了幻讀的發生)
1.數據行裏的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID字段,DB_TRX_ID標識最近一次對本行記錄作修改的事務ID,DB_ROLL_PTR回滾指針,當事務回滾時,去往undo日誌尋找上一版本的數據,DB_ROW_ID行號(MySQL自動建立的隱藏自增主鍵)。
2.undo日誌:存儲歷史版本的數據。當某行的某個字段進行修改時,首先用排他鎖鎖住該行,而後將該行數據拷貝一份放入undolog中,經過行中的DB_ROLL_PTR指針,指向undolog中的這條數據,而後修改當前行的值,並填寫DB_TRX_ID字段爲當前事務的ID。
使人頭禿。
本文圖片來自網絡,侵刪。
歡迎你們訪問個人我的博客:Object's Blog