1、事務(wù)
-
事務(wù)四大特性
- 原子性(Atomicity)
- 事務(wù)是一個(gè)不可分割的最小工作單位侍芝,事務(wù)中的操作只有都發(fā)生和都不發(fā)生兩種情況
- 一致性(Consistency)
- 數(shù)據(jù)庫(kù)的完整性約束沒有被破壞州叠,事務(wù)執(zhí)行的前后都是合法的數(shù)據(jù)狀態(tài)
- 隔離性(Isolation)
- 一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾咧栗,即一個(gè)事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對(duì)并發(fā)的其他事務(wù)是隔離的致板,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相干擾
- 持久性(Durability)
- 一個(gè)事務(wù)一旦提交成功斟或,它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變將是永久性的萝挤,接下來的其他操作或故障不應(yīng)對(duì)其有任何影響
- 原子性(Atomicity)
-
事務(wù)特性的技術(shù)實(shí)現(xiàn)
- 原子性
- 通過事務(wù)控制怜珍,保證一個(gè)事務(wù)里的任務(wù)要么全部成功酥泛,要么全部失敗揭璃,通過undo log來記錄上一次數(shù)據(jù)更新的狀態(tài)瘦馍,如果出現(xiàn)異常情组,可以通過undo log回滾到上一次的狀態(tài)
- 隔離性
- 通過事務(wù)隔離級(jí)別來保證相同事務(wù)讀取數(shù)據(jù)是一致的, 在Mysql的InnoDB存儲(chǔ)引擎中是可以通過MVCC與LBCC來實(shí)現(xiàn)隔離性, 通過生成一個(gè)類似快照的數(shù)據(jù)集,保證一個(gè)事務(wù)只能讀取到小于等于當(dāng)前事務(wù)id的數(shù)據(jù)院崇,以及之后其他事務(wù)刪除的數(shù)據(jù)
- 持久性
- 通過redo log與double write來確保一個(gè)事務(wù)中對(duì)數(shù)據(jù)的更新執(zhí)行成功后能永久的存儲(chǔ)在磁盤當(dāng)中底瓣,如果數(shù)據(jù)在刷盤之前崩潰捐凭,重啟后可以通過redo log恢復(fù)數(shù)據(jù)茁肠,前提是數(shù)據(jù)頁(yè)本身沒有被破壞垦梆,是完整的,這個(gè)通過雙寫緩沖(double write)保證
- 一致性
- 數(shù)據(jù)庫(kù)通過原子性托猩、隔離性京腥、持久性來保證一致性
- ACID四大特性之中绞旅,C(一致性)是目的因悲,A(原子性)、I(隔離性)讯检、D(持久性)是手段人灼,是為了保證一致性
- 原子性
-
事務(wù)的并發(fā)問題
- 臟讀
- 事務(wù)A讀取到了其他事務(wù)已經(jīng)修改但未提交的數(shù)據(jù)投放,導(dǎo)致前后兩次讀取數(shù)據(jù)不一致
- 不可重復(fù)讀
- 事務(wù)A讀取到了其他事務(wù)修改或刪除且提交的數(shù)據(jù)灸芳,導(dǎo)致前后兩次讀取數(shù)據(jù)不一致
- 幻讀
- 事務(wù)A讀取到了其他事務(wù)插入的數(shù)據(jù)烙样,導(dǎo)致前后兩次讀取數(shù)據(jù)不一致
- 臟讀
-
事務(wù)的隔離級(jí)別
- 讀未提交(Read UnCommitted)
- 這種隔離級(jí)別最低谒获,這種級(jí)別一般是在理論上存在批狱,數(shù)據(jù)庫(kù)隔離級(jí)別一般都高于該級(jí)別
- 三種并發(fā)問題都沒解決
- 讀已提交(Read Committed)
- 可以避免臟讀精耐,但不可重復(fù)讀和幻讀的問題仍然可能出現(xiàn)
- 可重復(fù)度(Repeatable Read)
- 可以避免臟讀和不可重復(fù)讀卦停,但幻讀仍然存在
- InnoDB中RR隔離級(jí)別通過間隙鎖+MVCC解決了大部分的幻讀問題惊完,只有一種特殊的幻讀情況無法解決
- MVCC只能對(duì)快照讀起作用小槐,而對(duì)于加鎖的讀請(qǐng)求,這種屬于當(dāng)前讀件豌,當(dāng)前讀的話是可以查詢到其他事務(wù)的變更的茧彤,所以會(huì)產(chǎn)生幻讀
- 串行化(Serializable)
- 能解決臟讀曾掂、不可重復(fù)讀珠洗、幻讀問題若专,會(huì)鎖住所有事務(wù),使每個(gè)事務(wù)串行執(zhí)行蛔糯,一個(gè)事務(wù)結(jié)束另一個(gè)事務(wù)才能執(zhí)行
- 以上隔離級(jí)別由上到下,執(zhí)行效率依次遞減
- 讀未提交(Read UnCommitted)
2萝喘、鎖
- 鎖的分類
- 按粒度區(qū)分
- 行鎖
- 鎖定是一行記錄
- 表鎖
- 鎖定是整張表
- 行鎖
- 按兼容性區(qū)分
- 共享鎖
- 也叫讀鎖阁簸,多個(gè)事務(wù)可以共享一把讀鎖启妹,共享鎖會(huì)阻塞其他事務(wù)的修改
- 手工在SQL后面加上
LOCK IN SHARE MOD
進(jìn)行加鎖
- 排它鎖
- 也叫寫鎖饶米,一個(gè)事務(wù)獲取了一行數(shù)據(jù)的排它鎖照瘾,其他事務(wù)就不能再獲取這一行數(shù)據(jù)的共享鎖和排它鎖
- 增刪改數(shù)據(jù)時(shí)會(huì)默認(rèn)加上排它鎖丧慈,也可以手工在SQL后面加上
FOR UPDATE
加鎖
- 共享鎖
- 按鎖的模式區(qū)分
- 記錄鎖(Record Locks)
- 屬于行鎖鹃愤,表示對(duì)某一行記錄加鎖
- 記錄鎖總是鎖定索引記錄(SELECT和UPDATE都會(huì)加鎖)软吐,即使表沒有定義索引关噪。對(duì)于這種情況使兔, InnoDB有一個(gè)隱藏的聚集索引進(jìn)行記錄鎖定虐沥。但因?yàn)榭赡軙?huì)掃描全表欲险,那么該鎖也就會(huì)退化為表鎖
- 間隙鎖(Gap Locks)
- 鎖的是索引記錄之間的間隙天试,或者在第一個(gè)索引記錄之前或最后一個(gè)索引記錄之后的間隙上鎖
- 對(duì)于具有唯一搜索條件的唯一索引喜每,InnoDB只鎖定索引記錄雳攘,而不會(huì)鎖定間隙
- 在RC隔離級(jí)別下吨灭,不會(huì)使用間隙鎖喧兄,在RR級(jí)別及以上才會(huì)使用它
- 間隙鎖可以共存啊楚。一個(gè)事務(wù)采用的間隙鎖不會(huì)阻止另一個(gè)事務(wù)在同一間隙上采用間隙鎖。共享和排他間隙鎖之間沒有區(qū)別闸昨。它們彼此不沖突饵较,并且執(zhí)行相同的功能
- 臨鍵鎖(Next-Key Locks)
- 臨鍵鎖是記錄鎖+間隙鎖的組合拍嵌,鎖的是索引記錄上的記錄鎖和索引記錄之前間隙上的間隙鎖的組合
- Next-Key 的鎖的范圍都是左開右閉的
- 只有在RR隔離級(jí)別中才會(huì)生效
- 意向鎖(Intention Locks)
- 意向鎖是表級(jí)鎖,指示事務(wù)稍后需要(或想要循诉,表明鎖的意向)對(duì)表中的行使用哪種類型的鎖(共享鎖或獨(dú)占鎖)横辆,即用來標(biāo)識(shí)該表上面有數(shù)據(jù)被鎖住(或即將被鎖)
- 意向共享鎖(IS):一個(gè)事務(wù)在獲惹衙ā(任何一行/或者全表)S鎖之前狈蚤,一定會(huì)先在所在的表上加IS鎖
- 意向排它鎖(IX):一個(gè)事務(wù)在獲取(任何一行/或者全表)X鎖之前划纽,一定會(huì)先在所在的表上加IX鎖
- 插入意向鎖(Insert Intention Locks)
- 插入意向鎖是在插入一條記錄行前脆侮,由 INSERT 操作產(chǎn)生的一種特別的間隙鎖
- 該鎖用以表示插入意向,當(dāng)多個(gè)事務(wù)在同一區(qū)間插入位置不同的多條數(shù)據(jù)時(shí)勇劣,事務(wù)之間并不會(huì)產(chǎn)生沖突
- 雖然插入意向鎖中含有意向鎖三個(gè)字命咐,但是它并不屬于意向鎖而屬于間隙鎖,因?yàn)橐庀蜴i是表鎖而插入意向鎖是行鎖
- 自增鎖(AUTO-INC Locks)
- 自增鎖是一種特殊的表級(jí)鎖锭魔,主要用于事務(wù)中插入自增字段
- 記錄鎖(Record Locks)
- 按粒度區(qū)分
3、MVCC
MVCC是Multiversion Concurrency Control的縮寫,翻譯過來是多版本并發(fā)控制,他也是一種并發(fā)控制的解決方案
對(duì)數(shù)據(jù)的操作主要有2中,分別是讀和寫,而在并發(fā)場(chǎng)景下,就可能出現(xiàn)三種情況:`讀-讀并發(fā)`,`讀-寫并發(fā)`则剃,`寫-寫并發(fā)`己肮。
在沒有寫的情況下`讀-讀并發(fā)`是不會(huì)出現(xiàn)問題的艘绍,而`寫-寫并發(fā)`這種情況比較常用的就是通過加鎖的方式實(shí)現(xiàn)。那么`讀-寫并發(fā)`則可以通過MVCC的機(jī)制解決
-
快照讀和當(dāng)前讀
- 快照讀就是讀取的是快照數(shù)據(jù)崔涂,即快照生成的那一刻的數(shù)據(jù),像我們常用的普通的SELECT語(yǔ)句在不加鎖情況下就是快照讀
- 當(dāng)前讀就是讀取最新數(shù)據(jù),所以加鎖的 SELECT,或者對(duì)數(shù)據(jù)進(jìn)行增刪改都會(huì)進(jìn)行當(dāng)前讀
- 快照讀是MVCC實(shí)現(xiàn)的基礎(chǔ)轴脐,而當(dāng)前讀是悲觀鎖實(shí)現(xiàn)的基礎(chǔ)
-
Undo Log
- undo log是Mysql中比較重要的事務(wù)日志之一溯捆。是一種用于回退的日志劳跃,在事務(wù)沒提交之前,MySQL會(huì)先記錄更新前的數(shù)據(jù)到 undo log日志文件里面,當(dāng)事務(wù)回滾時(shí)或者數(shù)據(jù)庫(kù)崩潰時(shí)志鹃,可以利用 undo log來進(jìn)行回退
- 上面提到的存在undo log中的
更新前的數(shù)據(jù)
就是我們前面提到的快照 - 行記錄的隱式字段
- 據(jù)庫(kù)中的每行記錄中,除了保存了我們自己定義的一些字段,還有重要的隱式字段
- db_row_id:隱藏主鍵粘舟,如果我們沒有給這個(gè)表創(chuàng)建主鍵些侍,那么會(huì)以這個(gè)字段來創(chuàng)建聚簇索引
- db_trx_id:對(duì)這條記錄做了最新一次修改的事務(wù)的ID
- db_roll_ptr:回滾指針刊咳,指向這條記錄的上一個(gè)版本淮韭,其實(shí)他指向的就是Undo Log中的上一個(gè)版本的快照的地址
- 據(jù)庫(kù)中的每行記錄中,除了保存了我們自己定義的一些字段,還有重要的隱式字段
- 每一次記錄變更之前都會(huì)先存儲(chǔ)一份快照到undo log中,那么這幾個(gè)隱式字段也會(huì)跟著記錄一起保存在undo log中
-
Read View
- Read View 主要來幫我們解決可見性的問題的,他會(huì)來告訴我們本次事務(wù)應(yīng)該看到哪個(gè)快照参咙,不應(yīng)該看到哪個(gè)快照
- 在 Read View 中有幾個(gè)重要的屬性
- trx_ids:系統(tǒng)當(dāng)前未提交的事務(wù) ID 的列表。
- low_limit_id:未提交的事務(wù)中最大的事務(wù) ID裹纳。
- up_limit_id:未提交的事務(wù)中最小的事務(wù) ID剃氧。
- creator_trx_id:創(chuàng)建這個(gè) Read View 的事務(wù) ID
- 每開啟一個(gè)事務(wù)疏哗,我們都會(huì)從數(shù)據(jù)庫(kù)中獲得一個(gè)事務(wù) ID,這個(gè)事務(wù) ID 是自增長(zhǎng)的,通過 ID 大小蘑险,我們就可以判斷事務(wù)的時(shí)間順序,事務(wù)ID大的事務(wù)應(yīng)該能看到事務(wù)ID小的事務(wù)的變更結(jié)果岳悟,反之則不能
- 根據(jù)不同的事務(wù)隔離級(jí)別佃迄,Read View的獲取時(shí)機(jī)是不同的泼差,在RC下,一個(gè)事務(wù)中的每一次SELECT都會(huì)重新獲取一次Read View呵俏,而在RR下堆缘,一個(gè)事務(wù)中只在第一次SELECT的時(shí)候會(huì)獲取一次Read View
- 所以在RR這種事務(wù)隔離級(jí)別之下,因?yàn)橛蠱VCC機(jī)制普碎,就可以解決不可重復(fù)讀的問題吼肥,因?yàn)樗挥性诘谝淮蜸ELECT的時(shí)候才會(huì)獲取一次Read View,天然不存在重復(fù)讀的問題了
MVCC就是通過Read View + Undo Log來實(shí)現(xiàn)的麻车,undo log中保存了歷史快照缀皱,而Read View 用來判斷具體哪一個(gè)快照是可見的
4、InnoDB 內(nèi)存結(jié)構(gòu)
image.png
-
Buffer Pool
-
Buffer Pool
緩存的是頁(yè)面信息动猬,包括數(shù)據(jù)頁(yè)啤斗、索引頁(yè)、鎖信息等 - MySQL表數(shù)據(jù)是以頁(yè)為單位赁咙,查詢一條記錄钮莲,會(huì)從硬盤把一頁(yè)的數(shù)據(jù)加載出來,加載出來的數(shù)據(jù)叫數(shù)據(jù)頁(yè)彼水,會(huì)放入到
Buffer Pool
中 - 后續(xù)的查詢先從
Buffer Pool
中找崔拥,沒有命中再去硬盤加載,減少硬盤IO
開銷凤覆,提升性能链瓦。更新表數(shù)據(jù)的時(shí),如果Buffer Pool
里命中數(shù)據(jù)叛赚,就直接在Buffer Pool
里更新 - 緩沖池使用最近最少使用的 (LRU) 算法的變體進(jìn)行緩存數(shù)據(jù)頁(yè)
- 后續(xù)對(duì)數(shù)據(jù)的增刪改查都是在
Buffer Pool
里操作- 查詢:從磁盤加載到緩存澡绩,后續(xù)直接查緩存
- 插入:直接寫入緩存
- 更新刪除:緩存中存在直接更新,不存在加載數(shù)據(jù)頁(yè)到緩存更新
- 直接更新數(shù)據(jù)的緩存頁(yè)稱為臟頁(yè)俺附,緩存頁(yè)刷盤后稱為干凈頁(yè)
-
-
Change Buffer(寫緩沖)
如果數(shù)據(jù)頁(yè)不是唯一索引肥卡,不存在數(shù)據(jù)重復(fù)的情況,就不需要從磁盤加載索引頁(yè)判斷數(shù)據(jù)是不是重復(fù)(唯一性檢查)
這種情況下可以先把修改記錄在內(nèi)存的緩沖池中事镣,從而提升更新語(yǔ)句(Insert步鉴、Delete、Update)的執(zhí)行速度
-
Change Buffer 記錄刷到數(shù)據(jù)頁(yè)的操作叫做 merge
- 訪問這個(gè)數(shù)據(jù)頁(yè)時(shí)觸發(fā)
- 后臺(tái)線程定時(shí)觸發(fā)
- 數(shù)據(jù)庫(kù) shut down時(shí)觸發(fā)
- redo log 寫滿時(shí)觸發(fā)
redo log 寫滿時(shí)觸發(fā)
-
Log Buffer(Redo Log)日志緩存
對(duì)表數(shù)據(jù)進(jìn)行更新時(shí)會(huì)把
在某個(gè)數(shù)據(jù)頁(yè)上做了什么修改
記錄到 redo log buffer 里璃哟,之后會(huì)刷盤到redo log
文件中-
刷盤時(shí)機(jī)
InnoDB
存儲(chǔ)引擎為redo log
的刷盤策略提供了innodb_flush_log_at_trx_commit
參數(shù)氛琢,它支持三種策略- 設(shè)置為0(延遲寫)
- 事務(wù)提交的時(shí)不會(huì)主動(dòng)觸發(fā)刷盤操作
- 設(shè)置為1(默認(rèn)值,實(shí)時(shí)寫随闪,實(shí)時(shí)刷)
- 每次事務(wù)提交時(shí)都將進(jìn)行刷盤操作
- 設(shè)置為2(實(shí)時(shí)寫阳似,延遲刷)
- 表示每次事務(wù)提交時(shí)都只把redo log buffer內(nèi)容寫入page cache
-
InnoDB
存儲(chǔ)引擎有一個(gè)后臺(tái)線程,每隔1
秒铐伴,就會(huì)把redo log buffer
中的內(nèi)容寫到文件系統(tǒng)緩存(page cache
)撮奏,然后調(diào)用fsync
刷盤 -
redo log buffer
占用的空間即將達(dá)到innodb_log_buffer_size
一半的時(shí)候俏讹,后臺(tái)線程會(huì)主動(dòng)刷盤
- 設(shè)置為0(延遲寫)