MySQL基础总结(二)
MySQL基礎(chǔ)總結(jié)(二)
文章目錄
- MySQL基礎(chǔ)總結(jié)(二)
- 四、索引
- 7.MyISAM主鍵索引與輔助索引的結(jié)構(gòu)
- 8.InnoDB主鍵索引與輔助索引的結(jié)構(gòu)
- **`主鍵索引`**
- **`輔助(非主鍵)索引:`**
- InnoDB 索引結(jié)構(gòu)需要注意的點
- 9.那為什么推薦使用整型自增主鍵而不是選擇UUID?
- 10.為什么非主鍵索引結(jié)構(gòu)葉子節(jié)點存儲的是主鍵值?
- 11.Hash索引
- 12.full-text全文索引
- 13.R-Tree空間索引
- 14.為什么Mysql索引要用B+樹不是B樹?
- 15.面試官:為何不采用Hash方式?
- 16.哪些情況需要創(chuàng)建索引
- 17.哪些情況不要創(chuàng)建索引
- 18.MySQL高效索引
- 五、SQL
- 1.count(*) 和 count(1)和count(列名)區(qū)別
- 執(zhí)行效果上:
- 執(zhí)行效率上:
- 2.MySQL中 in和 exists 的區(qū)別?
- 3.UNION和UNION ALL的區(qū)別?
- 4.SQL執(zhí)行順序
- 六、事務
- 1.ACID — 事務基本要素
- 并發(fā)事務處理帶來的問題
- 幻讀和不可重復讀的區(qū)別:
- 并發(fā)事務處理帶來的問題的解決辦法:
- 2.事務隔離級別
- Read uncommitted
- Read committed
- Repeatable read
- Serializable 序列化
- 3.MVCC 多版本并發(fā)控制
- 4.事務日志
- 5.事務的實現(xiàn)
- redo log(重做日志) 實現(xiàn)持久化和原子性
- undo log(回滾日志) 實現(xiàn)一致性
- 6.你知道MySQL 有多少種日志嗎?
- 7.MySQL對分布式事務的支持
四、索引
7.MyISAM主鍵索引與輔助索引的結(jié)構(gòu)
MyISAM引擎的索引文件和數(shù)據(jù)文件是分離的。MyISAM引擎索引結(jié)構(gòu)的葉子節(jié)點的數(shù)據(jù)域,存放的并不是實際的數(shù)據(jù)記錄,而是數(shù)據(jù)記錄的地址。
索引文件與數(shù)據(jù)文件分離,這樣的索引稱為"非聚簇索引"。MyISAM的主索引與輔助索引區(qū)別并不大,只是主鍵索引不能有重復的關(guān)鍵字。
- 在MyISAM中,索引(含葉子節(jié)點)存放在單獨的.myi文件中,葉子節(jié)點存放的是數(shù)據(jù)的物理地址偏移量(通過偏移量訪問就是隨機訪問,速度很快)。
- 主索引是指主鍵索引,鍵值不可能重復;輔助索引則是普通索引,鍵值可能重復。
- 通過索引查找數(shù)據(jù)的流程:先從索引文件中查找到索引節(jié)點,從中拿到數(shù)據(jù)的文件指針,再到數(shù)據(jù)文件中通過文件指針定位了具體的數(shù)據(jù)。輔助索引類似。
8.InnoDB主鍵索引與輔助索引的結(jié)構(gòu)
InnoDB引擎索引結(jié)構(gòu)的葉子節(jié)點的數(shù)據(jù)域,存放的就是實際的數(shù)據(jù)記錄(對于主索引,此處會存放表中所有的數(shù)據(jù)記錄;對于輔助索引此處會引用主鍵,檢索的時候通過主鍵到主鍵索引中找到對應數(shù)據(jù)行),或者說,InnoDB的數(shù)據(jù)文件本身就是主鍵索引文件,這樣的索引被稱為“聚簇索引”,一個表只能有一個聚簇索引。
主鍵索引
我們知道InnoDB索引是聚集索引,它的索引和數(shù)據(jù)是存入同一個.idb文件中的,因此它的索引結(jié)構(gòu)是在同一個樹節(jié)點中同時存放索引和數(shù)據(jù),如下圖中最底層的葉子節(jié)點有三行數(shù)據(jù),對應于數(shù)據(jù)表中的id、stu_id、name數(shù)據(jù)項。
在Innodb中,索引分葉子節(jié)點和非葉子節(jié)點,非葉子節(jié)點就像新華字典的目錄,單獨存放在索引段中,葉子節(jié)點則是順序排列的,在數(shù)據(jù)段中。
Innodb的數(shù)據(jù)文件可以按照表來切分(只需要開啟innodb_file_per_table),切分后存放在xxx.ibd中,默認不切分,存放在xxx.ibdata中。
輔助(非主鍵)索引:
這次我們以示例中學生表中的name列建立輔助索引,它的索引結(jié)構(gòu)跟主鍵索引的結(jié)構(gòu)有很大差別,在最底層的葉子結(jié)點有兩行數(shù)據(jù),第一行的字符串是輔助索引,按照ASCII碼進行排序,第二行的整數(shù)是主鍵的值。
- 這就意味著,對name列進行條件搜索,需要兩個步驟:
① 在輔助索引上檢索name,到達其葉子節(jié)點獲取對應的主鍵;
② 使用主鍵在主索引上再進行對應的檢索操作
這也就是所謂的“回表查詢”
InnoDB 索引結(jié)構(gòu)需要注意的點
- 數(shù)據(jù)文件本身就是索引文件
- 表數(shù)據(jù)文件本身就是按 B+Tree 組織的一個索引結(jié)構(gòu)文件
- 聚集索引中葉節(jié)點包含了完整的數(shù)據(jù)記錄
- InnoDB 表必須要有主鍵(原因:聚集索引),并且推薦使用整型自增主鍵(原因:索引字段不應該太長)
- 正如我們上面介紹 InnoDB 存儲結(jié)構(gòu),索引與數(shù)據(jù)是共同存儲的,不管是主鍵索引還是輔助索引,在查找時都是通過先查找到索引節(jié)點才能拿到相對應的數(shù)據(jù),如果我們在設計表結(jié)構(gòu)時沒有顯式指定索引列的話,MySQL 會從表中選擇數(shù)據(jù)不重復的列建立索引,如果沒有符合的列,則 MySQL 自動為 InnoDB 表生成一個隱含字段作為主鍵,并且這個字段長度為6個字節(jié),類型為整型。
9.那為什么推薦使用整型自增主鍵而不是選擇UUID?
- UUID是字符串,比整型消耗更多的存儲空間;
- 在B+樹中進行查找時需要跟經(jīng)過的節(jié)點值比較大小,整型數(shù)據(jù)的比較運算比字符串更快速;
- 自增的整型索引在磁盤中會連續(xù)存儲,在讀取一頁數(shù)據(jù)時也是連續(xù);
- UUID是隨機產(chǎn)生的,讀取的上下兩行數(shù)據(jù)存儲是分散的,不適合執(zhí)行where id > 5 && id < 20的條件查詢語句。
- 在插入或刪除數(shù)據(jù)時,整型自增主鍵會在葉子結(jié)點的末尾建立新的葉子節(jié)點,不會破壞左側(cè)子樹的結(jié)構(gòu);
- UUID主鍵很容易出現(xiàn)這樣的情況,B+樹為了維持自身的特性,有可能會進行結(jié)構(gòu)的重構(gòu),消耗更多的時間。
10.為什么非主鍵索引結(jié)構(gòu)葉子節(jié)點存儲的是主鍵值?
保證數(shù)據(jù)一致性和節(jié)省存儲空間
- 可以這么理解:商城系統(tǒng)訂單表會存儲一個用戶ID作為關(guān)聯(lián)外鍵,而不推薦存儲完整的用戶信息,因為當我們用戶表中的信息(真實名稱、手機號、收貨地址···)修改后,不需要再次維護訂單表的用戶數(shù)據(jù),同時也節(jié)省了存儲空間。
11.Hash索引
主要就是通過Hash算法(常見的Hash算法有直接定址法、平方取中法、折疊法、除數(shù)取余法、隨機數(shù)法),將數(shù)據(jù)庫字段數(shù)據(jù)轉(zhuǎn)換成定長的Hash值,與這條數(shù)據(jù)的行指針一并存入Hash表的對應位置;如果發(fā)生Hash碰撞(兩個不同關(guān)鍵字的Hash值相同),則在對應Hash鍵下以鏈表形式存儲。
檢索算法:在檢索查詢時,就再次對待查關(guān)鍵字再次執(zhí)行相同的Hash算法,得到Hash值,到對應Hash表對應位置取出數(shù)據(jù)即可,如果發(fā)生Hash碰撞,則需要在取值時進行篩選。目前使用Hash索引的數(shù)據(jù)庫并不多,主要有Memory等。
MySQL目前有Memory引擎和NDB引擎支持Hash索引。
12.full-text全文索引
全文索引也是MyISAM的一種特殊索引類型,主要用于全文索引,InnoDB從MYSQL5.6版本提供對全文索引的支持。
它用于替代效率較低的LIKE模糊匹配操作,而且可以通過多字段組合的全文索引一次性全模糊匹配多個字段。
同樣使用B-Tree存放索引數(shù)據(jù),但使用的是特定的算法,將字段數(shù)據(jù)分割后再進行索引(一般每4個字節(jié)一次分割),索引文件存儲的是分割前的索引字符串集合,與分割后的索引信息,對應Btree結(jié)構(gòu)的節(jié)點存儲的是分割后的詞信息以及它在分割前的索引字符串集合中的位置。
13.R-Tree空間索引
空間索引是MyISAM的一種特殊索引類型,主要用于地理空間數(shù)據(jù)類型
14.為什么Mysql索引要用B+樹不是B樹?
用B+樹不用B樹考慮的是IO對性能的影響,B樹的每個節(jié)點都存儲數(shù)據(jù),而B+樹只有葉子節(jié)點才存儲數(shù)據(jù),所以查找相同數(shù)據(jù)量的情況下,B樹的高度更高,IO更頻繁。
數(shù)據(jù)庫索引是存儲在磁盤上的,當數(shù)據(jù)量大時,就不能把整個索引全部加載到內(nèi)存了,只能逐一加載每一個磁盤頁(對應索引樹的節(jié)點)。
其中在MySQL底層對B+樹進行進一步優(yōu)化:在葉子節(jié)點中是雙向鏈表,且在鏈表的頭結(jié)點和尾節(jié)點也是循環(huán)指向的。
15.面試官:為何不采用Hash方式?
因為Hash索引底層是哈希表,哈希表是一種以key-value存儲數(shù)據(jù)的結(jié)構(gòu),所以多個數(shù)據(jù)在存儲關(guān)系上是完全沒有任何順序關(guān)系的,所以,對于區(qū)間查詢是無法直接通過索引查詢的,就需要全表掃描。
所以, 哈希索引只適用于等值查詢的場景。而B+ Tree是一種多路平衡查詢樹,所以他的節(jié)點是天然有序的(左子節(jié)點小于父節(jié)點、父節(jié)點小于右子節(jié)點),所以對于范圍查詢的時候不需要做全表掃描
哈希索引不支持多列聯(lián)合索引的最左匹配規(guī)則,如果有大量重復鍵值得情況下,哈希索引的效率會很低,因為存在哈希碰撞問題。
16.哪些情況需要創(chuàng)建索引
- 主鍵自動建立唯一索引
- 頻繁作為查詢條件的字段
- 查詢中與其他表關(guān)聯(lián)的字段,外鍵關(guān)系建立索引
- 單鍵/組合索引的選擇問題,高并發(fā)下傾向創(chuàng)建組合索引
- 查詢中排序的字段,排序字段通過索引訪問大幅提高排序速度
- 查詢中統(tǒng)計或分組字段
17.哪些情況不要創(chuàng)建索引
- 表記錄太少
- 經(jīng)常增刪改的表
- 數(shù)據(jù)重復且分布均勻的表字段,只應該為最經(jīng)常查詢和最經(jīng)常排序的數(shù)據(jù)列建立索引(如果某個數(shù)據(jù)類包含太多的重復數(shù)據(jù),建立索引沒有太大意義)
- 頻繁更新的字段不適合創(chuàng)建索引(會加重IO負擔)
- where條件里用不到的字段不創(chuàng)建索引
18.MySQL高效索引
覆蓋索引(Covering Index),或者叫索引覆蓋, 也就是平時所說的不需要回表操作就是select的數(shù)據(jù)列只用從索引中就能夠取得,不必讀取數(shù)據(jù)行,MySQL可以利用索引返回select列表中的字段,而不必根據(jù)索引再次讀取數(shù)據(jù)文件,換句話說查詢列要被所建的索引覆蓋。
索引是高效找到行的一個方法,但是一般數(shù)據(jù)庫也能使用索引找到一個列的數(shù)據(jù),因此它不必讀取整個行。畢竟索引葉子節(jié)點存儲了它們索引的數(shù)據(jù),當能通過讀取索引就可以得到想要的數(shù)據(jù),那就不需要讀取行了。一個索引包含(覆蓋)滿足查詢結(jié)果的數(shù)據(jù)就叫做覆蓋索引。
判斷標準:使用explain,可以通過輸出的extra列來判斷,對于一個索引覆蓋查詢,顯示為using index,MySQL查詢優(yōu)化器在執(zhí)行查詢前會決定是否有索引覆蓋查詢
五、SQL
1.count(*) 和 count(1)和count(列名)區(qū)別
執(zhí)行效果上:
- count(*)包括了所有的列,相當于行數(shù),在統(tǒng)計結(jié)果的時候,不會忽略列值為NULL
- count(1)包括了所有列,用1代表代碼行,在統(tǒng)計結(jié)果的時候,不會忽略列值為NULL
- count(列名)只包括列名那一列,在統(tǒng)計結(jié)果的時候,會忽略列值為空(這里的空不是只空字符串或者0,而是表示null)的計數(shù),即某個字段值為NULL時,不統(tǒng)計。
執(zhí)行效率上:
- 列名為主鍵,count(列名)會比count(1)快
- 列名不為主鍵,count(1)會比count(列名)快
- 如果表多個列并且沒有主鍵,則 count(1) 的執(zhí)行效率優(yōu)于 count(*)
- 如果有主鍵,則 select count(主鍵)的執(zhí)行效率是最優(yōu)的
- 如果表只有一個字段,則 select count(*) 最優(yōu)。
2.MySQL中 in和 exists 的區(qū)別?
- exists:exists對外表用loop逐條查詢,每次查詢都會查看exists的條件語句,當exists里的條件語句能夠返回記錄行時(無論記錄行是的多少,只要能返回),條件就為真,返回當前l(fā)oop到的這條記錄;反之,如果exists里的條件語句不能返回記錄行,則當前l(fā)oop到的這條記錄被丟棄,exists的條件就像一個bool條件,當能返回結(jié)果集則為true,不能返回結(jié)果集則為false
- in:in查詢相當于多個or條件的疊加
- 如果查詢的兩個表大小相當,那么用in和exists差別不大。
- 如果兩個表中一個較小,一個是大表,則子查詢表大的用exists,子查詢表小的用in
3.UNION和UNION ALL的區(qū)別?
- UNION和UNION ALL都是將兩個結(jié)果集合并為一個,兩個要聯(lián)合的SQL語句 字段個數(shù)必須一樣,而且字段類型要“相容”(一致);
- UNION在進行表連接后會篩選掉重復的數(shù)據(jù)記錄(效率較低),而UNION ALL則不會去掉重復的數(shù)據(jù)記錄;
- UNION會按照字段的順序進行排序,而UNION ALL只是簡單的將兩個結(jié)果合并就返回;
4.SQL執(zhí)行順序
手寫:
SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOIN <right_table> ON <join_condition> WHERE <where_condition> GROUP BY <group_by_list> HAVING <having_condition> ORDER BY <order_by_condition> LIMIT <limit_number>機讀:
FROM <left_table> ON <join_condition> <join_type> JOIN <right_table> WHERE <where_condition> GROUP BY <group_by_list> HAVING <having_condition> SELECT DISTINCT <select_list> ORDER BY <order_by_condition> LIMIT <limit_number>總結(jié):
六、事務
MySQL 事務主要用于處理操作量大,復雜度高的數(shù)據(jù)。比如說,在人員管理系統(tǒng)中,你刪除一個人員,你即需要刪除人員的基本資料,也要刪除和該人員相關(guān)的信息,如信箱,文章等等,這樣,這些數(shù)據(jù)庫操作語句就構(gòu)成一個事務!
1.ACID — 事務基本要素
事務是由一組SQL語句組成的邏輯處理單元,具有4個屬性,通常簡稱為事務的ACID屬性。
- A (Atomicity) 原子性:整個事務中的所有操作,要么全部完成,要么全部不完成,不可能停滯在中間某個環(huán)節(jié)。事務在執(zhí)行過程中發(fā)生錯誤,會被回滾(Rollback)到事務開始前的狀態(tài),就像這個事務從來沒有執(zhí)行過一樣
- C (Consistency) 一致性:在事務開始之前和事務結(jié)束以后,數(shù)據(jù)庫的完整性約束沒有被破壞
- I (Isolation)隔離性:一個事務的執(zhí)行不能其它事務干擾。即一個事務內(nèi)部的操作及使用的數(shù)據(jù)對其它并發(fā)事務是隔離的,并發(fā)執(zhí)行的各個事務之間不能互相干擾
- D (Durability) 持久性:在事務完成以后,該事務所對數(shù)據(jù)庫所作的更改便持久的保存在數(shù)據(jù)庫之中,并不會被回滾
并發(fā)事務處理帶來的問題
- 更新丟失(Lost Update):事務A和事務B選擇同一行,然后基于最初選定的值更新該行時,由于兩個事務都不知道彼此的存在,就會發(fā)生丟失更新問題
- 臟讀(Dirty Reads):事務A讀取了事務B更新的數(shù)據(jù),然后B回滾操作,那么A讀取到的數(shù)據(jù)是臟數(shù)據(jù)
- 不可重復讀(Non-Repeatable Reads):事務 A 多次讀取同一數(shù)據(jù),事務B在事務A多次讀取的過程中,對數(shù)據(jù)作了更新并提交,導致事務A多次讀取同一數(shù)據(jù)時,結(jié)果不一致。
- 幻讀(Phantom Reads):幻讀與不可重復讀類似。它發(fā)生在一個事務A讀取了幾行數(shù)據(jù),接著另一個并發(fā)事務B插入了一些數(shù)據(jù)時。在隨后的查詢中,事務A就會發(fā)現(xiàn)多了一些原本不存在的記錄,就好像發(fā)生了幻覺一樣,所以稱為幻讀。
幻讀和不可重復讀的區(qū)別:
- 不可重復讀的重點是修改:在同一事務中,同樣的條件,第一次讀的數(shù)據(jù)和第二次讀的數(shù)據(jù)不一樣。(因為中間有其他事務提交了修改)
- 幻讀的重點在于新增或者刪除:在同一事務中,同樣的條件,,第一次和第二次讀出來的記錄數(shù)不一樣。(因為中間有其他事務提交了插入/刪除)
并發(fā)事務處理帶來的問題的解決辦法:
- “更新丟失”通常是應該完全避免的。但防止更新丟失,并不能單靠數(shù)據(jù)庫事務控制器來解決,需要應用程序?qū)σ碌臄?shù)據(jù)加必要的鎖來解決,因此,防止更新丟失應該是應用的責任。
- “臟讀” 、 “不可重復讀”和“幻讀” ,其實都是數(shù)據(jù)庫讀一致性問題,必須由數(shù)據(jù)庫提供一定的事務隔離機制來解決:
- 一種是加鎖:在讀取數(shù)據(jù)前,對其加鎖,阻止其他事務對數(shù)據(jù)進行修改。
- 另一種是數(shù)據(jù)多版本并發(fā)控制(MultiVersion Concurrency Control,簡稱 MVCC 或 MCC),也稱為多版本數(shù)據(jù)庫:不用加任何鎖, 通過一定機制生成一個數(shù)據(jù)請求時間點的一致性數(shù)據(jù)快照 (Snapshot), 并用這個快照來提供一定級別 (語句級或事務級) 的一致性讀取。從用戶的角度來看,好象是數(shù)據(jù)庫可以提供同一數(shù)據(jù)的多個版本。
2.事務隔離級別
數(shù)據(jù)庫事務的隔離級別有4種,由低到高分別為
-
READ-UNCOMMITTED(讀未提交): 最低的隔離級別,允許讀取尚未提交的數(shù)據(jù)變更,可能會導致臟讀、幻讀或不可重復讀。
-
READ-COMMITTED(讀已提交): 允許讀取并發(fā)事務已經(jīng)提交的數(shù)據(jù),可以阻止臟讀,但是幻讀或不可重復讀仍有可能發(fā)生。
-
REPEATABLE-READ(可重復讀): 對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發(fā)生。
-
SERIALIZABLE(可串行化): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執(zhí)行,這樣事務之間就完全不可能產(chǎn)生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。
-
查看當前數(shù)據(jù)庫的事務隔離級別:
下面通過事例一一闡述在事務的并發(fā)操作中可能會出現(xiàn)臟讀,不可重復讀,幻讀和事務隔離級別的聯(lián)系。
數(shù)據(jù)庫的事務隔離越嚴格,并發(fā)副作用越小,但付出的代價就越大,因為事務隔離實質(zhì)上就是使事務在一定程度上“串行化”進行,這顯然與“并發(fā)”是矛盾的。
同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重復讀”和“幻讀”并不敏感,可能更關(guān)心數(shù)據(jù)并發(fā)訪問的能力。
Read uncommitted
-
讀未提交,就是一個事務可以讀取另一個未提交事務的數(shù)據(jù)。
-
事例:老板要給程序員發(fā)工資,程序員的工資是3.6萬/月。但是發(fā)工資時老板不小心按錯了數(shù)字,按成3.9萬/月,該錢已經(jīng)打到程序員的戶口,但是事務還沒有提交,就在這時,程序員去查看自己這個月的工資,發(fā)現(xiàn)比往常多了3千元,以為漲工資了非常高興。但是老板及時發(fā)現(xiàn)了不對,馬上回滾差點就提交了的事務,將數(shù)字改成3.6萬再提交。
-
分析:實際程序員這個月的工資還是3.6萬,但是程序員看到的是3.9萬。他看到的是老板還沒提交事務時的數(shù)據(jù)。這就是臟讀。
-
那怎么解決臟讀呢?Read committed!讀提交,能解決臟讀問題。
Read committed
-
讀提交,顧名思義,就是一個事務要等另一個事務提交后才能讀取數(shù)據(jù)。
-
事例:程序員拿著信用卡去享受生活(卡里當然是只有3.6萬),當他埋單時(程序員事務開啟),收費系統(tǒng)事先檢測到他的卡里有3.6萬,就在這個時候!!程序員的妻子要把錢全部轉(zhuǎn)出充當家用,并提交。當收費系統(tǒng)準備扣款時,再檢測卡里的金額,發(fā)現(xiàn)已經(jīng)沒錢了(第二次檢測金額當然要等待妻子轉(zhuǎn)出金額事務提交完)。程序員就會很郁悶,明明卡里是有錢的…
-
分析:這就是讀提交,若有事務對數(shù)據(jù)進行更新(UPDATE)操作時,讀操作事務要等待這個更新操作事務提交后才能讀取數(shù)據(jù),可以解決臟讀問題。但在這個事例中,出現(xiàn)了一個事務范圍內(nèi)兩個相同的查詢卻返回了不同數(shù)據(jù),這就是不可重復讀。
-
那怎么解決可能的不可重復讀問題?Repeatable read !
Repeatable read
- 重復讀,就是在開始讀取數(shù)據(jù)(事務開啟)時,不再允許修改操作。MySQL的默認事務隔離級別
- 事例:程序員拿著信用卡去享受生活(卡里當然是只有3.6萬),當他埋單時(事務開啟,不允許其他事務的UPDATE修改操作),收費系統(tǒng)事先檢測到他的卡里有3.6萬。這個時候他的妻子不能轉(zhuǎn)出金額了。接下來收費系統(tǒng)就可以扣款了。
- 分析:重復讀可以解決不可重復讀問題。寫到這里,應該明白的一點就是,不可重復讀對應的是修改,即UPDATE操作。但是可能還會有幻讀問題。因為幻讀問題對應的是插入INSERT操作,而不是UPDATE操作。
- 什么時候會出現(xiàn)幻讀?
- 事例:程序員某一天去消費,花了2千元,然后他的妻子去查看他今天的消費記錄(全表掃描FTS,妻子事務開啟),看到確實是花了2千元,就在這個時候,程序員花了1萬買了一部電腦,即新增INSERT了一條消費記錄,并提交。當妻子打印程序員的消費記錄清單時(妻子事務提交),發(fā)現(xiàn)花了1.2萬元,似乎出現(xiàn)了幻覺,這就是幻讀。
- 那怎么解決幻讀問題?Serializable!
Serializable 序列化
- Serializable 是最高的事務隔離級別,在該級別下,事務串行化順序執(zhí)行,可以避免臟讀、不可重復讀與幻讀。簡單來說,Serializable會在讀取的每一行數(shù)據(jù)上都加鎖,所以可能導致大量的超時和鎖爭用問題。這種事務隔離級別效率低下,比較耗數(shù)據(jù)庫性能,一般不使用。
比較:
- 需要說明的是,事務隔離級別和數(shù)據(jù)訪問的并發(fā)性是對立的,事務隔離級別越高并發(fā)性就越差。所以要根據(jù)具體的應用來確定合適的事務隔離級別,這個地方?jīng)]有萬能的原則。
- MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;命令來查看,MySQL 8.0 該命令改為SELECT @@transaction_isolation;
- 這里需要注意的是:與 SQL 標準不同的地方在于InnoDB 存儲引擎在 REPEATABLE-READ(可重讀)事務隔離級別下使用的是Next-Key Lock 算法,因此可以避免幻讀的產(chǎn)生,這與其他數(shù)據(jù)庫系統(tǒng)(如 SQL Server)是不同的。
- 所以說InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)已經(jīng)可以完全保證事務的隔離性要求,即達到了 SQL標準的 SERIALIZABLE(可串行化)隔離級別,而且保留了比較好的并發(fā)性能。
- 因為隔離級別越低,事務請求的鎖越少,所以大部分數(shù)據(jù)庫系統(tǒng)的隔離級別都是READ-COMMITTED(讀已提交),但是你要知道的是InnoDB 存儲引擎默認使用 REPEATABLE-READ(可重讀)并不會有任何性能損失。
3.MVCC 多版本并發(fā)控制
MySQL的大多數(shù)事務型存儲引擎實現(xiàn)都不是簡單的行級鎖。基于提升并發(fā)性考慮,一般都同時實現(xiàn)了多版本并發(fā)控制(MVCC),包括Oracle、PostgreSQL。只是實現(xiàn)機制各不相同。
可以認為 MVCC 是行級鎖的一個變種,但它在很多情況下避免了加鎖操作,因此開銷更低。雖然實現(xiàn)機制有所不同,但大都實現(xiàn)了非阻塞的讀操作,寫操作也只是鎖定必要的行。
- MVCC 的實現(xiàn)是通過保存數(shù)據(jù)在某個時間點的快照來實現(xiàn)的。也就是說不管需要執(zhí)行多長時間,每個事物看到的數(shù)據(jù)都是一致的。
- 典型的MVCC實現(xiàn)方式,分為樂觀(optimistic)并發(fā)控制和悲觀(pressimistic)并發(fā)控制。
下邊通過 InnoDB的簡化版行為來說明 MVCC 是如何工作的。
- InnoDB 的 MVCC,是通過在每行記錄后面保存兩個隱藏的列來實現(xiàn)。這兩個列,一個保存了行的創(chuàng)建時間,一個保存行的過期時間(刪除時間)。當然存儲的并不是真實的時間,而是系統(tǒng)版本號(system version number)。
- 每開始一個新的事務,系統(tǒng)版本號都會自動遞增。事務開始時刻的系統(tǒng)版本號會作為事務的版本號,用來和查詢到的每行記錄的版本號進行比較。
REPEATABLE READ(可重讀)隔離級別下MVCC如何工作:
- SELECT
- InnoDB會根據(jù)以下兩個條件檢查每行記錄:
- 只有符合上述兩個條件的才會被查詢出來
- InnoDB只查找版本早于當前事務版本的數(shù)據(jù)行,這樣可以確保事務讀取的行,要么是在開始事務之前已經(jīng)存在要么是事務自身插入或者修改過的
- 行的刪除版本號要么未定義,要么大于當前事務版本號,這樣可以確保事務讀取到的行在事務開始之前未被刪除
- INSERT:InnoDB為新插入的每一行保存當前系統(tǒng)版本號作為行版本號
- DELETE:InnoDB為刪除的每一行保存當前系統(tǒng)版本號作為行刪除標識
- UPDATE:InnoDB為插入的一行新紀錄保存當前系統(tǒng)版本號作為行版本號,同時保存當前系統(tǒng)版本號到原來的行作為刪除標識
保存這兩個額外系統(tǒng)版本號,使大多數(shù)操作都不用加鎖。使數(shù)據(jù)操作簡單,性能很好,并且也能保證只會讀取到符合要求的行。不足之處是每行記錄都需要額外的存儲空間,需要做更多的行檢查工作和一些額外的維護工作。
MVCC 只在 COMMITTED READ(讀提交)和REPEATABLE READ(可重復讀)兩種隔離級別下工作。
4.事務日志
InnoDB 使用日志來減少提交事務時的開銷。因為日志中已經(jīng)記錄了事務,就無須在每個事務提交時把緩沖池的臟塊刷新(flush)到磁盤中。
事務修改的數(shù)據(jù)和索引通常會映射到表空間的隨機位置,所以刷新這些變更到磁盤需要很多隨機 IO。
InnoDB 假設使用常規(guī)磁盤,隨機IO比順序IO昂貴得多,因為一個IO請求需要時間把磁頭移到正確的位置,然后等待磁盤上讀出需要的部分,再轉(zhuǎn)到開始位置。
InnoDB 用日志把隨機IO變成順序IO。一旦日志安全寫到磁盤,事務就持久化了,即使斷電了,InnoDB可以重放日志并且恢復已經(jīng)提交的事務。
InnoDB 使用一個后臺線程智能地刷新這些變更到數(shù)據(jù)文件。這個線程可以批量組合寫入,使得數(shù)據(jù)寫入更順序,以提高效率。
事務日志可以幫助提高事務效率:
- 使用事務日志,存儲引擎在修改表的數(shù)據(jù)時只需要修改其內(nèi)存拷貝,再把該修改行為記錄到持久在硬盤上的事務日志中,而不用每次都將修改的數(shù)據(jù)本身持久到磁盤。
- 事務日志采用的是追加的方式,因此寫日志的操作是磁盤上一小塊區(qū)域內(nèi)的順序I/O,而不像隨機I/O需要在磁盤的多個地方移動磁頭,所以采用事務日志的方式相對來說要快得多。
- 事務日志持久以后,內(nèi)存中被修改的數(shù)據(jù)在后臺可以慢慢刷回到磁盤。
- 如果數(shù)據(jù)的修改已經(jīng)記錄到事務日志并持久化,但數(shù)據(jù)本身沒有寫回到磁盤,此時系統(tǒng)崩潰,存儲引擎在重啟時能夠自動恢復這一部分修改的數(shù)據(jù)。
目前來說,大多數(shù)存儲引擎都是這樣實現(xiàn)的,我們通常稱之為預寫式日志(Write-Ahead Logging),修改數(shù)據(jù)需要寫兩次磁盤。
5.事務的實現(xiàn)
事務的實現(xiàn)是基于數(shù)據(jù)庫的存儲引擎。不同的存儲引擎對事務的支持程度不一樣。MySQL 中支持事務的存儲引擎有 InnoDB 和 NDB。
事務的實現(xiàn)就是如何實現(xiàn)ACID特性。
事務的隔離性是通過鎖實現(xiàn),而事務的原子性、一致性和持久性則是通過事務日志實現(xiàn) 。
事務是如何通過日志來實現(xiàn)的,說得越深入越好。
事務日志包括:重做日志redo和回滾日志undo
redo log(重做日志) 實現(xiàn)持久化和原子性
- 在innoDB的存儲引擎中,事務日志通過重做(redo)日志和innoDB存儲引擎的日志緩沖(InnoDB Log Buffer)實現(xiàn)。
- 事務開啟時,事務中的操作,都會先寫入存儲引擎的日志緩沖中,在事務提交之前,這些緩沖的日志都需要提前刷新到磁盤上持久化,這就是DBA們口中常說的“日志先行”(Write-Ahead Logging)。
- 當事務提交之后,在Buffer Pool中映射的數(shù)據(jù)文件才會慢慢刷新到磁盤。此時如果數(shù)據(jù)庫崩潰或者宕機,那么當系統(tǒng)重啟進行恢復時,就可以根據(jù)redo log中記錄的日志,把數(shù)據(jù)庫恢復到崩潰前的一個狀態(tài)。
- 未完成的事務,可以繼續(xù)提交,也可以選擇回滾,這基于恢復的策略而定。
- 在系統(tǒng)啟動的時候,就已經(jīng)為redo log分配了一塊連續(xù)的存儲空間,以順序追加的方式記錄Redo Log,通過順序IO來改善性能。
- 所有的事務共享redo log的存儲空間,它們的Redo Log按語句的執(zhí)行順序,依次交替的記錄在一起。
undo log(回滾日志) 實現(xiàn)一致性
- undo log 主要為事務的回滾服務。在事務執(zhí)行的過程中,除了記錄redo log,還會記錄一定量的undo log。
- undo log記錄了數(shù)據(jù)在每個操作前的狀態(tài),如果事務執(zhí)行過程中需要回滾,就可以根據(jù)undo log進行回滾操作。
- 單個事務的回滾,只會回滾當前事務做的操作,并不會影響到其他的事務做的操作。
- Undo記錄的是已部分完成并且寫入硬盤的未完成的事務,默認情況下回滾日志是記錄下表空間中的(共享表空間或者獨享表空間)
- 二種日志均可以視為一種恢復操作,redo_log是恢復提交事務修改的頁操作,而undo_log是回滾行記錄到特定版本。
- 二者記錄的內(nèi)容也不同,redo_log是物理日志,記錄頁的物理修改操作,而undo_log是邏輯日志,根據(jù)每行記錄進行記錄。
6.你知道MySQL 有多少種日志嗎?
- 錯誤日志:記錄出錯信息,也記錄一些警告信息或者正確的信息。
- 查詢?nèi)罩?#xff1a;記錄所有對數(shù)據(jù)庫請求的信息,不論這些請求是否得到了正確的執(zhí)行。
- 慢查詢?nèi)罩?#xff1a;設置一個閾值,將運行時間超過該值的所有SQL語句都記錄到慢查詢的日志文件中。
- 二進制日志:記錄對數(shù)據(jù)庫執(zhí)行更改的所有操作。
- 中繼日志:中繼日志也是二進制日志,用來給slave 庫恢復
- 事務日志:重做日志redo和回滾日志undo
7.MySQL對分布式事務的支持
分布式事務的實現(xiàn)方式有很多,既可以采用 InnoDB 提供的原生的事務支持,也可以采用消息隊列來實現(xiàn)分布式事務的最終一致性。
這里我們主要聊一下 InnoDB 對分布式事務的支持。
MySQL 從 5.0.3 InnoDB 存儲引擎開始支持XA協(xié)議的分布式事務。一個分布式事務會涉及多個行動,這些行動本身是事務性的。所有行動都必須一起成功完成,或者一起被回滾。
在MySQL中,使用分布式事務涉及一個或多個資源管理器和一個事務管理器。
如圖,MySQL 的分布式事務模型。模型中分三塊:應用程序(AP)、資源管理器(RM)、事務管理器(TM):
- 應用程序:定義了事務的邊界,指定需要做哪些事務;
- 資源管理器:提供了訪問事務的方法,通常一個數(shù)據(jù)庫就是一個資源管理器;
- 事務管理器:協(xié)調(diào)參與了全局事務中的各個事務。
分布式事務采用兩段式提交(two-phase commit)的方式:
- 第一階段所有的事務節(jié)點開始準備,告訴事務管理器ready。
- 第二階段事務管理器告訴每個節(jié)點是commit還是rollback。如果有一個節(jié)點失敗,就需要全局的節(jié)點全部rollback,以此保障事務的原子性。
總結(jié)
以上是生活随笔為你收集整理的MySQL基础总结(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL基础总结(一)
- 下一篇: MySQL基础总结(三)