目錄
數據庫索引的存儲結構一般是B+樹,爲什麼不適用紅黑樹等普通的二叉樹
知識整理
- B樹
- 特點:平衡、層少、升序
- 結構:父節點、子節點指針數組、key信息數組(查找值或折半)
- B+樹
- 結構區別:非葉節點保存下一節點的索引、葉子節點保存key數組、有序鏈表
- 性能區別:IO少、平衡、範圍查找
- B+與普通二叉樹比較 IO少:層數少、層間局部性原理、順序讀寫
- 索引
- 優點:查找排序快、主鍵索引保證唯一
- 缺點:存儲空間、維護時間
- 使用場景:查詢多、更新少、空值少的數據;
- 隔離級別 4種隔離級別、3種錯誤【髒讀、不可重複讀、幻讀】
- 讀未提交
- 讀已提交 read commited:事務被提交後才能讀
- 可重複讀 Repeatable read:同個事務的兩次查詢,結果一致;行鎖 innodb的版本實現
- 串行化 Serializable:範圍讀取後不能insert;讀寫互斥;頁鎖
- InnoDB
- 組成
- 事務、檢查點
- 自適應索引
- 插入緩衝
數據庫索引的存儲結構一般是B+樹,爲什麼不適用紅黑樹等普通的二叉樹
- 局部性原理:程序運行期間所需要的數據通常比較集中;當一個數據被用到時,其附近的數據也通常會馬上被使用;
- 磁盤會順序預讀 頁的整數倍 的數據到內存中,減少磁盤的IO次數
- 磁盤順序讀取的效率很高
- B樹的節點大小設置爲一頁(4k~16k),減少層間索引次數,效率較高;
- 一次檢索磁盤操作最多需要h-1次I/O,漸進複雜度爲O(h)=O(logdN)
- 實際應用中,出度d是非常大的數字,通常超過100,因此h非常小
- 紅黑樹等高度較高,索引次數較多,即磁盤IO次數較多,性能較慢
1. 數據庫文件是放在硬盤上,磁盤尋道開銷是非常大,應該儘量減少磁盤I/O次數。
2. B+樹所有的關鍵字都出現在葉子節點,內存中可以存入更多的關鍵字,減少磁盤I/O次數
3.葉子節點通過鏈表相連,鏈表中的關鍵字是有序的,適合範圍查找。非葉子節點只起索引作用
層間索引是磁盤
層內是順序讀
B樹是一種平衡的多路查找樹
- 索引高度h:從根塊到達葉子塊時所遍歷的數據塊的個數,大多數的高度都是2到3,層數越高,性能越差;
- 關鍵字集合分佈在整棵樹中、每個節點存儲着關鍵字信息
- 平衡:所有葉子節點位於同一層上,到達任意葉子節點的搜索代價相同
- 分支節點和葉子節點默認升序排序
typedef struct BTNode
{
int keynum; //節點信息的數量,不包含key[0]節點
struct BTNode *parent; //父節點
int key[M+1]; //節點信息數組,第一個節點沒用。
struct BTNode *ptr[M+1];//子節點信息
}BTNode;
B樹的查找
(1)從樹的根節點T開始,遍歷key數組,或折半查找給定的值,如果找到,則返回節點指針和在節點中的位置;如果沒有,則到(2)
(2)與節點中的Key進行比較,找到給定值左右Key中間的指針,去其子樹中查找
(3)重複執行1,2兩步,直到找到。如果直到葉子節點,仍未找到,則返回0,並返回最後搜索的葉子節點。(此節點是給定值需要插入的位置)
關於分支節點(包括根節點塊)
1、 索引有序(缺省是升序排列,也可以在創建索引時指定爲降序排列)。
2、 索引兩個字段:所鏈接的索引塊中包含的最小(大)鍵值;第二個字段:索引塊的地址,指向下一個索引塊
(0 B1)、(500 B2)、(1000 B3)指向三個分支節點塊。其中的0、500和1000分別表示鏈接的鍵值的最小值。B1、B2和B3:索引塊的地址。
關於葉子節點
- 平衡:B樹索引的所有葉子塊一定位於同一層上。根塊到達任何一個葉子塊的遍歷代價相同,保證了B樹索引的平衡,提高索引性能。防止數據插入、刪除操作造成B樹索引變得不平衡,影響索引性能。
- 有序:葉子節點所包含的索引條目與分支節點一樣,有序(缺省是升序排列,也可以在創建索引時指定爲降序排列)
2字段:每個索引條目(也可以 叫做每條記錄)也具有兩個字段。第一個字段表示索引的鍵值,對於單列索引來說是一個值;而對於多列索引來說則是多個值組合在一起的。第二個字段:鍵值對應的記錄行的ROWID,該ROWID是記錄行在表裏的物理地址。
除根結點之外的結點(包括葉子結點)的關鍵字的個數n必須滿足: (ceil(m / 2)-1)<= n <=m-1。m表示最多含有m個孩子,n表示關鍵字數
在B樹中插入關鍵碼key的思路:
對高度爲h的m階B樹,新結點一般是插在第h層。通過檢索可以確定關鍵碼應插入的結點位置。然後分兩種情況討論:
1、 若該結點中關鍵碼個數小於m-1,則直接插入即可。
2、 若該結點中關鍵碼個數等於m-1,則將引起結點的分裂。以中間關鍵碼爲界將結點一分爲二,產生一個新結點,並把中間關鍵碼插入到父結點(h-1層)中
重複上述工作,最壞情況一直分裂到根結點,建立一個新的根結點,整個B樹增加一層。
刪除(delete)操作
- 判斷是否存在:首先查找B樹中需刪除的元素,如果該元素在B樹中存在,則將該元素在其結點中進行刪除,
- 判斷是否有左右孩子:如果有,則上移孩子結點中最相近元素(“左孩子最右邊的節點”或“右孩子最左邊的節點”)到父節點中;如果沒有,直接刪除
刪除元素,移動相應元素 豐滿:結點中元素個數>=ceil (m/2)-1, 關鍵字數小了<(ceil(m/2)-1)就合併,大了>(m-1)就分裂
- 自身豐滿,ok
- 若自身不豐滿:元素數目(即關鍵字數)小於ceil(m/2)-1,則需要看其某相鄰兄弟結點是否豐滿
- 如果相鄰兄弟結點豐滿,則向父節點借一個元素來滿足條件,兄弟結點上移到父結點;
- 如果相鄰兄弟剛脫貧(==ceil (m/2)-1),借了之後結點數目小於ceil(m/2)-1,則該結+相鄰兄弟結點+父結點=“合併”成一個結點,根節點時,樹減少一層
減少一層,變爲:
B+樹 n叉排序樹
聚集索引,葉結點data域保存了完整的數據記錄,數據文件本身要按主鍵聚集
輔助索引data域存儲相應記錄主鍵的值,而不是地址
非葉子節點
- 不保存關鍵字指針,只進行節點索引,內存中能保存更多關鍵字指針;
- 所有數據地址必須要到葉子節點才能獲取到,所以每次數據查詢的次數都一樣;
葉子節點 特有
- 所有關鍵字都會在葉子結點出現;(與非葉會重複,閉區間)
- 有序
- 所有葉子結點指針有序的連接在一起;,可以按照關鍵字遍歷,範圍查找效率高
子樹的個數最多可以與關鍵字一樣多
B+缺點:B+樹最大的性能問題是會產生大量的隨機IO,隨着新數據的插入,葉子節點會慢慢分裂,邏輯上連續的葉子節點在物理上往往不連續,甚至分離的很遠,但做範圍查詢時,會產生大量讀隨機IO。解決B+樹這一問題可採用LSM樹,即Log-Structured Merge-Trees。其中Hbase中就利用LSM,感興趣可以多查查LSM樹相關的知識,這裏就不再贅述。
性能上優點 存入更多key,減少IO;效率穩定;範圍查找效率高
- B+樹的磁盤讀寫代價更低:B+樹的內部結點不包含關鍵字,其內部結點比B樹小,內存能容納的結點中關鍵字數量更多
- B+樹的查詢效率更加穩定。B樹搜索有可能會在非葉子結點結束,越靠近根節點的記錄查找時間越短,只要找到關鍵字即可確定記錄的存在,其性能等價於在關鍵字全集內做一次二分查找。而在B+樹中,順序檢索比較明顯,隨機檢索時,任何關鍵字的查找都必須走一條從根節點到葉節點的路,所有關鍵字的查找路徑長度相同,每一個關鍵字的查詢效率相當。
- B+樹的葉子節點使用指針鏈接在一起,只要遍歷葉子節點就可以實現整棵樹的遍歷,基於範圍的查詢效率高
B樹優點:
由於B樹的每一個節點都包含key和value,因此經常訪問的元素可能離根節點更近,因此訪問也更迅速
索引
現在看下如何定位一個Record:
1 通過根節點開始遍歷一個索引的B+樹,通過各層非葉子節點最終到達一個數據頁Page,這個Page裏存放的都是葉子節點。
2 在Page內從"Infimum"節點開始遍歷單鏈表(這種遍歷往往會被優化),如果找到該鍵則成功返回。如果記錄到達了"supremum",說明當前Page裏沒有合適的鍵,這時要藉助Page的Next Page指針,跳轉到下一個Page繼續從"Infimum"開始逐個查找。
對數據庫表中列值進行排序的結構,作用:協助快速查詢
索引在創建或者刪除時,MySQL會先創建一個新的臨時表,然後把數據導入臨時表,刪除原表,再把臨時表更名爲原表名稱
索引缺點:存儲空間、維護時間
- 增加了數據庫的存儲空間
- 創建索引和維護索引要耗費時間
- 在插入和修改數據時要花費較多的時間(因爲索引也要隨之變動)
優點:
- 唯一性索引,可以保證行數據的唯一性
- 加快數據檢索速度
- 減少查詢中分組和排序的時間
場景 優化 不適用
- 很少查詢的列不用創建索引
- 很少取值的無需單獨創建,text, image、性別等
- 性別只有男女,索引查詢只能得到表50%的數據,沒有意義
- 修改性能大於檢索性能時,不應該創建索引;修改性能和檢索性能是互相矛盾
數據庫創建三種索引:唯一索引、主鍵索引和聚集索引
- 唯一索引 可以保證行數據的唯一性,不允許其中任何兩行具有相同索引值的索引,允許null;UNIQUE
- 主鍵索引 要求主鍵值唯一,爲表定義主鍵將自動創建主鍵索引,不允許null PRIMARY
- 普通索引:無限制;如果是CHAR,VARCHAR類型,length可以小於字段實際長度;如果是BLOB和TEXT類型,必須指定 length。
- 聚集索引 提供更快的數據訪問速度,表中行的物理順序與鍵值索引順序相同。一個表只能包含一個聚集索引,
普通索引的唯一任務是加快對數據的訪問速度,因此,應該只爲那些最經常出現在查詢條件或者排序條件中的數據列創建索引
唯一索引用關鍵字UNIQUE把它定義爲一個,Mysql會在有新紀錄插入數據表時,自動檢查新紀錄的這個字段的值是否已經在某個記錄的這個字段裏出現過了。如果是,mysql將拒絕插入那條新紀錄。唯一索引可以保證數據記錄的唯一性
對比:
1.值空值:主鍵不允許空值,唯一索引允許空值
2.字段數量:主鍵字段只允許一個,唯一索引的字段允許多個
兩者都要求數據值唯一
主鍵產生唯一的聚集索引,唯一索引產生唯一的輔助索引
注:聚集索引確定表中數據的物理順序,所以是主鍵是唯一的(聚集就是整理數據的意思)
InnoDB的主鍵選擇與插入優化
與業務無關的自增字段作爲主鍵,每次插入新的記錄,記錄就會順序添加到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁,形成一個緊湊的索引結構,近似順序填滿。由於每次插入時也不需要移動已有數據
聚集索引、輔助索引、非聚集索引
聚集(clustered)索引,也叫聚簇索引
- 定義:聚集索引是按每張表的主鍵構造的一顆B+樹,並且葉節點中存放着整張表的行記錄數據
- 索引的葉子節點就是對應的數據節點,可以直接獲取到對應的全部字段的數據
- 數據行的物理順序與主鍵的邏輯順序相同,一個表中只能擁有一個聚集索引
- 由於定義了數據的邏輯順序,聚集索引能夠特別快地訪問針對範圍值的查詢
- 非葉結點只存放僅僅是鍵值及數據頁的偏移量
優點:聚族索引將索引和數據保存在同一個B+樹中,索引的葉子節點就是對應的數據節點,可以直接獲取到對應的全部列的數據,而非聚集索引在索引沒有覆蓋到對應的列的時候需要進行二次查詢,後面會詳細講。因此在查詢方面,聚集索引的速度往往會更佔優勢
輔助索引
輔助索引data域存儲相應記錄主鍵的值而不是地址,再通過主鍵找到整行數據
輔助索引頁級別不包含行的全部數據,葉節點除了包含鍵值以外,每個葉級別中的索引行中還包含了一個書籤bookmark,該書籤用來告訴InnoDB哪裏可以找到與索引相對應的行數據
需要通過主鍵進行二次查詢,因此在查詢方面,聚集索引的速度往往會更佔優勢
非聚集索引:數據存儲在一個地方,索引存儲在另一個地方,索引帶有指針指向數據的存儲位置。
例子 查字典:聚集索引:按拼音直接在正文查找;非聚集索引:按偏旁在索引目錄查,再去翻正文
非聚簇索引
就是指B+Tree的葉子節點上的data並不是數據本身,而是數據存放的地址,主要用在MyISAM存儲引擎。 主索引和輔助索引沒啥區別,只是主索引中的key一定是唯一的。
MyISAM使用的是非聚簇索引,非聚簇索引的兩棵B+樹看上去沒什麼不同,節點的結構完全一致只是存儲的內容不同而已,主鍵索引B+樹的節點存儲了主鍵,輔助鍵索引B+樹存儲了輔助鍵。表數據存儲在獨立的地方,這兩顆B+樹的葉子節點都使用一個地址指向真正的表數據,對於表數據來說,這兩個鍵沒有任何差別。由於索引樹是獨立的,通過輔助鍵檢索無需訪問主鍵的索引樹。
我們重點關注聚簇索引,看上去聚簇索引的效率明顯要低於非聚簇索引,因爲每次使用輔助索引檢索都要經過兩次B+樹查找,這不是多此一舉嗎?聚簇索引的優勢在哪?
1 由於行數據和葉子節點存儲在一起,這樣主鍵和行數據是一起被載入內存的,找到葉子節點就可以立刻將行數據返回了,如果按照主鍵Id來組織數據,獲得數據更快
2 輔助索引使用主鍵作爲"指針" 而不是使用地址值作爲指針的好處是,減少了當出現行移動或者數據頁分裂時輔助索引的維護工作,使用主鍵值當作指針會讓輔助索引讀入更多數據到內存,換來的好處是InnoDB在移動行時無須更新輔助索引中的這個"指針"。也就是說行的位置(實現中通過16K的Page來定位,後面會涉及)會隨着數據庫裏數據的修改而發生變化(前面的B+樹節點分裂以及Page的分裂),使用聚簇索引就可以保證不管這個主鍵B+樹的節點如何變化,輔助索引樹都不受影響
索引爲什麼那麼快
1.二分查找、順序讀,效率高
索引就是通過事先排好序,從而在查找時可以應用二分查找等高效率的算法
一般的順序查找,複雜度爲O(n),而二分查找複雜度爲O(log2n)。當n很大時,二者的效率相差及其懸殊。
1.1 先讀緩存 不管數據表有無索引,首先在數據緩存池Buffer Pool中查找所需要的數據,
如果緩衝區中沒有需要的數據時,服務器進程纔去讀磁盤【層間I/O】
- 無索引,直接去讀表數據存放的磁盤塊,讀到數據緩衝區中,再查找需要的數據
- 有索引,先讀入索引表,通過索引表直接找到所需數據的物理地址page,並把數據讀入數據緩衝區中。再查找
2.B+樹結構
聯合索引=複合索引=組合索引
概念: 單一索引是指索引列爲一列的情況,即新建索引的語句只實施在一列上;
用戶可以在多個列上建立索引,這種索引叫做複合索引(組合索引);
聯合索引的鍵值數量大於等於2,且葉節點按左位優先排序【類似於二次排序、n次排序】
因此,如果不從最左位開始查,只查詢第二個,結果是無序的,索引不起作用
第二個鍵值進行了排序處理,如用戶、購買時間
聯合索引在數據庫操作期間所需的開銷更小,可以代替多個單一索引;
同時有兩個概念叫做窄索引和寬索引,窄索引是指索引列爲1-2列的索引,寬索引也就是索引列超過2列的索引;
設計索引的一個重要原則就是能用窄索引不用寬索引,因爲窄索引往往比組合索引更有效;
使用:創建索引
create index idx1 on table1(col1,col2,col3)
select * from table1 where col1= A and col2= B and col3 = C
這時候查詢優化器,不在掃描表了,而是直接的從索引中拿數據,因爲索引中有這些數據,這叫覆蓋式查詢,這樣的查詢速度非常快;
注意事項:
1、對於複合索引,在查詢使用時,最好將條件順序按照索引的順序,這樣效率最高;
select * from table1 where col1=A AND col2=B AND col3=D
如果使用 where col2=B AND col1=A 或者 where col2=B 將不會使用索引
2、何時複合索引 根據where條件建索引是極其重要的一個原則;
注意不要過多用索引,否則對錶更新的效率有很大的影響,因爲在操作表的時候要化大量時間花在創建索引中
- 索引應該建在選擇性高的字段上(鍵值唯一的記錄數/總記錄條數),選擇性越高索引的效果越好、價值越大,唯一索引的選擇性最高;
- 組合索引中字段的順序,選擇性越高的字段排在最前面;
- where條件中包含兩個選擇性高的字段時,可以考慮分別創建索引,引擎會同時使用兩個索引(在OR條件下,應該說必須分開建索引);
- 用窄索引
3、複合索引會替代單一索引麼
不能。複合索引的使用原則是第一個條件應該是複合索引的第一列;正常情況下複合索引不能替代多個單一索引
如果索引滿足窄索引的情況下可以建立複合索引,這樣可以節約空間和時間
備註: 對一張表來說,如果有一個複合索引 on(col1,col2),就沒有必要同時建立一個單索引 on col1;
如果查詢條件需要,可以在已有單索引 on col1的情況下,添加複合索引on (col1,col2),對於效率有一定的提高
4.複合索引何時起作用:從第一個索引位置開始【左位優先匹配】,查詢任意個複合字段;否則不起作用
如果不從最左位開始查,只查詢第二個,結果是無序的,索引不起作用
覆蓋索引
從輔助索引中即可查詢到的記錄,則不去聚集索引中進行二次查詢,如count
利用輔助索引只存儲主鍵,讀入內存的數據更多,且不需要二次查詢
InnoDB中的hash索引
目的:從內存【innodb_buffer_pool】中O(1)速度,得到某個被緩存的頁;
除法散列:h(k) = k mod m,m爲大於2倍緩衝池頁數的最小質數,k爲=[表空間id]space_id<<20+space_id+offset[頁偏移量]
innodb會對錶上的索引頁的查詢進行監控,如果發現建立hash索引能夠帶來性能提升,就自動創建hash索引。
hash索引的創建是有條件的,首先是必定能夠帶來性能提升。其次數據庫以特定模式的連續訪問超過了100次,
自適應hash根據B+樹中的索引構造而來,只需爲這個表的熱點頁構造hash索引而不是爲整張表都構建。
全文檢索、倒排索引
在輔助表中存儲單詞與單詞所在文檔位置之間的映射,利用關聯數組實現
兩種存儲格式
inverted file index:{單詞,單詞所在文檔ID}
full inverted index:{單詞,{單詞所在文檔ID:文檔中具體位置}} InnoDB默認
額外保存:第一次出現文檔的ID,最後一次文檔ID、在多少個文檔中存在
刪除操作會將文檔ID插入DELETED表中,徹底刪除使用optimize table命令
具體查詢 match{clo1,col2} aganist (查詢模式[自然語言NL{單詞存在哪個文檔}、Boolean{包含限定條件,包含啥,不包含啥}])
數據庫各種隔離級別、事務的4種隔離性、會發生的3種問題:首先 set session autocommit=0,關閉sql自動提交
低到高分別爲Read uncommitted 、Read committed 、Repeatable read 、Serializable
1.Read uncommitted 讀未提交 髒讀--在賬戶中看到了未提交的數據
(在賬戶中看到了未提交的數據)
2.Read committed 讀已提交 針對 髒讀--在賬戶中看到了未提交的數據 oracle默認
只有在事務提交後,其更新結果纔會被其他事務看見。解決髒讀問題
問題:讀取後UPDATE delete,數據變了,重複讀時不一致,存在不可重複讀;因爲其他事務進行了修改
(A付費時,檢測到賬戶錢有3w,此時B把錢轉走了,A發現賬戶沒錢了,即A的轉賬事務內,再查詢(重複讀)時數據不一樣了)
RC
輸出的是最新提交的結果,RC級別的快照讀遵循以下規則:
- 優先讀取當前事務修改的數據,自己修改的,當然可以讀到了;
- 其次讀取最新已提交數據
會出現前後讀取結果不一樣的情況,但讀取的是最新數據。
3.Repeatable read 可重複讀 針對 單條數據讀取後UPDATE delete,數據變了,重複讀時不一致,對數據加行鎖 mysql默認
解決方法:讀取數據加行鎖,其它事務無法修改這些數據,在一個事務中,保證對於同一份數據( 相同字段)的讀取結果總是相同的。
可以解決髒讀、不可重複讀(A付費時,鎖定賬戶,不允許B用戶拿該賬戶轉賬,即事務開啓時,不允許其他事務的UPDATE修改操作)
問題:範圍查詢時,重複查又多了一條;
4.Serializable (串行化) 針對 讀取後insert ,發現多了一條;解決,行鎖+間隙鎖=next-key lock
幻讀和不可重複讀都是讀取了另一條已經提交的事務,不可重複讀重點在於update和delete,而幻讀的重點在於insert
所以當事務A先前讀取了數據,或修改了全部數據,事務B還是可以insert數據提交,這時事務A就會發現莫名其妙多了一條之前沒有的數據
需要Serializable隔離級別:事務串行化順序執行,讀用讀鎖,寫用寫鎖,讀鎖和寫鎖互斥,這麼做可以有效的避免幻讀、不可重複讀、髒讀等問題,但會極大的降低數據庫的併發能力
innodb解決幻讀:
實現了一致性不鎖定讀(Consistent Nonlocking Reads),從而避免了幻讀
在默認隔離級別REPEATABLE READ下,同一事務的所有一致性讀只會讀取第一次查詢時創建的快照
一致性讀是通過 MVCC 爲查詢提供了一個基於時間的點的快照。這個查詢只能看到在自己之前提交的數據,而在查詢開始之後提交的數據是不可以看到的
- 把該行修改前的值Copy到undo log(Copy on write);
- 修改當前行的值,填寫事務編號,使回滾指針指向undo log中的修改前的行。
在READ COMMITTED事務隔離級別下,對於快照數據,非一致性讀總是讀取被鎖定行的最新一份快照數據
而在REPEATABLE READ事務隔離級別下,對於快照數據,非一致性讀總是讀取事務開始時的數據版本
SQL語句:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
LOCK TABLES tablename WRITE;
LOCK TABLES tablename READ;
鎖的粒度主要有以下幾種類型: 都是悲觀鎖
l 行鎖: 索引項加鎖,粒度最小,併發性最高 for updata
l 頁鎖:一次鎖定一頁。25個行鎖可升級爲一個頁鎖。
l 表鎖:粒度大,併發性低
l 數據庫鎖:控制整個數據庫操作
間隙鎖
樂觀鎖:人工控制: 或CAS
- 認爲數據一般情況下不會造成衝突,在數據進行提交更新時,才衝突檢測;
- 如果衝突,則返回錯誤的信息給用戶,讓用戶決定如何去做;
- 一般的實現樂觀鎖的方式就是記錄數據版本、時間戳
如果數據庫表當前版本號與更新前取出來的version值相等,則予以更新,否則認爲是過期數據。用下面的一張圖來說明:
2.樂觀鎖定的第二種實現方式和第一種差不多,在table中增加一個字段,字段類型使用時間戳(timestamp)
在更新提交的時候檢查當前數據庫中數據的時間戳,和自己更新前取到的時間戳進行對比,如果一致則OK,否則就是版本衝突。
數據庫設置隔離級別
悲觀鎖:
- 每次去拿數據都認爲可能存在併發修改,都會上鎖,其他線程想拿數據就會阻塞,直到它拿到鎖。
- 關係型數據庫,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖
要使用悲觀鎖,我們必須關閉mysql數據庫的自動提交屬性,因爲MySQL默認使用autocommit模式,也就是說,當你執行一個更新操作後,MySQL會立刻將結果進行提交。
begin;
//1.查詢出商品信息
select status from t_goods where id=1 for update; //id=1的數據被行鎖
//2.根據商品信息生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status爲2
update t_goods set status=2;
//4.提交事務
commit;/commit work;
只有「明確」地指定主鍵,MySQL 纔會執行Row lock (只鎖住被選取的數據) ;如果是 name=..,執行表鎖
否則MySQL 將會執行Table Lock (將整個數據表單給鎖住)。
明確指定索引,並且有此數據,row lock;給name創建索引,再依據name=..查,行鎖
Mysql 分頁查詢 分頁語句
select * from table limit (start-1)*limit,limit
取前5條數據
select * from table_name limit 0,5
或者
select * from table_name limit 5
查詢第11到第15條數據
select * from table_name limit 10,5
大數據量下的優化
1.在查詢下一頁時把上一頁的行id,作爲參數傳遞給客戶端程序;
select * from table where id>3000000 limit 10;
2.SELECT * FROM table WHERE id BETWEEN 1000000 AND 1000010;【雙閉合】
Mysql有哪些存儲引擎?有什麼區別?
MyISAM:
- 不支持事務,但是每次查詢都是原子的;
- 支持表級鎖,即每次操作是對整個表加鎖;
- 存儲表的總行數;
- 一個MYISAM表有三個文件:索引文件、表結構文件、數據文件;
- 採用非聚集索引,索引文件的數據域存儲指向數據文件的指針
- MYISAM索引和數據是分開的,而且其索引是壓縮的,可以更好地利用內存。所以它的查詢性能明顯優於INNODB
InnoDb:
- 支持ACID的事務,支持事務的四種隔離級別;
- 支持行級鎖及外鍵約束:因此可以支持併發寫;
- 不存儲總行數;
- 一個InnoDb引擎存儲在一個文件空間(共享表空間,表大小不受操作系統控制,一個表可能分佈在多個文件裏),也有可能爲多個(設置爲獨立表空,表大小受操作系統文件大小限制,一般爲2G),受操作系統文件大小的限制;
- 主鍵索引採用聚集索引(索引的數據域存儲數據文件本身),
- 輔索引的數據域存儲主鍵的值;因此從輔索引查找數據,需要先通過輔索引找到主鍵值,再訪問輔索引;最好使用自增主鍵,防止插入數據時,爲維持B+樹結構,文件的大調整
innodb
INNODB會支持一些關係數據庫的高級功能,如事務功能和行級鎖,MYISAM不支持。MYISAM的性能更優,佔用的存儲空間少
- 你的應用程序一定要使用事務,毫無疑問你要選擇INNODB引擎
- 如果你的應用程序對查詢性能要求較高,就要使用MYISAM了。MYISAM索引和數據是分開的,而且其索引是壓縮的,可以更好地利用內存。所以它的查詢性能明顯優於INNODB
- InnoDB也使用B+Tree作爲索引結構,InnoDB的數據文件本身就是索引文件【MyISAM索引文件和數據文件是分離】
- 葉節點包含了完整的數據記錄,這種索引叫做聚集索引。這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。因爲InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有);
- 如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作爲主鍵,如果不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段作爲主鍵,這個字段長度爲6個字節,類型爲長整形
- InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作爲data域。例如,下圖爲定義在Col3上的一個輔助索引
innodb架構
- innodb的整個體系架構就是由多個內存塊組成的緩衝池、多個後臺線程構成
- 緩衝池緩存磁盤數據(解決cpu速度和磁盤速度的嚴重不匹配問題),
- 後臺進程保證緩存池和磁盤數據的一致性(讀取、刷新),並保證數據異常宕機時能恢復到正常狀態
緩衝池主要分爲三個部分:redo log buffer、innodb_buffer_pool、innodb_additional_mem_pool
- innodb_buffer_pool由包含:數據、索引、插入緩衝 ,自適應hash索引,鎖信息及數據字典
- 數據的讀寫需要經過緩存(緩存在buffer pool 即在內存中)
- 數據以整頁(16K)位單位讀取到緩存中
- 緩存中的數據以LRU策略換出(最少使用策略)
- IO效率高,性能好
- redo log buffer 用來緩存重做日誌。
- additional memory pool:用來緩存LRU鏈表、等待、鎖等數據結構
- double write:存儲髒頁的拷貝,每次1M刷新到磁盤
後臺進程分爲:master thread,IO thread,purge thread,page cleaner thread。
- master thread負責刷新緩存數據到磁盤,並協調調度其它後臺進程。
- IO thread 分爲 insert buffer、log、read、write進程。分別用來處理插入緩衝insert buffer、重做日誌、讀寫請求的IO回調。
- purge thread用來回收undo 頁
- page cleaner thread用來刷新髒頁
master thread根據服務器的壓力分爲了每一秒及每十秒的操作。
每一秒的操作包括:刷新重做日誌、根據過去一秒的磁盤吞吐量來判斷是否需要merge insert buffer、根據髒頁在緩衝池中佔比是否超過最大髒頁佔比及是否開啓自適應刷新來刷新髒頁。
每十秒的操作包括:根據過去10秒的磁盤吞吐量來刷新髒頁,刷新重做日誌,回收undo 頁,再根據髒頁佔比是否超過70%刷新定量髒頁。
特點:
- 根據主鍵尋址速度很快
- 主鍵值遞增的insert插入效率較好
- 主鍵值隨機insert插入操作效率差
- 因此,innodb表必須指定主鍵,建議使用自增數字;
如果不使用主鍵,系統會自動加上一個6字符字符串的主鍵;
- 數據的讀寫需要經過緩存(緩存在buffer pool 即在內存中)
- 數據以整頁(16K)位單位讀取到緩存中
- 緩存中的數據以LRU策略換出(最少使用策略)
- IO效率高,性能好
事務機制
- Mysql會最大程度的使用緩存機制來提高數據庫的訪問效率
- 事務日誌包括:重做日誌redo和回滾日誌undo
- Redo記錄的是已經全部完成的事務,就是執行了commit的事務,記錄文件是ib_logfile0, ib_logfile1
- Undo記錄的是已部分完成並且寫入硬盤的,未完成的事務
引入checkpoint機制
隨着redo的量增加,每次從redo的第一條開始恢復就會浪費長的時間,所以引入了checkpoint機制
Dirty page:髒頁
一般業務運行過程中,當業務需要對某張的某行數據進行修改的時候,innodb會先將該數據從磁盤讀取到緩存中去,然後在緩存中對這條數據進行修改,這樣緩存中的數據就和磁盤的數據不一致了,這個時候緩存中的數據就稱爲dirty page,只有當髒頁統一刷新到磁盤中才會是clean page
Checkpoint:如果在某個時間點,髒頁的數據被刷新到了磁盤,系統就把這個刷新的時間點記錄到redo log的結尾位置,在進行恢復數據的時候,checkpoint時間點之前的數據就不需要進行恢復了,可以縮短時間
double write:
- double write的實現分兩個部分,緩衝池,2M的內存塊大小,共享表空間中連續的128個頁
- 髒頁從flush list刷新時,並不是直接刷新到磁盤而是先調用函數(memcpy),將髒頁拷貝到double write buffer中,
- 然後再分兩次,每次1M將double write buffer 刷新到磁盤double write 區,之後再調用fsync操作,同步到磁盤
因爲髒頁刷新到磁盤的寫入單元小於單個頁的大小,如果在寫入過程中數據庫突然宕機,可能會使數據頁的寫入不完成,造成數據頁的損壞。
redo log中記錄的是對頁的物理操作,如果數據頁損壞了,通過redo log也無法進行恢復。所以爲了保證數據頁的寫入安全,引入了double write
double write的實現分兩個部分,緩衝池,2M的內存塊大小,共享表空間中連續的128個頁
髒頁從flush list刷新時,並不是直接刷新到磁盤而是先調用函數(memcpy),將髒頁拷貝到double write buffer中,
然後再分兩次,每次1M將double write buffer 刷新到磁盤double write 區,之後再調用fsync操作,同步到磁盤。
如果應用在業務高峯期,innodb_dblwr_pages_written:innodb_dblwr_writes遠小於64:1,則說明,系統寫入壓力不大。
雖然,double write buffer刷新到磁盤的時候是順序寫,但還是是有性能損耗的。如果系統本身支持頁的安全性保障(部分寫失效防範機制),如ZFS,那麼就可以禁用掉該特性(skip_innodb_doublewrite)。
adaptive hash index:
innodb會對錶上的索引頁的查詢進行監控,如果發現建立hash索引能夠帶來性能提升,就自動創建hash索引。
hash索引的創建是有條件的,首先是必定能夠帶來性能提升。其次數據庫以特定模式的連續訪問超過了100次,
自適應hash根據B+樹中的索引構造而來,只需爲這個表的熱點頁構造hash索引而不是爲整張表都構建。
insert buffer:
專門爲維護非唯一輔助索引的更新設計的
聚集索引對應的輔助索引的更新則是離散的,爲了避免大量離散讀寫
先判斷插入的非聚集索引葉子是否在緩衝池中,若在,則直接插入;
若不在,則先將插入的記錄放到insert buffer中,然後根據一些算法將insert buffer 緩存的記錄通過後臺線程慢慢的合併(merge)回輔助索引頁中。
這樣做的好處是:(1)減少磁盤的離散讀取;(2)將多次插入合併爲一次操作。
checkpoint:
Dirty page:髒頁
一般業務運行過程中,當業務需要對某張的某行數據進行修改的時候,innodb會先將該數據從磁盤讀取到緩存中去,然後在緩存中對這條數據進行修改,這樣緩存中的數據就和磁盤的數據不一致了,這個時候緩存中的數據就稱爲dirty page,只有當髒頁統一刷新到磁盤中才會是clean page
Checkpoint:如果在某個時間點,髒頁的數據被刷新到了磁盤,系統就把這個刷新的時間點記錄到redo log的結尾位置,在進行恢復數據的時候,checkpoint時間點之前的數據就不需要進行恢復了,可以縮短時間
異步IO:
mysql 5.5之前並不支持異步IO,而是通過innodb代碼模擬實現。5.5之後開始提供AIO支持。數據庫可以連續發出IO請求,然後再等待IO請求的處理結果。異步IO帶來的好處就是可以進行IO合併操作,減少磁盤壓力。要想mysql支持異步IO還需要操作系統支持,首先操作系統必須支持異步IO,像windows,linux都是支持的,但是 mac osx卻不支持。同時在編譯和運行時還需要有libaio依賴包。可以通過設置innodb_use_native_aio來控制是否啓用這個特性,一般開啓這個特性可以使數據恢復帶來75%的性能提升。
多隔離級別:
innodb支持四種隔離級別RU\RC\RR\serializable。RU不使用MVCC,讀取的時候也不加鎖。RC利用MVCC都是讀取記錄最新的版本,RR利用MVCC總是讀取記錄最舊的版本,並通過next-key locking來避免幻讀,serializable不使用MVCC,讀取記錄的時候加共享鎖,堵塞了其它事務對該記錄的更新,實現可串行化。隔離級別越高,維護成本越高,併發越低。RC隔離級別下要求二進制日誌格式必須是row格式的,因爲RC隔離級別下,不會加gap鎖,不能禁止一個事務在執行的過程中另一個事務對它的間隙進行操作的情況。這種情況下,對於事務開始的和提交的順序是先更改後提交,後更改先提交的情況,statement格式的binlog只會是按照事務提交的順序進行記錄。這可能會導致複製環境的slave數據和master數據不一致。通過設置innodb_locks_unsafe_for_binlog=1也可以使用statement格式,但是主從數據的一致性沒法保證。
MVCC 多版本併發控制機制
機制可以控制併發操作,但是其系統開銷較大,而MVCC可以在大多數情況下代替行級鎖,使用MVCC,能降低其系統開銷.
MVCC是通過保存數據在某個時間點的快照來實現的
- InnoDB的MVCC,是通過在每行記錄後面保存兩個隱藏的列來實現,這兩個列,分別保存了這個行的創建時間、行的刪除時間
- 這裏存儲的並不是實際的時間值,而是系統版本號(可以理解爲事務的ID),每開始一個新的事務,系統版本號就會自動遞增
- 事務開始時刻的系統版本號會作爲事務的ID
下面看一下在可重複讀REPEATABLE READ隔離級別下,MVCC具體是如何操作的
事務ID爲1的插入
start transaction; insert into yang values(NULL,'yang') ; insert into yang values(NULL,'long'); insert into yang values(NULL,'fei'); commit;
事務ID爲2的查詢
start transaction; select * from yang; select * from yang; commit;
假設3:在執行這個事務ID爲2的過程中,這時,有另一個事務ID爲3往這個表裏插入了一條數據;
假設4:執行這個事務ID爲2的過程中,事務執行完事務ID3後,接着又執行了事務ID4; 刪除了id=1的數據
start transaction; delete from yang where id=1; commit;
繼續事務ID爲2 的查詢結果
根據SELECT 檢索條件
- 檢索創建時間(創建事務ID)小於當前事務ID【2】的行
- 和刪除時間(刪除事務ID)大於當前事務ID【2】的行
所以,新插入數據和已經刪除的數據不會對檢索結果產生影響
UPDATE
InnoDB執行UPDATE,實際上是新插入了一行記錄,並保存更新行 創建時間 爲當前事務ID,同時保存 被更新行 刪除時間 爲當前事務ID
假設5:在執行事務2時,其它用戶執行了事務3、4,這時,又有一個用戶對這張表執行了UPDATE操作:,事務ID爲5
start transaction; update yang set name='Long' where id=2; commit;
MYSQL優化
- 開啓查詢緩存,不要用一些對查詢緩存不起作用的函數,如CURDATE()【當前日期】、 NOW() 和 RAND() ;直接用date("Y-m-d");
- EXPLAIN 你的 SELECT 查詢,EXPLAIN 的查詢結果還會告訴你你的索引主鍵被如何利用的,你的數據表是如何被搜索和排序的
- 當只要一行數據時使用 LIMIT 1,MySQL數據庫引擎會在找到一條數據後停止搜索,而不是繼續往後查少下一條符合記錄的數據
- 爲搜索字段建索引
- 在Join表的時候使用相當類型的字段,併爲Join的字段設索引
- 避免 SELECT *
- 固定長度的表會更快 ,固定的長度是很容易計算下一個數據的偏移量的,所以讀取的自然也會很快。而如果字段不是定長的,那麼,每一次要找下一條的話,需要程序找到主鍵
如何做一條sql的優化
查詢慢日誌
explain+sql進行詳細檢查
從數據庫出來的數據要如何存儲到Java bean中
在bean裏面實現set get方法,將數據庫查詢出來的值set進去;首先需要實例化一個新對象
配置文件幫忙OR映射
hash索引和b+樹索引的區別
B+樹是一個平衡的多叉樹,從根節點到每個葉子節點的高度差值不超過1,而且同層級的節點間有指針相互鏈接。
在B+樹上的常規檢索,從根節點到葉子節點的搜索效率基本相當,不會出現大幅波動,而且基於索引的順序掃描時,也可以利用雙向指針快速左右移動,效率非常高。
因此,B+樹索引被廣泛應用於數據庫、文件系統等場景
簡單地說,哈希索引就是採用一定的哈希算法,把鍵值換算成新的哈希值,檢索時不需要類似B+樹那樣從根節點到葉子節點逐級查找,只需一次哈希算法即可立刻定位到相應的位置【O1】,速度非常快
從上面的圖來看,B+樹索引和哈希索引的明顯區別是:
- 如果是等值查詢,那麼哈希索引明顯有絕對優勢,因爲只需要經過一次算法即可找到相應的鍵值;當然了,這個前提是,鍵值都是唯一的
- 在有大量重複鍵值情況下,哈希索引的效率也是極低的,因爲存在所謂的哈希碰撞問題,如果鍵值不是唯一的,就需要先找到該鍵所在位置,然後再根據鏈表往後掃描,直到找到相應的數據;
- 範圍查詢檢索,哈希索引原先是有序的鍵值,經過哈希算法後,有可能變成不連續的了,就沒辦法再利用索引完成範圍查詢檢索;
- 哈希索引也沒辦法利用索引完成排序,以及like ‘xxx%’ 這樣的部分模糊查詢(這種部分模糊查詢,其實本質上也是範圍查詢);
- 哈希索引也不支持多列聯合索引的最左匹配規則;
- B+樹索引的關鍵字檢索效率比較平均
try{ con.setAutoCommit(false);//開啓事務 ...... con.commit();//try的最後提交事務 } catch() { con.rollback();//回滾事務 }
什麼是三大範式:
第一範式:當關系模式R的所有屬性都不能在分解爲更基本的數據單位時,稱R是滿足第一範式的,簡記爲1NF。滿足第一範式是關係模式規範化的最低要
求,否則,將有很多基本操作在這樣的關係模式中實現不了。
第二範式:如果關係模式R滿足第一範式,並且R得所有非主屬性都 完全依賴於 候選關鍵屬性,稱R滿足第二範式,簡記爲2NF。
第三範式:設R是一個滿足第一範式條件的關係模式,X是R的任意屬性集,如果任意屬性集 非傳遞依賴於 候選關鍵字,稱R滿足第三範式,簡記爲3NF.
Student表(學號,姓名,年齡,性別,所在院校,院校地址,院校電話)
這樣一個表結構,就存在上述關係。 學號--> 所在院校 --> (院校地址,院校電話)
這樣的表結構,我們應該拆開來,(學號,姓名,年齡,性別,所在院校)--(所在院校,院校地址,院校電話)