MySQL中的事務與鎖

這篇文章詳細介紹一下數據庫事務與鎖的相關知識。主要是一些概念性的東西看起來可能比較乏味,但作為一名合格的程序員來說,你應該掌握也必須掌握。這些理論知識好比是一個人的內功,我們平時敲代碼是外功,只有內外兼修,相互促進,才能達到武林高手的境界。好了廢話不多說,下面開始。

數據庫事務

事務的邊界

事務的開始邊界(begin)?
事務的結束邊界(commit):提交事務,永久保存被事務更新后的數據庫狀態。?
事務的異常結束邊界(rollback):撤銷事務,使數據庫退回到執行事務前的初始狀態。

每啟動一個MySQL.exe程序,就會得到一個單獨的數據庫連接。每個數據庫連接都有一個全局變量autocommit,表示當前的事務模式,它有兩個值可選:

  • 0:表示手工提交模式

  • 1:表示自動提交模式,默認值

我們可以查看和修改這個值。

數據庫事務的4個特性(ACID):

  • 原子性(Atomicity):事務是一個原子操作單元,其對數據的修改,要么全部執行,要么全都不執行;

  • 一致性(Consistent):在事務開始和完成時,數據都必須保持一致狀態;

  • 隔離性(Isolation):數據庫系統提供一定的隔離機制,保證事務在不受外部并發操作影響的“獨立”環境執行;

  • 持久性(Durable):事務完成之后,它對于數據的修改是永久性的,即使出現系統故障也能夠保持。

事務隔離級別

數據庫事務隔離級別,只是針對一個事務能不能讀取其它事務的中間結果。

MySQL中的事務與鎖

  • Read Uncommitted (讀取未提交內容)?
    在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用于實際應用,因為它的性能也不比其他級別好多少。讀取未提交的數據,也被稱之為臟讀( Dirty Read )。

  • Read Committed (讀取提交內容)?
    這是大多數數據庫系統的默認隔離級別(但不是 MySQL 默認的)。它滿足了隔離的簡單定義:一個事務只能看見已經提交事務所做的改變。這種隔離級別 也支持所謂的不可重復讀( Nonrepeatable Read ),因為同一事務的其他實例在該實例處理其間可能會有新的 commit ,所以同一 select 可能返回不同結果。

  • Repeatable Read (可重讀)?
    這是 MySQL 的默認事務隔離級別,它確保同一事務的多個實例在并發讀取數據時,會看到同樣的數據行。不過理論上,這會導致另一個棘手的問題:幻讀 ( Phantom Read )。簡單的說,幻讀指當用戶讀取某一范圍的數據行時,另一個事務又在該范圍內插入了新行,當用戶再讀取該范圍的數據行時,會發現有新的 ” 幻影 ” 行。 InnoDB和 Falcon 存儲引擎通過多版本并發控制( MVCC , Multiversion Concurrency Control )機制解決了該問題。

  • Serializable (可串行化)?
    這是最高的隔離級別,它通過強制事務排序,使之不可能相互沖突,從而解決幻讀問題。簡言之,它是在每個讀的數據行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。

隔離級別越高,越能保證數據的完整性和一致性,但是對并發性能的影響也越大。?
對于多數應用程序,可以有效考慮把數據庫系統的隔離級別設為Read Committed,它能夠避免臟讀,而且具有較好的并發性能。盡管它會導致不可重復讀、虛度和第二類丟失更新這些并發問題,在可能出現這類問題的個別場合,可以由應用程序采用悲觀鎖和樂觀鎖來控制。

事務的傳播性

  1. PROPAGATION_REQUIRED?
    加入當前正要執行的事務,如果當前事務不存在,那么就起一個新的事務。Spring 操作數據庫默認的事務傳播行為就是 propagation_required 。

  2. PROPAGATION_SUPPORTS?
    如果當前在事務中,即以事務的形式運行,如果當前不再一個事務中,那么就以非事務的形式運行.

  3. PROPAGATION_MANDATORY?
    必須在一個事務中運行。也就是說,他只能被一個父事務調用。否則,他就要拋出異常。

  4. PROPAGATION_REQUIRES_NEW?
    掛起當前事務,另起一個新的事務。

  5. PROPAGATION_NOT_SUPPORTED?

    當前不支持事務。如果在事務中,會掛起當前事務,自己以非事務的行為運行。

  6. PROPAGATION_NEVER?
    不能在事務中運行,如果在事務中運行就會拋出異常。

  7. PROPAGATION_NESTED?
    嵌套的事務依賴父事務,父事務提交,它跟著提交,父事務回滾,它跟著回滾。

行級鎖

Mysql中三種類型的鎖:

行級:引擎 INNODB , 單獨的一行記錄加鎖?
頁級:引擎 BDB,一次鎖定相鄰的一組記錄。?
表級:引擎 MyISAM , 理解為鎖住整個表,可以同時讀,寫不行。?
三種鎖的特性可大致歸納如下:?
1) 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,并發度最低。?
2) 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,并發度也最高。?
3) 頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般。

我們這里主要談論的是行級鎖,一般的在秒殺系統中我們會對商品庫存使用行級鎖,因為秒殺的時候庫存是一個很重要的數據,我們在創建數據庫的表時可能會出現下面這樣的設置:

ENGINE?=?InnoDB?AUTO_INCREMENT=10?DEFAULT?CHARACTER?SET?=?utf8?comment='用戶表

將引擎設置為InnoDB,InnnoDB與其他引擎的不同:一是支持事務(TRANCSACTION),二是采用了行級鎖。

InnoDB中兩種模式的行級鎖:

1)共享鎖:允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。?
( Select * from table_name where ……lock in share mode)?
2)排他鎖:允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和 排他寫鎖。(select * from table_name where…..for update)

為了允許行鎖和表鎖共存,實現多粒度鎖機制;同時還有兩種內部使用的意向鎖(都是表鎖),分別為意向共享鎖和意向排他鎖。

  • 意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。

  • 意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。

注意:InnoDB行鎖是通過給索引上的索引項加鎖來實現的,這一點與Oracle不同,后者是通過在數據塊中對相應數據行加鎖來實現的。InnoDB這種行鎖實現特點意味著:只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會出現鎖沖突的。應用設計的時候要注意這一點。

行級鎖的優缺點

行級鎖定的優點:

  • 當在許多線程中訪問不同的行時只存在少量鎖定沖突。

  • 回滾時只有少量的更改。

  • 可以長時間鎖定單一的行。

行級鎖定的缺點:

  • 比頁級或表級鎖定占用更多的內存。

  • 當在表的大部分數據上使用時,比頁級或表級鎖定速度慢,因為你必須獲取更多的鎖。如果你在大部分數據上經常進行GROUP BY操作或

者必須經常掃描整個表,比其它鎖定明顯慢很多。

hibernate中通過行級鎖實現的悲觀鎖。

一些例子:

假設有個表單products ,里面有id跟name二個欄位,id是主鍵。?
1: 明確指定主鍵,并且有此條記錄,執行row lock。若查無此記錄,無lock。

SELECT?*?FROM?products?WHERE?id='3'?FOR?UPDATE;SELECT?*?FROM?products?WHERE?id='3'?and?name="cat"?FOR?UPDATE;

2: 無主鍵,執行table lock。

SELECT?*?FROM?products?WHERE?name='Mouse'?FOR?UPDATE;

3: 主鍵不明確,table lock。

SELECT?*?FROM?products?WHERE?id'3'?FOR?UPDATE;

注意: FOR UPDATE僅適用于InnoDB,且必須在事務塊(BEGIN/COMMIT)中才能生效。此外,如果A與B都對表id進行查詢但查詢不到記錄,則A與B在查詢上不會進行row鎖,但A與B都會獲取排它鎖,此時A再插入一條記錄的話則會因為B已經有鎖而處于等待中,此時B再插入一條同樣的數據則會拋出Deadlock found when trying to get lock; try restarting transaction。然后釋放鎖,此時A就獲得了鎖而插入成功。

以上就是MySQL中的事務與鎖的內容,更多相關內容請關注PHP中文網(www.php.cn)!

? 版權聲明
THE END
喜歡就支持一下吧
點贊14 分享