當前,Mysql數據庫使用的鎖機制有三種類型:行級鎖定,頁級鎖定和表級鎖定。
表級,直接鎖定整張表,在你鎖定期間,其它進程無法對該表進行寫操作。如果你是寫鎖,則其它進程則讀也不允許。
行級, 僅對指定的記錄進行加鎖,這樣其它進程還是可以對同一個表中的其它記錄進行操作。
頁級,表級鎖速度快,但沖突多,行級沖突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。
根據不同的存儲引擎,MySQL中鎖的特性可以大致歸納如下:
行鎖 | 表鎖 | 頁鎖 | |
MyISAM | √ | ||
BDB | √ | √ | |
InnoDB | √ | √ |
MyISAM表鎖
MySQL的表鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨占寫鎖(Table Write Lock)。
對MyISAM表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求;對MyISAM表的寫操作,則會阻塞其他用戶對同一表的讀和寫請求;MyISAM表的讀和寫操作之間,以及寫和寫操作之間是串行的!(當一線程獲得對一個表的寫鎖后,只有持有鎖的線程可以對表進行更新操作。其他線程的讀、寫操作都會等待,直到鎖被釋放為止。)
如何加表鎖
InnoDB鎖
InnoDB與MyISAM的最大不同有兩點:一是支持事務(TRANSACTION);二是采用了行級鎖。
- 原子性(Atomicity):事務是一個原子操作單元,其對數據的修改,要么全都執行,要么全都不執行。
- 一致性(Consistent):在事務開始和完成時,數據都必須保持一致狀態。這意味着所有相關的數據規則都必須應用於事務的修改,以保持數據的完整性;事務結束時,所有的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的。
- 隔離性(Isolation):數據庫系統提供一定的隔離機制,保證事務在不受外部並發操作影響的“獨立”環境執行。這意味着事務處理過程中的中間狀態對外部是不可見的,反之亦然。
- 持久性(Durable):事務完成之后,它對於數據的修改是永久性的,即使出現系統故障也能夠保持。
2.並發事務處理帶來的問題。在數據庫中,當不止一個查詢同時修改數據時,會產生並發控制問題--並發讀和並發寫。主要有以下幾種:
1. 丟失更新。
2. 臟讀。
3. 不可重復讀。
4. 幻像讀。
解決並發問題的手段就是鎖機制,對正在操作的數據加鎖,限制其他事務對該記錄進行讀或寫。
分為讀鎖和寫鎖:
讀鎖是共享的,在同一時間,多個用戶可以讀取同一資源,而互不干擾。
寫鎖是排他的,一個寫鎖會阻塞其他的讀鎖和寫鎖,在給定時間里,只有一個用戶能寫入資源,以防止用戶在寫操作的同時其他用戶讀取同一資源。
各種並發問題加鎖的策略是不同的,具體分析如下:
1:丟失更新。
e.g.事務A和事務B同時修改某行的值,
- 事務A將數值改為1並提交
- 事務B將數值改為2並提交。(這時數據的值為2,事務A所做的更新將會丟失。)
解決方法是加鎖,有悲觀鎖和樂觀鎖兩種:(都是行級鎖)
1:悲觀鎖。
在包含修改操作的事務中,先使用select ……for update nowait進行查詢, 通過添加for update nowait語句,將這條記錄鎖住,避免其他用戶更新,從而保證后續的更新是在正確的狀態下更新的。然后在保持這個鏈接的狀態下,再做修改操作,最后提交事務。
2:樂觀鎖(版本法)
在表上加一個屬性列作為版本列,其數值表示最新的版本號,這一列的數據類型可以是NUMBER或 DATE/TIMESTAMP,用來記錄這條數據的版本。在應用程序中我們每次操作對版本列做修改即可。在更新時我們把上次版本作為條件進行更新。在對一行進行更新的時候 限制條件=主鍵+版本號,同時對記錄的版本號進行更新。
e.g.
update table set version = old_verison + 1 ,new_value = old_value+1 where primary_key = ? and version = old_version
2:臟讀。
當一個事務讀取另一個事務尚未提交的修改時,產生臟讀。
e.g.
1.Mary的原工資為1000, 財務人員將Mary的工資改為了8000(但未提交事務)
2.Mary讀取自己的工資 ,發現自己的工資變為了8000,歡天喜地!
3.而財務發現操作有誤,回滾了事務,Mary的工資又變為了1000
像這樣,Mary記取的工資數8000是一個臟數據。
解決辦法:如果在第一個事務提交前,對數據進行加鎖,任何其他事務不可讀取其修改過的值,則可以避免該問題。
3:不可重復讀。
同一查詢在同一事務中多次進行,由於其他提交事務所做的修改或刪除,每次返回不同的結果集,此時發生非重復讀。e.g.
- 在事務1中,Mary 讀取了自己的工資為1000,操作並沒有完成
- 在事務2中,這時財務人員修改了Mary的工資為2000,並提交了事務.
- 在事務1中,Mary 再次讀取自己的工資時,工資變為了2000
解決辦法:如果只有在修改事務完全提交之后才可以讀取數據,則可以避免該問題。
4:幻象讀。
同一查詢在同一事務中多次進行,由於其他提交事務所做的插入、修改操作,每次返回不同的結果集,此時發生幻像讀。當對某行執行插入或刪除操作,而該行屬於某個事務正在讀取的行的范圍時,會發生幻像讀問題。事務第一次讀的行范圍顯示出其中一行已不復存在於第二次讀或后續讀中,因為該行已被其它事務刪除。同樣,由於其它事務的插入操作,事務的第二次或后續讀顯示有一行已不存在於原始讀中。
【區分臟讀:臟讀是由於多次讀取同一條數據,而該數據被其他事務修改而導致多次讀取數據結果不同。
幻象讀:則是由於根據某一條件進行多次查詢時,其他事務插入或刪除了符合條件的一些記錄,導致多次查詢返回的記錄數不同。】
e.g.目前工資為1000的員工有10人。
- 事務1,讀取所有工資為1000的員工。
- 這時事務2向employee表插入了一條員工記錄,工資也為1000。
- 事務1再次讀取所有工資為1000的員工 共讀取到了11條記錄。
解決辦法:如果在操作事務完成數據處理之前(插入、刪除),任何其他事務都不可以添加新數據,則可避免該問題。
- 第一種情況是:事務需要更新大部分或全部數據,表又比較大,如果使用默認的行鎖,不僅這個事務執行效率低,而且可能造成其他事務長時間鎖等待和鎖沖突,這種情況下可以考慮使用表鎖來提高該事務的執行速度。
- 第二種情況是:事務涉及多個表,比較復雜,很可能引起死鎖,造成大量事務回滾。這種情況也可以考慮一次性鎖定事務涉及的表,從而避免死鎖、減少數據庫因事務回滾帶來的開銷。