1. 程式人生 > 資訊 >聯想回應撤回科創板 IPO 申請:財務資訊可能失效,撤回不影響財務狀況

聯想回應撤回科創板 IPO 申請:財務資訊可能失效,撤回不影響財務狀況

不少人在開發的時候,應該很少會注意到這些鎖的問題,也很少會給程式加鎖(除了庫存這些對數量準確性要求極高的情況下),即使我們不會這些鎖知識,我們的程式在一般情況下還是可以跑得好好的。因為資料庫隱式幫我們加了這些鎖了,只有在某些特定的場景下我們才需要手動加鎖。

對於UPDATE、DELETE、INSERT語句,InnoDB會自動給涉及資料集加排他鎖(X) 。而MyISAM在執行查詢語句SELECT前,會自動給涉及的所有表加讀鎖,在執行增、刪、改操作前,會自動給涉及的表加寫鎖,這個過程並不需要我們去手動操作。

那麼在特定情況下,我們該如何去加鎖呢?下面咱們來認真的看看

看上圖就知道MySQL鎖可以按使用方式分為:樂觀鎖與悲觀鎖。按粒度分可以分為表級鎖,行級鎖,頁級鎖。

表鎖

從鎖的粒度,我們可以分成兩大類:

表鎖:開銷小,加鎖快;不會出現死鎖;鎖定力度大,發生鎖衝突概率高,併發度最低。

行鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度小,發生鎖衝突的概率低,併發度高 不同的儲存引擎支援的鎖粒度是不一樣的。

  • InnoDB行鎖和表鎖都支援、MyISAM只支援表鎖!

  • InnoDB只有通過索引條件檢索資料才使用行級鎖,否則,InnoDB使用表鎖也就是說,InnoDB的行鎖是基於索引的!

表鎖下又分為兩種模式:表讀鎖(Table Read Lock)&&表寫鎖(Table Write Lock)

從下圖可以清晰看到,在表讀鎖和表寫鎖的環境下:讀讀不阻塞,讀寫阻塞,寫寫阻塞!

讀讀不阻塞:當前使用者在讀資料,其他的使用者也在讀資料,不會加鎖

讀寫阻塞:當前使用者在讀資料,其他的使用者不能修改當前使用者讀的資料,會加鎖!

寫寫阻塞:當前使用者在修改資料,其他的使用者不能修改當前使用者正在修改的資料,會加鎖!

從上面已經看到了:讀鎖和寫鎖是互斥的,讀寫操作是序列。

  • 如果某個程序想要獲取讀鎖,同時另外一個程序想要獲取寫鎖。在mysql中,寫鎖是優先於讀鎖的!

  • 寫鎖和讀鎖優先順序的問題是可以通過引數調節的:max_write_lock_count和low-priority-updates

注意:

MyISAM支援查詢與插入操作的併發進行,也可以通過系統變數concurrent_insert指定哪種模式。在MyISAM中預設:如果MyISAM表的中間沒有被刪除的行的話,那MyISAM是允許在一個程序讀表的同時,另一個程序從表尾做插入記錄的。但是INNODB是不支援的。

行鎖

InnoDB和MyISAM有兩個本質的區別:InnoDB支援行鎖、InnoDB支援事務。

InnoDB實現了以下兩種型別的行鎖:

  • 共享鎖(S鎖、讀鎖):允許一個事務去讀一行,阻止其他事務獲得相同資料集的排他鎖。即多個客戶可以同時讀取同一個資源,但不允許其他客戶修改。

  • 排他鎖(X鎖、寫鎖):允許獲得排他鎖的事務更新資料,阻止其他事務取得相同資料集的讀鎖和寫鎖。寫鎖是排他的,寫鎖會阻塞其他的寫鎖和讀鎖。

另外,為了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖:

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

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

  • 意向鎖也是資料庫隱式幫我們做了,不需要程式設計師關心!

MVCC行級鎖

MVCC(Multi-Version ConcurrencyControl)多版本併發控制,可以簡單地認為:MVCC就是行級鎖的一個變種(升級版)。

在表鎖中我們讀寫是阻塞的,基於提升併發效能的考慮,MVCC一般讀寫是不阻塞的(很多情況下避免了加鎖的操作)。

可以簡單的理解為:對資料庫的任何修改的提交都不會直接覆蓋之前的資料,而是產生一個新的版本與老版本共存,使得讀取時可以完全不加鎖。

事務的隔離級別

事務的隔離級別就是通過鎖的機制來實現,鎖的應用最終導致不同事務的隔離級別,只不過隱藏了加鎖細節,事務的隔離級別有4種:

  • Read uncommitted:會出現髒讀,不可重複讀,幻讀

  • Read committed:會出現不可重複讀,幻讀

  • Repeatable read:會出現幻讀(Mysql預設的隔離級別,但是Repeatable read配合gap鎖不會出現幻讀!)

  • Serializable:序列,避免以上的情況

Read uncommitted:出現的現象->髒讀:一個事務讀取到另外一個事務未提交的資料.

例子:A向B轉賬,A執行了轉賬語句,但A還沒有提交事務,B讀取資料,發現自己賬戶錢變多了!B跟A說,我已經收到錢了。A回滾事務【rollback】,等B再檢視賬戶的錢時,發現錢並沒有多...

Read committed:出現的現象->不可重複讀:一個事務讀取到另外一個事務已經提交的資料,也就是說一個事務可以看到其他事務所做的修改.

例如:A查詢資料庫得到資料,B去修改資料庫的資料,導致A多次查詢資料庫的結果都不一樣【危害:A每次查詢的結果都是受B的影響的,那麼A查詢出來的資訊就沒有意思了】

Repeatable read:避免不可重複讀是事務級別的快照!每次讀取的都是當前事務的版本,即使被修改了,也只會讀取當前事務版本的資料

虛讀(幻讀):是指在一個事務內讀取到了別的事務插入的資料,導致前後讀取不一致。和不可重複讀類似,但虛讀(幻讀)會讀到其他事務的插入的資料,導致前後讀取不 一致,幻讀的重點在於新增或者刪除(資料條數變化),不可重複讀的重點是修改.

樂觀鎖和悲觀鎖

無論是Read committed還是Repeatable read隔離級別,都是為了解決讀寫衝突的問題,現在考慮一個問題:有一張資料庫表USER,只有id、name欄位,現在有2個請求同時操作表A,過程如下:(模擬更新丟失,雖然不是很恰當)

  1. 操作1查詢出name="zhangsan"
  2. 操作2也查詢出name="zhangsan"
  3. 操作1把name欄位資料修改成lisi並提交
  4. 操作2把name欄位資料修改為wangwu並提交

那麼操作1的更新丟失啦,即一個事務的更新覆蓋了其它事務的更新結果,解決上述更新丟失的方式有如下3種:

  • 使用Serializable隔離級別,事務是序列執行的!
  • 樂觀鎖
  • 悲觀鎖

悲觀鎖

我們使用悲觀鎖的話其實很簡單(手動加行鎖就行了):select * from xxxx for update,在select 語句後邊加了for update相當於加了排它鎖(寫鎖),加了寫鎖以後,其他事務就不能對它修改了!需要等待當前事務修改完之後才可以修改.也就是說,如果操作1使用select ... for update,操作2就無法對該條記錄修改了,即可避免更新丟失。

樂觀鎖

樂觀鎖不是資料庫層面上的鎖,需要使用者手動去加的鎖。一般我們在資料庫表中新增一個版本欄位version來實現,例如操作1和操作2在更新User表的時,執行語句如下:

update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},

此時即可避免更新丟失。

間隙鎖GAP

當我們用範圍條件檢索資料而不是相等條件檢索資料,並請求共享或排他鎖時,InnoDB會給符合範圍條件的已有資料記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在 的記錄,叫做“間隙(GAP)”。

InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖。

例子:假如emp表中只有101條記錄,其empid的值分別是1,2,...,100,101

Select * from emp where empid > 100 for update;

上面是一個範圍查詢,InnoDB不僅會對符合條件的empid值為101的記錄加鎖,也會對empid大於101(這些記錄並不存在)的“間隙”加鎖

InnoDB使用間隙鎖的目的有2個:

  • 為了防止幻讀(上面也說了,Repeatable read隔離級別下再通過GAP鎖即可避免了幻讀)

  • 滿足恢復和複製的需要:MySQL的恢復機制要求在一個事務未提交前,其他併發事務不能插入滿足其鎖定條件的任何記錄,也就是不允許出現幻讀

死鎖

1、產生原因

所謂死鎖:是指兩個或兩個以上的程序在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去.此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序。表級鎖不會產生死鎖.所以解決死鎖主要還是針對於最常用的InnoDB。

死鎖的關鍵在於:兩個(或以上)的Session加鎖的順序不一致。

那麼對應的解決死鎖問題的關鍵就是:讓不同的session加鎖有次序

2、產生示例

案例

需求:將投資的錢拆成幾份隨機分配給借款人。

起初業務程式思路是這樣的:

投資人投資後,將金額隨機分為幾份,然後隨機從借款人表裡面選幾個,然後通過一條條select for update 去更新借款人表裡面的餘額等。

例如:兩個使用者同時投資,A使用者金額隨機分為2份,分給借款人1,2

B使用者金額隨機分為2份,分給借款人2,1,由於加鎖的順序不一樣,死鎖當然很快就出現了。

對於這個問題的改進很簡單,直接把所有分配到的借款人直接一次鎖住就行了。

Select*fromxxxwhereidin(xx,xx,xx)forupdate

在in裡面的列表值mysql是會自動從小到大排序,加鎖也是一條條從小到大加的鎖

鎖總結

表鎖其實我們程式設計師是很少關心它的:

  • 在MyISAM儲存引擎中,當執行SQL語句的時候是自動加的。

  • 在InnoDB儲存引擎中,如果沒有使用索引,表鎖也是自動加的。

現在我們大多數使用MySQL都是使用InnoDB,InnoDB支援行鎖:

  • 共享鎖->讀鎖->S鎖

  • 排它鎖->寫鎖->X鎖

在預設的情況下,select是不加任何行鎖的,事務可以通過以下語句顯示給記錄集加共享鎖或排他鎖。

  • 共享鎖(S):

  • SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
  • 排他鎖(X):

  • SELECT * FROM table_name WHERE ... FOR UPDATE
  • InnoDB基於行鎖還實現了MVCC多版本併發控制,MVCC在隔離級別下的Read committed和Repeatable read下工作。MVCC實現了讀寫不阻塞。