數據庫事務總結與整理(一)

前言

提數據庫就會講到數據庫的事務問題,常見的一個問題就是:你的數據庫是如何保證事務的?這裏的數據庫可以是關係型數據庫,如mysql,或者是非關係型的,如MongoDB或是其他數據庫。令人頭疼的是,這樣的問題很常見卻不知怎麼回答,一個原因是沒有涉及過事務相關的操作,還有項目中有事務,往往也是一個註解搞定,根本不深入。今天就來全面總結下有關事務的問題。

事務的若干概念

ACID特性

一般來說,事務具有4個特點,A(原子性),C(一致性),I(隔離性,事務之間具有一定隔離性),D(持久性),稱爲ACID特性。

原子性(Atomicity):事務作爲一個整體被執行,包含在其中的對數據庫的操作要麼全部被執行,要麼都不執行。

一致性(Consistency):事務應確保數據庫的狀態從一個一致狀態轉變爲另一個一致狀態。一致狀態的含義是數據庫中的數據應滿足完整性約束。

隔離性(Isolation):多個事務併發執行時,一個事務的執行不應影響其他事務的執行。這裏的不影響具體要看具體的事務隔離級別。

持久性(Durability):一個事務一旦提交,他對數據庫的修改應該永久保存在數據庫中。

數據庫的原子性,一致性和持久性一般通過事務日誌實現,而隔離性通過數據庫鎖、MVCC(併發一致性控制)實現。

事務隔離型

事務定義了四種隔離級別。分別是未提交讀已提交讀可重複讀串行化

  1. 讀未提交(READ-UNCOMMITTED)
    所有事務都可以看到其他未提交事務的執行結果,可能產生髒讀,一般(十分)不採用。

  2. 讀已提交(READ-COMMITTED)
    其他事務提交後纔可以看到,避免了髒讀現象。但仍避免不了這個問題:我一個事務中第一次某行數據是一個值,第二次讀又是一個值了。(在兩次讀過程中,有事務提交了),這種現象叫不可重複讀。顧名思義,就是不能重複讀取。
    讀已提交是大多數數據庫默認的隔離級別(不包括mysql,mysql隔離級別是可重複讀),因爲這已經滿足隔離的基本定義,性能也很高。

  3. 可重複讀(REPEATABLE-READ)
    針對前一種隔離級別的問題,修復了不可重複讀的問題,也就是在一個事務中,讀取同一行的數據是不變的。不過,有個問題是:當用戶讀取某一範圍的數據行時,另一個事務又在該範圍內插入了新行,當用戶再讀取該範圍的數據行時,會發現有新的“幻影” 行。這是幻讀
    可重複讀是mysql默認隔離級別。

  4. 串行化(SERIALIZABLE)
    所有事務操作串行執行,沒有隔離級別引起的問題,但性能也最差,一般不用。

設置mysql隔離級別

set session transaction isolation level read committed;

mysql的鎖

按鎖的粒度來分,分爲表鎖和行鎖。

行級鎖是Mysql中鎖定粒度最細的一種鎖,只針對行加鎖。行級鎖能大大減少數據庫操作的衝突。加鎖粒度最小,但加鎖的開銷也最大。行級鎖分爲共享鎖 和 排他鎖。

表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支持。最常使用的MYISAM與INNODB都支持表級鎖定。表級鎖定分爲表共享讀鎖(共享鎖)與表獨佔寫鎖(排他鎖)。

MySQL不同的存儲引擎支持不同的鎖機制。如MyISAM支持表級鎖,而InnoDB支持行級鎖和表級鎖(默認支持行級鎖)。

MyISAM

MyIsam支持表級鎖,表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock)。也就是讀鎖和寫鎖。其中讀讀之間可以共存,讀寫之間互斥。

MyIsam的讀、寫鎖都是阻塞鎖,即如果拿不到鎖,會一直阻塞住。

加鎖方式是一次性拿到所有的鎖,所以不會出現死鎖現象。一般select會自動加讀鎖,updateinsertdelete會自動加寫鎖。另外,即是同一個表,如果在sql中出現多次,也會加多個鎖。

默認寫鎖的級別比讀鎖高,當有大量讀、寫任務時,讀鎖可能會一直阻塞獲取鎖。這也正是 MyISAM 表不太適合於有大量更新操作和查詢操作應用的原因。

InnoDB

InnoDB默認爲行鎖,表鎖前面已經說了,現在說說行鎖。行鎖種類和表鎖類似,也分別讀和寫鎖。默認分爲共享鎖(S,也就是讀)和排他鎖(X,寫鎖)。

共享鎖,行鎖加共享鎖使用SELECT ... LOCK IN SHARE MODE。共享鎖之間可以併發。

排他鎖,使用for update進行加鎖。排他鎖與其他鎖互斥,加排他鎖後不能再加其他鎖。

select .... for update 就是對讀操作加了排他鎖。

其他 update、delete、insert等寫操作,會自動加排他鎖。

注意: 行鎖是一行一行進行的,分別取出來一一加鎖。

MVCC(多版本併發控制)

MVCC是數據庫提高讀、寫併發的常用技術,簡單的說就是通過設置多版本,在讀取數據時根據事務開始時機以及隔離級別讀取對應版本的數據,可以讓讀、寫互不阻塞。大大提高性能。

MVCC中讀分爲快照讀和當前讀。快照讀就是讀取歷史版本,當前讀就是讀取當前數據。

普通的讀都屬於快照讀,如select ...

屬於當前讀的有(加鎖操作和寫操作)

select ... lock in share mode 加行共享鎖
select ... for update 加行排他鎖
update ...
delete
insert into

加鎖分析

在innodb下分析

  1. 普通的select 不加鎖,直接走快照讀。
  2. updatedelete操作,在RC(讀已提交)情況下,有主鍵走主鍵(加X鎖),有索引走索引(唯一索引或普通索引,加鎖兩次,一次加在二級索引上,一次加在主鍵索引上,唯一索引鎖一行,普通的鎖符合條件的行數,加X鎖)
  3. 對於updatedelete,沒有索引的情況,在RC(讀已提交)下,會掃全表加鎖,然後對不符合條件的釋放鎖。(全部加鎖這一步是不能少的)
  4. 對於updatedelete在RR(可重複讀)情況下,查詢條件是索引,但不是唯一索引的情況下,會在索引中加X鎖和間隙鎖(GAP), 在主鍵索引中加X鎖。GAP鎖是爲防止記錄之間插入記錄。
  5. 對於updatedelete,在RR下,查詢條件不是索引,掃描主鍵索引,在主鍵索引所有記錄上加X鎖,並在記錄之間加GAP(間隙鎖)。
  6. 對於where條件,多餘1個情況下,可分爲Index keyIndex Filtertable filter。有索引確定查詢條件(會添加間隙鎖),index filter 對索引條件進行過濾,過濾後加上X鎖,table filter 會始終加上X鎖。有mysql引擎層過濾。

發生死鎖的條件不在於sql的多少,而在於加鎖的順序,比如一個sql先對A行加鎖,後對B行加鎖,而另一個sql則相反,則可能會產生死鎖。具體可看MySQL 加鎖處理分析 寫的很詳細。

隔離性實踐

讀已提交情況下

事務A 事務B
begin; begin;
select score from grade where id=1; // id爲1的分數爲50
update grade set score =100 where id=1;
select * from grade where id=1; // 這裏成了100
commit; commit;

如果改爲可重複讀,則結果一直是50。

參考文章

  1. 互聯網項目中mysql應該選什麼事務隔離級別
  2. 徹底理解數據庫事務
  3. MySQL 加鎖處理分析
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章