一届氢、什么是事務
事務:是數(shù)據(jù)庫操作的最小工作單元,是作為單個邏輯工作單元執(zhí)行的一系列操作辰斋;這些操作作為一個整體一起向系統(tǒng)提交,要么都執(zhí)行瘸味、要么都不執(zhí)行宫仗;事務是一組不可再分割的操作集合(工作邏輯單元);
事務的四大特性:
- 原子性(Atomicity):事務是數(shù)據(jù)庫的邏輯工作單位旁仿,事務中包含的各操作要么都做藕夫,要么都不做
- 一致性(Consistency):事務開始前和結束后,數(shù)據(jù)庫的完整性約束沒有被破壞 枯冈。比如A向B轉賬毅贮,不可能A扣了錢,B卻沒收到尘奏。
- 隔離型(Isolation):一個事務的執(zhí)行不能被其它事務干擾滩褥。即一個事務內部的操作及使用的數(shù)據(jù)對其它并發(fā)事務是隔離的,并發(fā)執(zhí)行的各個事務之間不能互相干擾罪既。
- 持久性(Durability):指一個事務一旦提交铸题,它對數(shù)據(jù)庫中的數(shù)據(jù)的改變就應該是永久性的。接下來的其它操作或故障不應該對其執(zhí)行結果有任何影響琢感。
個人認為這四大特性總結起來就是兩種:
可靠性:原子性丢间、一致性、持久性可以歸納為可靠性驹针『娲欤可靠就是要保證數(shù)據(jù)的一致與不丟失。數(shù)據(jù)庫要保證數(shù)據(jù)的一致,就要處理commit與rollBack饮六;顯然處理commit指令的時候需要記錄要提交哪些數(shù)據(jù)其垄,rollback的時候需要知道回退的原數(shù)據(jù)。mysql中commit需要redo log卤橄,rollBack 對應undo log
并發(fā)控制(隔離性):當多個并發(fā)請求過來绿满,并且其中有一個請求是對數(shù)據(jù)修改操作的時候會有影響,為了避免讀到臟數(shù)據(jù)窟扑,所以需要對事務之間的讀寫進行隔離喇颁,至于隔離到啥程度得看業(yè)務系統(tǒng)的場景了,實現(xiàn)這個就得用MySQL 的隔離級別嚎货。
二橘霎、redo log 與undo log
1、redo log
redo log和undo log都屬于InnoDB的事務日志殖属。redo log 主要實現(xiàn)數(shù)據(jù)的持久化
InnoDB作為MySQL的存儲引擎姐叁,數(shù)據(jù)是存放在磁盤中的,但如果每次讀寫數(shù)據(jù)都需要磁盤IO洗显,效率會很低外潜。為此,InnoDB提供了緩存(Buffer Pool)墙懂,Buffer Pool中包含了磁盤中部分數(shù)據(jù)頁的映射橡卤,作為訪問數(shù)據(jù)庫的緩沖:當從數(shù)據(jù)庫讀取數(shù)據(jù)時,會首先從Buffer Pool中讀取损搬,如果Buffer Pool中沒有碧库,則從磁盤讀取后放入Buffer Pool;當向數(shù)據(jù)庫寫入數(shù)據(jù)時巧勤,會首先寫入Buffer Pool嵌灰,Buffer Pool中修改的數(shù)據(jù)會定期刷新到磁盤中(這一過程稱為刷臟)。Buffer Pool的使用大大提高了讀寫數(shù)據(jù)的效率颅悉,但是也帶了新的問題:如果MySQL宕機沽瞭,而此時Buffer Pool中修改的數(shù)據(jù)還沒有刷新到磁盤,就會導致數(shù)據(jù)的丟失剩瓶,事務的持久性無法保證驹溃。
如上圖所示mysql采用redo log來處理該問題:當數(shù)據(jù)修改時,除了修改Buffer Pool中的數(shù)據(jù)延曙,還會在redo log Buffer 中記錄這次操作豌鹤;當事務提交時,會調用fsync接口對redo log進行刷盤枝缔。如果MySQL宕機布疙,重啟時可以讀取redo log中的數(shù)據(jù),對數(shù)據(jù)庫進行恢復。redo log采用的是WAL(Write-ahead logging灵临,預寫式日志)截型,所有修改先寫入日志,再更新到Buffer Pool儒溉,保證了數(shù)據(jù)不會因MySQL宕機而丟失宦焦,從而滿足了持久性要求。
MySQL支持用戶自定義在commit時如何將log buffer中的日志刷log file中睁搭。這種控制通過變量 innodb_flush_log_at_trx_commit 的值來決定赶诊。該變量有3種值:0笼平、1园骆、2,默認為1寓调。但注意锌唾,這個變量只是控制commit動作是否刷新log buffer到磁盤。
- 當設置為1的時候夺英,事務每次提交都會將log buffer中的日志寫入os buffer并調用fsync()刷到log file on disk中晌涕。這種方式即使系統(tǒng)崩潰也不會丟失任何數(shù)據(jù),但是因為每次提交都寫入磁盤痛悯,IO的性能較差余黎。
- 當設置為0的時候,事務提交時不會將log buffer中日志寫入到os buffer载萌,而是每秒寫入os buffer并調用fsync()寫入到log file on disk中惧财。也就是說設置為0時是(大約)每秒刷新寫入到磁盤中的,當系統(tǒng)崩潰扭仁,會丟失1秒鐘的數(shù)據(jù)垮衷。
- 當設置為2的時候,每次提交都僅寫入到os buffer乖坠,然后是每秒調用fsync()將os buffer中的日志寫入到log file on disk搀突。
既然redo log也需要在事務提交時將日志寫入磁盤,為什么它比直接將Buffer Pool中修改的數(shù)據(jù)寫入磁盤(即刷臟)要快呢熊泵?主要有以下兩方面的原因:
- 刷臟是隨機IO仰迁,因為每次修改的數(shù)據(jù)位置隨機,但寫redo log是追加操作顽分,屬于順序IO徐许。
- 刷臟是以數(shù)據(jù)頁(Page)為單位的,MySQL默認頁大小是16KB怯邪,一個Page上一個小修改都要整頁寫入绊寻;而redo log中只包含真正需要寫入的部分,無效IO大大減少。
2澄步、undo log
undo log 的寫入時機與redo log一致冰蘑。
InnoDB實現(xiàn)回滾,靠的是undo log:當事務對數(shù)據(jù)庫進行修改時村缸,InnoDB會生成對應的undo log祠肥;如果事務執(zhí)行失敗或調用了rollback,導致事務需要回滾梯皿,便可以利用undo log中的信息將數(shù)據(jù)回滾到修改之前的樣子仇箱。
undo log屬于邏輯日志,它記錄的是sql執(zhí)行相關的信息东羹。當發(fā)生回滾時剂桥,InnoDB會根據(jù)undo log的內容做與之前相反的工作:對于每個insert,回滾時會執(zhí)行delete属提;對于每個delete权逗,回滾時會執(zhí)行insert;對于每個update冤议,回滾時會執(zhí)行一個相反的update斟薇,把數(shù)據(jù)改回去。以update操作為例:當事務執(zhí)行update時恕酸,其生成的undo log中會包含被修改行的主鍵(以便知道修改了哪些行)堪滨、修改了哪些列、這些列在修改前后的值等信息蕊温,回滾時便可以使用這些信息將數(shù)據(jù)還原到update之前的狀態(tài)袱箱。
三、Mysql的鎖機制
當數(shù)據(jù)庫有并發(fā)事務的時候寿弱,可能會產(chǎn)生數(shù)據(jù)的不一致犯眠,這時候需要一些機制來保證訪問的次序,mysql的鎖機制可以達到該目的
1. 按照鎖的粒度分數(shù)據(jù)庫鎖有哪些症革?鎖機制與InnoDB鎖算法
在關系型數(shù)據(jù)庫中筐咧,可以按照鎖的粒度把數(shù)據(jù)庫鎖分為行級鎖(INNODB引擎)、表級鎖(MYISAM引擎)和頁級鎖(BDB引擎 )噪矛。
MyISAM和InnoDB存儲引擎使用的鎖:
- MyISAM采用表級鎖(table-level locking)量蕊。
- InnoDB支持行級鎖(row-level locking)和表級鎖,默認為行級鎖
行級鎖:行級鎖是Mysql中鎖定粒度最細的一種鎖艇挨,表示只針對當前操作的行進行加鎖残炮。行級鎖能大大減少數(shù)據(jù)庫操作的沖突。其加鎖粒度最小缩滨,但加鎖的開銷也最大势就。行級鎖分為共享鎖 和 排他鎖泉瞻。
特點:開銷大,加鎖慢苞冯;會出現(xiàn)死鎖袖牙;鎖定粒度最小,發(fā)生鎖沖突的概率最低舅锄,并發(fā)度也最高鞭达。
表級鎖:表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖皇忿,它實現(xiàn)簡單畴蹭,資源消耗較少,被大部分MySQL引擎支持鳍烁。最常使用的MYISAM與INNODB都支持表級鎖定叨襟。表級鎖定分為表共享讀鎖(共享鎖)與表獨占寫鎖(排他鎖)。
特點:開銷小老翘,加鎖快芹啥;不會出現(xiàn)死鎖;鎖定粒度大铺峭,發(fā)出鎖沖突的概率最高,并發(fā)度最低汽纠。
頁級鎖 :頁級鎖是MySQL中鎖定粒度介于行級鎖和表級鎖中間的一種鎖卫键。表級鎖速度快,但沖突多虱朵,行級沖突少莉炉,但速度慢。所以取了折衷的頁級碴犬,一次鎖定相鄰的一組記錄絮宁。
特點:開銷和加鎖時間界于表鎖和行鎖之間;會出現(xiàn)死鎖服协;鎖定粒度界于表鎖和行鎖之間绍昂,并發(fā)度一般
從鎖的類別上來講,有共享鎖和排他鎖偿荷。
- 共享鎖(S鎖): 又叫做讀鎖窘游。當用戶要進行數(shù)據(jù)的讀取時,對數(shù)據(jù)加上共享鎖跳纳。共享鎖可以同時加上多個忍饰。事務T對數(shù)據(jù)對象A加上共享鎖,則事務T可以讀A但不能修改A寺庄,其他事務只能再對A加共享鎖艾蓝,而不能加排他鎖力崇,直到T釋放A上的共享鎖。這保證了其他事務可以讀A赢织,但在T釋放A上的共享鎖之前不能對A做任何修改餐曹。
- 排他鎖(X鎖): 又叫做寫鎖。當用戶要進行數(shù)據(jù)的寫入時敌厘,對數(shù)據(jù)加上排他鎖台猴。排他鎖只可以加一個。若事務T對數(shù)據(jù)對象A加上排他鎖俱两,事務T可以讀A也可以修改A饱狂,其他事務不能再對A加任何鎖,直到T釋放A上的鎖宪彩。這保證了其他事務在T釋放A上的排他鎖之前不能再讀取和修改A休讳。
2、InnoDB鎖的特性
由于 MySQL 的Innodb引擎的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會出現(xiàn)鎖沖突的尿孔。
在不通過索引條件查詢的時候俊柔,InnoDB使用的是表鎖!
當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,另外,不論 是使用主鍵索引活合、唯一索引或普通索引,InnoDB 都會使用行鎖來對數(shù)據(jù)加鎖雏婶。
即便在條件中使用了索引字段,但是否使用索引來檢索數(shù)據(jù)是由 MySQL 通過判斷不同 執(zhí)行計劃的代價來決定的,如果 MySQL 認為全表掃 效率更高,比如對一些很小的表,它 就不會使用索引,這種情況下 InnoDB 將使用表鎖,而不是行鎖。因此,在分析鎖沖突時, 別忘了檢查 SQL 的執(zhí)行計劃(explain查看),以確認是否真正使用了索引白指。
三留晚、Mysql的隔離機制
Read uncommitted 讀未提交:READ UNCOMMITTED級別忽略其它事務放置的鎖。使用READ UNCOMMITTED級別運行的事務告嘲,能夠讀取尚未由其它事務提交的改動后的數(shù)據(jù)值错维,這些行為稱為“臟”讀。我們所說的臟讀橄唬,兩個并發(fā)的事務赋焕,事務A可以讀取到事務B未提交的數(shù)據(jù)。假設事務A回滾仰楚,事務B就讀取了一行沒有提交的數(shù)據(jù)隆判。這種數(shù)據(jù)我們覺得是不存在的。
Read committed 讀提交:一個事務只能讀取另一個事務已經(jīng)提交的修改缸血。其避免了臟讀蜜氨,但仍然存在不可重復讀和幻讀問題。大多數(shù)數(shù)據(jù)庫的默認級別就是Read committed捎泻。比方Sql Server , Oracle飒炎。
Repeatable read 反復讀:該級別指定了在當前事務提交之前,其它不論什么事務均不能夠改動或刪除當前事務已讀取的數(shù)據(jù)笆豁。并發(fā)性低于 READ COMMITTED郎汪。由于已讀數(shù)據(jù)的共享鎖在整個事務期間持有赤赊,而不是在每一個語句結束時釋放。這個隔離級別僅僅是說煞赢,不可以改動和刪除抛计,可是并沒有強制不能插入新的滿足條件查詢的數(shù)據(jù)行。所以會產(chǎn)生“幻讀”照筑;Mysql的默認隔離級別就是Repeatable read
Serializable 串行讀:完全串行化的讀吹截,每次讀都需要獲得表級共享鎖,讀寫相互都會阻塞
| 隔離級別 | 讀數(shù)據(jù)一致性 | 臟讀 | 不可重復讀 | 幻讀 |
| 未提交讀(Read uncommitted) | 最低級別隔離凝危,只能保證不讀取物理上損壞的數(shù)據(jù) | 是 | 是 | 是 |
| 已提交讀(Read committed) | 語句級別 | 否 | 是 | 是 |
| 可重復讀(Repeatable read) | 事務級別 | 否 | 否 | 是 |
| 可序列化(Serializable) | 最高級別波俄,事務級 | 否 | 否 | 否 |
- 臟讀(Drity Read):某個事務已更新一份數(shù)據(jù),另一個事務在此時讀取了同一份數(shù)據(jù)蛾默,由于某些原因懦铺,前一個RollBack了操作,則后一個事務所讀取的數(shù)據(jù)就會是不正確的支鸡。
- 不可重復讀(Non-repeatable read):在一個事務的兩次查詢之中數(shù)據(jù)不一致冬念,這可能是兩次查詢過程中間插入了一個事務更新了原有的數(shù)據(jù)。不可重復讀主要針對的是update與delete
- 幻讀(Phantom Read):在一個事務的兩次查詢中數(shù)據(jù)筆數(shù)不一致牧挣,例如有一個事務查詢了幾列(Row)數(shù)據(jù)急前,而另一個事務卻在此時插入了新的幾列數(shù)據(jù),先前的事務在接下來的查詢中浸踩,就會發(fā)現(xiàn)有幾列數(shù)據(jù)是它先前所沒有的叔汁。幻讀主要是針對insert检碗;
1、mysql解決幻讀的方式:MVCC
在InnoDB中码邻,會在每行數(shù)據(jù)后添加兩個額外的隱藏的值來實現(xiàn)MVCC折剃,這兩個值一個記錄這行數(shù)據(jù)何時被創(chuàng)建,另外一個記錄這行數(shù)據(jù)何時過期(或者被刪除)像屋。 在實際操作中怕犁,存儲的并不是時間,而是事務的版本號己莺,每開啟一個新事務奏甫,事務的版本號就會遞增。 在可重讀Repeatable reads事務隔離級別下:
- SELECT時凌受,讀取創(chuàng)建版本號<=當前事務版本號阵子,并且會移除版本號為空或>當前事務版本號的數(shù)據(jù)行。
- INSERT時胜蛉,保存當前事務版本號為行的創(chuàng)建版本號
- DELETE時挠进,保存當前事務版本號為行的刪除版本號
- UPDATE時色乾,插入一條新紀錄,保存當前事務版本號為行創(chuàng)建版本號领突,同時保存當前事務版本號到原來刪除的行
舉例說明MVCC如何避免幻讀的:事務A讀取age<20的數(shù)據(jù)暖璧,返回5條,Mysql為其創(chuàng)建的事務版本號是10001君旦;此時事務B插入age=18的一條數(shù)據(jù)澎办,Mysql為其創(chuàng)建的事務版本號是10001;緊接著事務A再次查詢age<20的數(shù)據(jù)金砍,返回依然是5條局蚀,也就是事務B新插入的數(shù)據(jù)對于事務A來說是隔離的。
由此我們發(fā)現(xiàn)在RR級別中捞魁,通過MVCC機制至会,雖然讓數(shù)據(jù)變得可重復讀,并且避免的幻讀谱俭,但我們讀到的數(shù)據(jù)可能是歷史數(shù)據(jù)奉件,是不及時的數(shù)據(jù),不是數(shù)據(jù)庫當前的數(shù)據(jù)昆著!這在一些對于數(shù)據(jù)的時效特別敏感的業(yè)務中县貌,就很可能出問題。對于這種讀取歷史數(shù)據(jù)的方式凑懂,我們叫它快照讀 (snapshot read)煤痕,而讀取數(shù)據(jù)庫當前版本數(shù)據(jù)的方式,叫當前讀 (current read)接谨。很顯然摆碉,在MVCC中是采取的快照讀;如果要實現(xiàn)當前讀就需要使用鎖機制脓豪。