什么是事務?
官方點說,事務就是數據庫管理系統執行過程中的一個邏輯單位,由一個有限的數據庫操作作序列構成。
通俗點說,事務就是一組操作要么同時成功要么同時失敗。
事務的四大特性(ACID):
1.原子性(atomicity):一個事務必須視為一個不可分割的最小工作單元,整個事務中的所有操作要么全部提交成功,要么全部失敗回滾,對于一個事務來說,不可能只執行其中的一部分操作,這就是事務的原子性。
2.一致性(consistency):數據庫總數從一個一致性的狀態轉換到另一個一致性的狀態。
3.隔離性(isolation):一個事務所做的修改在最終提交以前,對其他事務是不可見的。
4.持久性(durability):一旦事務提交,則其所做的修改就會永久保存到數據庫中。此時即使系統崩潰,修改的數據也不會丟失。
事務并發帶來的問題:
1.臟讀:臟讀是指一個事務在處理過程中讀取了另一個事務未提交的數據。
2.不可重復讀:指的是在一個事務處理中讀取到其他事務修改或刪除并提交的數據,導致多次讀取結果不一致。
3.幻讀:指的是在一個事務處理中讀取到其他事務插入并提交的數據,導致多次讀取結果不一致。
事務并發帶來的三大問題其實都是數據庫讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決。
mysql事務的隔離級別:
事務隔離級別臟讀不可重復讀幻讀
讀未提交(read-uncommitted)可能可能可能
不可重復讀(read-committed)不可能可能可能
可重復讀(repeatable-read)不可能不可能在InnoDB中不可能
串行化(serializable)不可能不可能不可能
為什么InnoDB中在rr級別的時候就解決了幻讀這個問題?
MVCC與LBCC
如果要解決讀一致性問題,保證一個事務前后讀取數據結果的一致性,實現事務隔離應該怎么做?
簡單思考下解決方案:
LBCC(全稱Lock Based Concyrrency Control):在讀取數據前對其加鎖,阻止其他事務對數據進行修改。
MVCC(全稱Multi Version Concurrency Control):生成一個數據請求時間點的一致性數據快照(snapshot),并用這個快照來提供一定級別(語句級或者事務級)的一致性讀取。
InnoDB中怎么實現的MVCC
其實InnoDB中,自動為每一行數據添加了三個隱藏字段:
DB_ROW_ID(6字節):行標識,前邊的文章中提到過這個字段。
DB_TRX_ID(6字節):創建版本號,插入或更新行的最后一個事務的id,自動遞增。
DB_ROLL_PIR(7字節):刪除版本號,用于回滾。
主要記住,在InnoDB中,一個事務開始時,會生成一個當前時間點的數據快照(可以理解為一個副本),只要事務不結束,他就只能在這個數據快照里查找數據,也就是只能查找到創建版本號小于當前事務id的數據和刪除版本號大于當前事務id的數據(或者是在創建版本號小于當前事務id的前提下,沒有刪除版本號的數據)
在InnoDB中MVCC和鎖是相互配合使用的,下邊就看看鎖的東西。
鎖
表鎖與行鎖的區別:
鎖定粒度:表鎖 > 行鎖
加鎖效率:表鎖 > 行鎖
沖突概率:表鎖 > 行鎖
并發性能:表鎖 < 行鎖
注意:MYISAM引擎只支持表鎖,InnoDB既支持表鎖也支持行鎖
行鎖
共享鎖(Shared Locks):
又稱為讀鎖,簡稱s鎖,顧名思義共享鎖就是多個事務對于同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能寫,不然容易造成死鎖。
加鎖方式:在select語句后加LOCK IN SHARE MODE;
釋鎖方式:commit / rollback
排他鎖(Exclusive Locks):
又稱為寫鎖,簡稱x鎖,排他鎖不能和其他鎖共存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能獲取該行的鎖(包括共享鎖和排他鎖),只有獲取到該行的排他鎖的事務是可以對數據進行讀取和修改。
加鎖方式:
1.手動加鎖:在語句后加FOR UPDATE;(select時需手動)
2.自動加鎖:在insert / update / delete 語句時或自動加排他鎖
釋鎖方式:commit / rollback
注意:innoDB中獲取鎖超時間默認為50秒,查看語句為show variables like 'innodb_lock_wait_timeout';。
表鎖
意向共享鎖(Intention Shared Lock):
簡稱IS鎖,表示事務準備給數據行進行加入共享鎖,也就是說一個數據行加共享鎖之前必須先獲得該表的IS鎖。
意向排他鎖(Intention Exclusive Lock):
簡稱IX鎖,表示事務準備給數據行加入排他鎖,說明事務在一個數據行加排他鎖前必須要獲得該表的IX鎖。
注意:意向鎖是由數據引擎自己維護的,用戶是無法手動操作意向鎖的。
問:為什么需要表級別的意向鎖?
答:假如要給一張表加表鎖,那么這個加表鎖的前提是沒有其他任何事務已經鎖住了這張表的任意一行數據。那么怎么來確定這個前提呢?
必然要有一個全表掃描才能確定每一行數據都沒有被加鎖。如果這張表數據比較大呢?有千萬數據,那這個全表掃描的過程就要消耗很多時間才能確定能不能加表鎖,并且如果在掃描的過程中萬一有其他事務來加鎖怎么辦,所以說,全表掃描加表鎖這種方式既慢并且消耗性能,帶來嚴重的并發問題。為了解決這一問題,才有了意向鎖。加表鎖的時候只要看一下這個表上有沒有意向鎖就ok了,因為你加行鎖的時候都會先獲取到表的意向鎖才行。意向鎖就是為了提高加表鎖的效率。
問:鎖的作用?
答:和Java中的鎖一樣,都是為了解決資源競爭的問題,Java中的資源是對象。而數據庫中的資源就是表和行數據,鎖就是為了解決對于事務并發訪問的問題。
問:鎖到底鎖住了什么?是一行數據?還是一個字段?
InnoDB行鎖原理
針對上述問題,其實有三種情況:
1.沒有索引的表上加行鎖,會鎖住整張表,出現鎖表的情況。
2.有主鍵索引的表上加行鎖,會鎖住索引。
3.用唯一索引(輔助索引)的字段加行鎖,會鎖住輔助索引和主鍵索引。
在InnoDB中,行鎖就是鎖住索引記錄來實現的,加鎖的時候在mysql自帶的information_schema庫中的INN0DB_LOCKS表中可以看到:
lock_type:鎖類型;lock_table:加鎖的表;lock_index:鎖住的索引
問:沒有索引的表上加行鎖為什么會鎖表?
注意:在之前的文章中說過,InnoDB中一張表是不可能沒有索引的,如果用戶沒定義,默認就會用隱藏的字段ROW_ID作為聚集索引。那么加行鎖的時候,條件沒有命中索引(因為用的是隱藏字段作為索引,那么肯定不能命中啊),就會走全表掃描,不得不把所有的聚集索引全都鎖住,所以會出現鎖表的情況。
問:為什么用唯一索引加行鎖時,會鎖住主鍵索引?
這就又涉及到前邊的知識——回表,因為InnoDB中,輔助索引的葉子節點上存放的是主鍵索引,索引用輔助索引加行鎖時會先鎖住輔助索引,然后鎖住主鍵索引。本質上其實還是鎖住了主鍵索引。那么鎖到底鎖住了什么?相信大家心里已經有答案了。
InnoDB行鎖算法
主要有三種算法:
1.記錄鎖(Record Lock)
鎖定記錄,在唯一性索引(唯一/主鍵)等值查詢時,精準匹配到一個索引記錄的時候會使用記錄鎖。
(如下圖)比如用where id = 4這個條件時,就會精準匹配到一個索引記錄。
2.間隙鎖(Gap Lock)
鎖定區間,鎖定是的是數據庫不存在的區間范圍,Gap Lock相互之間不會沖突。
比如,用where id = 6這個條件時,發現沒有這個數據記錄,就會鎖住(4,7)這個范圍(注意左開右開不包括記錄)。
3.臨建鎖(Next-key Lock)
鎖定記錄和區間,條件范圍查找時,會鎖住記錄和區間。Next-key Lock = Record Lock + Gap Lock;
比如,用where id > 5 and id <9時,就會鎖住(4,7]和(7,10]這個范圍的區間和記錄,也就是(4,10]這個范圍的記錄和區間(注意是左開右閉,不包括左邊的記錄,包括右邊的記錄)
事務隔離級別的選擇
首先Read Uncommited不加鎖和Serializable串行化這兩種級別基本是不會被使用的。
Read Commited 對于普通的select語句使用mvcc,對于加鎖的select和更新語句使用Record Lock記錄鎖。
Repeatable Read 對于普通的select語句使用mvcc,對于加鎖的select和更新語句會使用Next-key Lock 、 Record Lock 、 Gap Lock。
1.RR的間隙鎖會導致鎖的范圍擴大
2.條件列未使用到索引時,RR鎖表,RC鎖行
3.RC的“半一致性”讀(semi-consistent)可以增加update操作的并發性。
從上述三個問題中,看似RC的級別更有優勢,其實在實際中,合理的加鎖,在有索引的列加鎖并不會出現上邊的情況,通常使用默認的事務隔離級別RR就ok了。