1. 程式人生 > >使用mysql中的鎖解決高併發問題

使用mysql中的鎖解決高併發問題

為什麼要加鎖

多核計算機的出現,計算機實現真正平行計算,可以在同一時刻,執行多個任務。在多執行緒程式設計中,因為執行緒執行順序不可控導致的資料錯誤。比如,多執行緒的理想狀態是這樣的多執行緒理想.jpg
但是實際情況是這樣的:

多執行緒實際.jpg
在網路程式設計中,在同一時刻,多個客戶端同時請求同一個資源,如果不做控制,也會帶來資料錯誤。比如在同一時間有10000人去搶10張火車票,10張火車票有可能會買給100個人,這顯然是不符合要求的。
在多執行緒程式設計中,為了解決執行緒執行不可控帶來的問題,通常情況下都是通過加鎖來實現資料同步的。在網路程式設計中,也可以通過加鎖機制來控制。

在網路程式設計中,可以通過給資料庫加鎖,達到控制併發的目的。在php開發時,基本都是使用mysql作為資料庫。所以,就會給mysql加鎖控制網路併發引起資料錯誤問題。

MySQL的儲存引擎

不是要說MySQL的鎖嗎,怎麼說上儲存引擎了?因為MySQL儲存引擎不同,鎖也會不同。MySQL有MyISAMInnoDB兩種儲存引擎,現在主要使用InnoDB,所以主要介紹InnoDB下鎖的使用。

InnoDB引擎支援事務操作,使用事務可以保證多條sql語句執行的完整性(要不都成功,要不都失敗)

事務是由一組SQL語句組成的邏輯處理單元,事務具有4屬性
  • 原性性(Actomicity):事務是一個原子操作單元,其對資料的修改,要麼全都執行,要麼全都不執行。
  • 一致性(Consistent):在事務開始和完成時,資料都必須保持一致狀態。這意味著所有相關的資料規則都必須應用於事務的修改,以操持完整性;事務結束時,所有的內部資料結構(如B樹索引或雙向連結串列)也都必須是正確的。
  • 隔離性(Isolation):資料庫系統提供一定的隔離機制,保證事務在不受外部併發操作影響的“獨立”環境執行。這意味著事務處理過程中的中間狀態對外部是不可見的,反之亦然。
  • 永續性(Durable):事務完成之後,它對於資料的修改是永久性的,即使出現系統故障也能夠保持。
多個事務併發執行會帶來新的問題
  • 更新丟失(Lost Update):當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,由於每個 事務都不知道其他事務的存在,就會發生丟失更新問題——最後的更新覆蓋了其他事務所做的更新。例如,兩個編輯人員製作了同一文件的電子副本。每個編輯人員獨立地更改其副本,然後儲存更改後的副本,這樣就覆蓋了原始文件。最後儲存其更改儲存其更改副本的編輯人員覆蓋另一個編輯人員所做的修改。如果在一個編輯人員完成並提交事務之前,另一個編輯人員不能訪問同一檔案,則可避免此問題
  • 髒讀(Dirty Reads):一個事務正在對一條記錄做修改,在這個事務並提交前,這條記錄的資料就處於不一致狀態;這時,另一個事務也來讀取同一條記錄,如果不加控制,第二個事務讀取了這些“髒”的資料,並據此做進一步的處理,就會產生未提交的資料依賴關係。這種現象被形象地叫做“髒讀”。
  • 不可重複讀(Non-Repeatable Reads):一個事務在讀取某些資料已經發生了改變、或某些記錄已經被刪除了!這種現象叫做“不可重複讀”。
  • 幻讀(Phantom Reads):一個事務按相同的查詢條件重新讀取以前檢索過的資料,卻發現其他事務插入了滿足其查詢條件的新資料,這種現象就稱為“幻讀”。

曾經年少無知的我,以為使用事務就能保證併發情況下資料同步問題,後來的一次慘痛經歷才明白了,事務不能保證併發情況的資料同步問題,需要事務和鎖同時使用才能保證。

鎖的種類

  • 樂觀鎖 機制採取了更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠資料庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是資料庫效能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。相對悲觀鎖而言,樂觀鎖更傾向於開發運用。
  • 悲觀鎖 具有強烈的獨佔和排他特性。它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。

MySQL中鎖的種類

樂觀鎖和悲觀鎖是一種思想,不是具體實現,在MySQL中,有鎖的具體的實現方式

(文中的執行緒在MySQL中可以視作MySQL的連線)

  • 共享鎖 一個執行緒在持有鎖時,其他的執行緒可以查詢被鎖的資料,但是不能修改,不能刪除。實現方式
    SELECT * FROM table_name WHERE id =? lock in share mode;
  • 排它鎖 一個執行緒在持有鎖時,其他的執行緒不能查詢,不能更新,不能刪除被鎖的資料,直到鎖被釋放.
    SELECT * FROM table_name WHERE id =? for update
  • 總結一下:共享鎖類似於java中的讀鎖,一個執行緒在持有樂觀鎖的時候,其他的執行緒也可以對被鎖的資料進行讀操作,但是不能對被鎖的資料進行刪除和更新操作;排他鎖類似於java的寫鎖,一個執行緒持有寫鎖的時候,其他的執行緒不能再對被鎖的資料進行任何查詢,更新,刪除操作。
  • 重點 InnoDB的行鎖是基於索引實現的,如果在查詢中不使用索引,會鎖表。

MySQL鎖粒度

  • 表級鎖 是MySQL中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支援。最常使用的MyISAM與InnoDB都支援表級鎖定。表級鎖分為表共享讀鎖與表獨佔寫鎖。
  • 行級鎖 是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。行級鎖能大大減少資料庫操作的衝突。其加鎖粒度最小,但加鎖的開銷也最大。行級鎖分為共享鎖 和 排他鎖。

共享鎖的使用

注意: 下面的操作,都是行鎖操作,MySQL為InnoDB引擎,id為自增主鍵

先建立一個測試表

CREATE TABLE `test`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `number` bigint(0) NOT NULL,
  `age` int(0) NULL,
  PRIMARY KEY (`id`)
) ENGINE = InnoDB;

看一下隨便插入的幾條資料

沒有問題,使用共享鎖看一下效果:

  1. 開啟事務 begin;
  2. 給id為1的資料加共享鎖 mysql> select * from test where id=1 lock in share mode;
  3. 分別使用加鎖和不加鎖查詢id為1 的資料

都可以查詢到資料

修改id為1 的資料看看

sql語句會一直停在這裡,直到超時或者鎖釋放(事務提交或者回滾)

左邊的事務提交後,右邊的sql會執行,完成更新操作。

同樣,刪除操作也會等待鎖釋放才能操作,這裡就不演示了。


再看一下另一種情況,左邊鎖住id為5的資料,右邊更新id為1 的資料,不受影響。這就是行級鎖,只會鎖住相關的一行資料

排他鎖的使用

還是使用test這張表

  1. 開啟事務begin;
  2. 給id1的資料加排他鎖select * from test where id = 1 for update;
  3. 在右邊查詢id為1的資料


查詢語句會一直等待,直到超時或者鎖釋放(左邊commit或者rollback)


左邊commit後

使用排它鎖對id為5的資料加鎖後,更新id為5的資料

sql語句同樣會等待,直到超時或者鎖釋放,刪除操作也是一樣

看一下對id為1的資料加鎖,然後操作id不為1的資料的情況


沒有問題,MySQL只是鎖住了id為5的資料,其他的資料都可以操作。

看一下InnoDB引擎鎖表的情況


我們常常說InnoDB是行鎖,但是這裡介紹一下它鎖表的情況。因為name列沒有索引,所以,在加行鎖的時候,MySQL不能加正常加行鎖,會鎖住整張表。

InnoDB行鎖是通過索引上的索引項來實現的,這一點MySQL與Oracle不同,後者是通過在資料中對相應資料行加鎖來實現的。InnoDB這種行鎖實現特點意味者:只有通過索引條件檢索資料,InnoDB才會使用行級鎖,否則,InnoDB將使用表鎖!
在實際應用中,要特別注意InnoDB行鎖的這一特性,不然的話,可能導致大量的鎖衝突,從而影響併發效能。

再看另一種情況


使用排它鎖對id為1的資料加鎖時,使用不加鎖的查詢和沒有約束的查詢時,一樣可以立刻查詢到資料。只有使用加鎖的查詢或者更新和刪除時才會等待鎖釋放。

總結

  • InnoDB的鎖配合事務使用
  • MySQL有共享鎖和排它鎖
  • 使用共享鎖時,其他執行緒(連線)可以查詢資料,但是不能更新和刪除資料,使用排它鎖時,不能查詢資料不能更新資料,不能刪除資料
  • MySQL的InnoDB引擎支援行級鎖和表級鎖,行級鎖
  • InnoDB的行級鎖是基於索引的,加鎖是對索引加鎖,加鎖時沒有索引時會鎖住整張表

以上是我對MySQL鎖的理解,文中如果有不正確的地方,還請各位大哥批評指正。