這篇文章帶給大家的內容是關於MySQL資料庫的事務隔離和MVCC的詳細介紹(圖文),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
前言:事務是存取資料庫的一個操作序列,資料庫應用系統透過事務集來完成對資料庫的存取.
1. 什麼是事務?
事務必須服從ISO/IEC所製定的ACID原則。 ACID是原子性(atomicity)、一致性(consistency)、隔離性(isolation)、持久性(durability)的縮寫,這四種狀態的意思是:
1.原子性(Atomicity)
原子性是指事務包含的所有操作要么全部成功,要么全部失敗回滾,這和前面兩篇博客介紹事務的功能是一樣的概念,因此事務的操作如果成功就必須要完全應用到數據庫,如果操作失敗則不能對資料庫有任何影響。
2.一致性(Consistency)
一致性是指交易必須使資料庫從一個一致性狀態轉換到另一個一致性狀態.
3.隔離性(Isolation)
在事務正確在提交之前,不允許把事務對該資料的改變提供給任何其他事務,即在事務正確提交之前,它可能的結果不應該顯示給其他事務.
4.持久性(Durability)
持久性是指一個交易一旦被提交了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會遺失提交交易的操作。
2. 事務的作用
當多個執行緒都開啟事務操作資料庫中的資料時,資料庫系統要能進行隔離操作,以確保各個執行緒取得資料的準確性.
3. 遇到的並發問題
1.第一類遺失更新:A交易撤銷時,把已經提交的B事務的更新資料覆蓋了.
2.第二類別遺失更新:A事務覆蓋B事務已經提交的數據,造成B事務所做操作丟失.
3.髒讀:A事務讀取了事務B中未提交的數據.
4.不可重複讀:A事務多次讀取的值不同,因為該值被B事務修改並提交了.
5.幻讀:A事務兩次讀取之間,B事務插入了資料.
4. 如何解決上面的問題呢?
為了解決上面的問題,開發者為MySQL資料庫設計了以下四種交易隔離等級:
1.Read Uncommitted(未提交讀取):允許髒讀,也就是可能讀取到其他會話中未提交交易修改的資料.
2.Read Committed(提交讀取):只能讀取到已經提交的資料。 Oracle等多數資料庫預設都是該等級 (不重複讀取).
3.Repeated Read(可重複讀取):可重複讀取。在同一個事務內的查詢都是事務開始時刻一致的,InnoDB預設層級。在SQL標準中,此隔離等級消除了不可重複讀,但是還存在幻象讀,但是innoDB解決了幻讀.
4.Serializable(串行讀):完全串行化的讀,每次讀都需要取得表格層級共享鎖定,讀寫相互都會阻塞.
1.查看全域或會話的交易隔離等級
SELECT @@global.tx_isolation, @@tx_isolation;
2.修改全域或會話的交易隔離等級
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
以下將先介紹資料庫所涉及的鎖定.
1.鎖定簡介
資料庫中的鎖是指一種軟體機制,用來控制防止某個用戶(進程會話)在已經佔用了某種資料資源時,其他用戶做出影響本使用者資料操作或導致資料非完整性和非一致性問題發生的手段。
2.鎖的等級
依鎖定層級劃分,鎖定可分為共享鎖定、排他鎖。
針對同一塊數據,多個讀取操作可以同時進行而不會互相影響。共享鎖只針對UPDATE時候加鎖,在未對UPDATE操作提交之前,其他事務只能夠取得最新的記錄但不能夠UPDATE操作。
目前寫入作業沒有完成前,阻斷其他寫鎖定和讀取鎖定。
3.鎖的粒度
依鎖的粒度劃分,鎖可分為表級鎖、行級鎖、頁級鎖。
開銷大,加上鎖慢,會出現死鎖,鎖定力度最小,發生鎖衝突的機率最低,並發度高。
開銷小,加鎖快,不會出現死鎖,鎖定力道大,發生衝突所的機率高,並發度低。
開銷和加鎖時間介於表鎖和行鎖之間,會出現死鎖,鎖定力道介於表和行行級鎖之間,並發度一般。
1.基本想法:總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖(共享資源每次只給一個執行緒使用,其它執行緒阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,例如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖.所以不管衝突是否真的發生,都會使用鎖機制。
2.悲觀鎖定功能:
1.基本想法:總是假設最好的情況,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS演算法實作。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量.
2.解釋:樂觀鎖是一種思想,樂觀鎖不會鎖住任何東西,也就是說,它不依賴資料庫的事務機制,樂觀鎖完全是應用系統層面的東西。所以它不是一種鎖定機制.如果使用樂觀鎖,那麼資料庫就必須加版本字段,否則就只能比較所有字段,但因為浮點類型不能比較,所以實際上沒有版本字段是不可行的
一般是在資料表中加上一個資料版本號version字段,表示資料被修改的次數,當資料被修改時,version值會加一。當執行緒A要更新資料值時,在讀取資料的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為目前資料庫中的version值相等時才更新,否則重試更新操作,直到更新成功。
1.核心思想:Compare and Swap,即比較再交換。
2.過程:假設有A線程準備去修改內存中變量名為name的值,因此A線程會用以前自己讀到的name變量值和此刻name的值做對比,如果一樣,則表明在變數值沒被修改過,因此可以更新修改,否則更新失敗.
前面說過,MySQL預設實現了可重複讀的事務隔離等級,但是不能解決幻讀的問題,然而在MySQL資料庫使用可重複讀的事務隔離條件下,並未發生幻讀.MySQL使用MVCC(多版本並發控制)進行了控制.
1.MVCC:是multiversion concurrency control的简称,也就是多版本并发控制,是个很基本的概念。MVCC的作用是让事务在并行发生时,在一定隔离级别前提下,可以保证在某个事务中能实现一致性读,也就是该事务启动时根据某个条件读取到的数据,直到事务结束时,再次执行相同条件,还是读到同一份数据,不会发生变化(不会看到被其他并行事务修改的数据)。
2.read view:InnoDB MVCC使用的内部快照的意思。在不同的隔离级别下,事务启动时(有些情况下,可能是SQL语句开始时)看到的数据快照版本可能也不同。在上面介绍的几个隔离级别下会用到 read view。
3.快照读: 就是所谓的根据read view去获取信息和数据,不会加任何的锁。
4.当前读:前读会获取得到所有已经提交数据,按照逻辑上来讲的话,在一个事务中第一次当前读和第二次当前读的中间有新的事务进行DML操作,这个时候俩次当前读的结果应该是不一致的,但是实际的情况却是在当前读的这个事务还没提交之前,所有针对当前读的数据修改和插入都会被阻塞,主要是因为next-key lock解决了当前读可能会发生幻读的情况。
next-key lock当使用主键索引进行当前读的时候,会降级为record lock(行锁)
InnoDB支持MVCC多版本控制,其中READ COMMITTED和REPEATABLE READ隔离级别是利用consistent read view(一致读视图)方式支持的。所谓的consistent read view就是在某一时刻给事务系统trx_sys打snapshot(快照),把当时的trx_sys状态(包括活跃读写事务数组)记下来,之后的所有读操作根据其事务ID(即trx_id)与snapshot中trx_sys的状态做比较,以此判断read view对事务的可见性。
REPEATABLE READ隔离级别(除了GAP锁之外)和READ COMMITTED隔离级别的差别是创建snapshot时机不同。REPEATABLE READ隔离级别是在事务开始时刻,确切的说是第一个读操作创建read view的时候,READ COMMITTED隔离级别是在语句开始时刻创建read view的。这就意味着REPEATABLE READ隔离级别下面一个事务的SELECT操作只会获取一个read view,但是READ COMMITTED隔离级别下一个事务是可以获取多个read view的。
创建/关闭read view需要持有trx_sys->mutex,会降低系统性能,5.7版本对此进行优化,在事务提交时session会cache只读事务的read view。
在InnoDB中,创建一个新事务的时候,InnoDB会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候,InnoDB会将该行当前的版本号与该read view进行比较。
具体的算法如下:
设该行的当前事务id为trx_id,read view中最早的事务id为trx_id_min, 最迟的事务id为trx_id_max。
如果trx_id如果trx_id>trx_id_max的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,所以该行记录的当前值不可见。
如果trx_id_min
从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号的数据,将该可见行的值返回。
需要注意的是,新建事务(当前事务)与正在内存中commit 的事务不在活跃事务链表中。
在具体多版本控制中我们先来看下源码:
函数:read_view_sees_trx_id。 read_view中保存了当前全局的事务的范围: 【low_limit_id, up_limit_id】 1.当行记录的事务ID小于当前系统的最小活动id,就是可见的。 if (trx_id up_limit_id) { return(TRUE); } 2.当行记录的事务ID大于当前系统的最大活动id(也就是尚未分配的下一个事务的id),就是不可见的。 if (trx_id >= view->low_limit_id) { return(FALSE); } 3.当行记录的事务ID在活动范围之中时,判断是否在活动链表中,如果在就不可见,如果不在就是可见的。 for (i = 0; iRead view 图解:
结语:笔者水平有限,文中如有不妥,请大家多多指教,MySQL数据库事务机制还有很多需要深入研究的,我们仍需不断钻研。
本篇文章到这里就已经全部结束了,更多其他精彩内容可以关注PHP中文网的MySQL视频教程栏目!
隔離等級 | 髒讀 | 不可重複度 | 不幻讀 |
---|---|---|---|
Read Uncommitted(未提交讀取) | 可能 | 可能 | 可能 |
Read Committed(提交讀取) | #不可能 | 可能 | ##可能|
不可能 | 不可能 | 可能 | |
不可能 | 不可能 | #不可能 |
以上是MySQL資料庫的事務隔離與MVCC的詳細介紹(圖文)的詳細內容。更多資訊請關注PHP中文網其他相關文章!