【MySQL】并發(fā)控制

無論何時,只有有多個查詢需要在同一時刻修改數(shù)據(jù),都會產(chǎn)生并發(fā)控制的問題。這里討論mysql在兩個層面的并發(fā)控制:服務器層與存儲引擎層。并發(fā)控制是一個內(nèi)容龐大的話題,有大量的理論文獻對其進行詳細的論述。在此只是簡要地討論mysql如何控制并發(fā)讀寫。

以unix系統(tǒng)的email box為例子,典型的mbox文件格式是非常簡單的。一個mbox郵箱中的所有郵件都串行在一起,彼此首尾相連。這種格式對于讀取和肥西郵件信息非常友好,同時投遞郵件也很容易,只要在文件末尾附加新的郵件內(nèi)容即可。

但是如果兩個進程在同一時刻對同一個郵箱投遞郵件,會發(fā)生什么情況?顯然,郵箱的數(shù)據(jù)會被破壞,兩封郵件的內(nèi)容會交叉地附加在郵箱文件的末尾。設計娘好的郵箱投遞系統(tǒng)會通過鎖(lock)來防止數(shù)據(jù)損壞。如果客戶試圖投遞郵件,而郵箱已經(jīng)被其他客戶鎖住,那么就必須等待,直到鎖釋放才能進行投遞。

這種鎖的方案在實際應用環(huán)境中雖然工作良好,但是并不支持并發(fā)處理。因為在任意一個時刻,只有一個進程可以修改郵箱的數(shù)據(jù),這在大容量的郵箱系統(tǒng)中是個問題。

讀寫鎖

從郵箱中讀取數(shù)據(jù)沒有這樣的麻煩,即使同一時刻多個用戶并發(fā)讀取也不會有什么問題。因為讀取不會修改數(shù)據(jù),所以不會出錯。但是如果某個客戶正在讀取郵箱,同時另一個用戶試圖刪除編號為25的郵件,會產(chǎn)生什么結果?結論是不確定的,讀的客戶可能會報錯退出,也可能讀取不到一致的郵箱數(shù)據(jù)。所以,為了安全起見,即使是讀取郵箱也需要特別注意。

如果把上述的郵箱當成數(shù)據(jù)庫中的一張表,把郵件當成表中的一行記錄,就很容易看出,同樣的問題依然存在。從很多方面來說,郵箱就是一張簡單的數(shù)據(jù)庫表。修改數(shù)據(jù)庫表中的記錄,和刪除或者修改郵箱中的郵件信息,十分類似。

解決這類經(jīng)典的問題的方法就是并發(fā)控制(讀鎖和寫鎖)。其實非常簡單,在處理并發(fā)讀或者寫的時候,可以通過實現(xiàn)一個由兩種類型的鎖組成鎖系統(tǒng)來解決問題。這兩種類型的鎖通常被稱為共享鎖(shared lock)排他鎖(exclusive lock),也叫讀鎖(read lock)寫鎖(write lock)

這里先不討論如何具體實現(xiàn),描述一下鎖的概念如下:讀鎖是共享的,或者說是相互不阻塞的。多個客戶在同一時刻可以同時讀取同一資源,而互不干擾。寫鎖則是排他的,也就是說一個寫鎖會阻塞其他的寫鎖和讀鎖,這是出于安全策略的考慮,只有這樣,才能確保給定的時間里,只有一個用戶能執(zhí)行寫入,并防止其他用戶讀取正在寫入的同一資源。

在實際的數(shù)據(jù)庫系統(tǒng)中,每時每刻都在發(fā)生鎖定,當某個用戶在修改某一部分數(shù)據(jù)的時候,MySQL會通過鎖定防止其他用戶讀取同一數(shù)據(jù)。大多數(shù)時候,MySQL鎖的內(nèi)部管理都是透明的。

鎖粒度

一種提供共享資源并發(fā)性的方式就是讓鎖定對象更有選擇性。盡量只鎖定需要修改的部分數(shù)據(jù),而不是所有的資源。更理想的方式是,只對會修改的數(shù)據(jù)片(具體到鎖定所修改的字段)進行精確的鎖定。任何時候,在給定的資源上,鎖定的數(shù)據(jù)量越少,則系統(tǒng)的并發(fā)成都越高,只要互相之間不發(fā)生沖突即可。

問題是加鎖也需要消耗資源。鎖的各種操作,包括獲得鎖,檢查鎖是否已經(jīng)被解除,釋放鎖等,都會增加系統(tǒng)的開銷。如果系統(tǒng)花費大量的時間來管理鎖,而不是存取數(shù)據(jù),那么系統(tǒng)的性能可能因此受到影響。

所謂的鎖策略,就是在鎖的開銷和數(shù)據(jù)的安全性之間尋求平衡,這種平衡當然有會影響到性能,大多數(shù)商業(yè)數(shù)據(jù)庫系統(tǒng)沒有提供更多的選擇,一般都是在表上施加行級鎖(row-level lock),并以各種復雜的方式來實現(xiàn),以便在鎖比較多的情況下盡可能地提供更好的性能。

而MySQL則提供多種選擇,每種MySQL存儲引擎都可以實現(xiàn)自己的鎖策略和鎖粒度。在存儲引擎的設計中,鎖管理是個非常重要的決定。將鎖粒度固定在某個級別,可以為某些特定的應用場景提供更好的性能。但是同事卻會失去對另外一些應用場景的良好支持。好在Mysql支持多個存儲引擎的架構,所以不需要單一的通用解決方案。下面介紹兩種最重要的鎖策略。

表鎖(table lock)

表鎖是MySQL中最基本的鎖策略,并且是開銷最小的策略。表鎖非常類似于前文描述的郵箱加鎖機制:它會鎖定整張表。一個用戶在對表進行寫操作(插入,刪除,更新等等)前,需要先獲得寫鎖,這個會阻塞其他用戶對該表的所有讀寫操作。只有沒有寫鎖的時候,其他讀取的用戶才能獲得讀鎖,讀鎖之間是不互相阻塞的。

在特定的場景中,表鎖也可能有良好的性能。例如,read local表鎖支持某些類型的并發(fā)寫操作。另外,寫鎖也比讀鎖有更高的優(yōu)先級,因此一個寫鎖請求可能會被插入到讀鎖隊列的前面(寫鎖可以插入到鎖隊列中讀鎖的前面,反之讀鎖則不能插入寫鎖的前面)。

盡管存儲引擎可以管理自己的鎖,Mysql本身還是會使用各種有效的表鎖來實現(xiàn)不同的目的。例如服務器會諸如alter table之類的語句使用表鎖,而忽略存儲引擎的鎖機制。

【備注:鎖機制是存儲引擎管理的,但是MySQL本身也會有時候強制管理這個鎖機制】

行級鎖(row lock)

行級鎖可以最大程度地支持并發(fā)處理(同時也帶來了最大的鎖開銷)。眾多周知,在InnoDB和XtraDB,以及其他一些存儲引擎中實現(xiàn)了行級鎖。行級鎖只在存儲引擎層實現(xiàn),而MySQL服務器層(如果有必要,請回顧上篇的邏輯架構圖)沒有實現(xiàn)。服務器層完全不了解存儲引擎中的鎖實現(xiàn)。在本章的后續(xù)內(nèi)容以及全書中,所有的存儲引擎都以自己的方式顯示了鎖機制

無論何時,只有有多個查詢需要在同一時刻修改數(shù)據(jù),都會產(chǎn)生并發(fā)控制的問題。這里討論mysql在兩個層面的并發(fā)控制:服務器層與存儲引擎層。并發(fā)控制是一個內(nèi)容龐大的話題,有大量的理論文獻對其進行詳細的論述。在此只是簡要地討論mysql如何控制并發(fā)讀寫。

以unix系統(tǒng)的email box為例子,典型的mbox文件格式是非常簡單的。一個mbox郵箱中的所有郵件都串行在一起,彼此首尾相連。這種格式對于讀取和肥西郵件信息非常友好,同時投遞郵件也很容易,只要在文件末尾附加新的郵件內(nèi)容即可。

但是如果兩個進程在同一時刻對同一個郵箱投遞郵件,會發(fā)生什么情況?顯然,郵箱的數(shù)據(jù)會被破壞,兩封郵件的內(nèi)容會交叉地附加在郵箱文件的末尾。設計娘好的郵箱投遞系統(tǒng)會通過鎖(lock)來防止數(shù)據(jù)損壞。如果客戶試圖投遞郵件,而郵箱已經(jīng)被其他客戶鎖住,那么就必須等待,直到鎖釋放才能進行投遞。

這種鎖的方案在實際應用環(huán)境中雖然工作良好,但是并不支持并發(fā)處理。因為在任意一個時刻,只有一個進程可以修改郵箱的數(shù)據(jù),這在大容量的郵箱系統(tǒng)中是個問題。

讀寫鎖

從郵箱中讀取數(shù)據(jù)沒有這樣的麻煩,即使同一時刻多個用戶并發(fā)讀取也不會有什么問題。因為讀取不會修改數(shù)據(jù),所以不會出錯。但是如果某個客戶正在讀取郵箱,同時另一個用戶試圖刪除編號為25的郵件,會產(chǎn)生什么結果?結論是不確定的,讀的客戶可能會報錯退出,也可能讀取不到一致的郵箱數(shù)據(jù)。所以,為了安全起見,即使是讀取郵箱也需要特別注意。

如果把上述的郵箱當成數(shù)據(jù)庫中的一張表,把郵件當成表中的一行記錄,就很容易看出,同樣的問題依然存在。從很多方面來說,郵箱就是一張簡單的數(shù)據(jù)庫表。修改數(shù)據(jù)庫表中的記錄,和刪除或者修改郵箱中的郵件信息,十分類似。

解決這類經(jīng)典的問題的方法就是并發(fā)控制(讀鎖和寫鎖)。其實非常簡單,在處理并發(fā)讀或者寫的時候,可以通過實現(xiàn)一個由兩種類型的鎖組成鎖系統(tǒng)來解決問題。這兩種類型的鎖通常被稱為共享鎖(shared lock)排他鎖(exclusive lock),也叫讀鎖(read lock)寫鎖(write lock)

這里先不討論如何具體實現(xiàn),描述一下鎖的概念如下:讀鎖是共享的,或者說是相互不阻塞的。多個客戶在同一時刻可以同時讀取同一資源,而互不干擾。寫鎖則是排他的,也就是說一個寫鎖會阻塞其他的寫鎖和讀鎖,這是出于安全策略的考慮,只有這樣,才能確保給定的時間里,只有一個用戶能執(zhí)行寫入,并防止其他用戶讀取正在寫入的同一資源。

在實際的數(shù)據(jù)庫系統(tǒng)中,每時每刻都在發(fā)生鎖定,當某個用戶在修改某一部分數(shù)據(jù)的時候,MySQL會通過鎖定防止其他用戶讀取同一數(shù)據(jù)。大多數(shù)時候,MySQL鎖的內(nèi)部管理都是透明的。

鎖粒度

一種提供共享資源并發(fā)性的方式就是讓鎖定對象更有選擇性。盡量只鎖定需要修改的部分數(shù)據(jù),而不是所有的資源。更理想的方式是,只對會修改的數(shù)據(jù)片(具體到鎖定所修改的字段)進行精確的鎖定。任何時候,在給定的資源上,鎖定的數(shù)據(jù)量越少,則系統(tǒng)的并發(fā)成都越高,只要互相之間不發(fā)生沖突即可。

問題是加鎖也需要消耗資源。鎖的各種操作,包括獲得鎖,檢查鎖是否已經(jīng)被解除,釋放鎖等,都會增加系統(tǒng)的開銷。如果系統(tǒng)花費大量的時間來管理鎖,而不是存取數(shù)據(jù),那么系統(tǒng)的性能可能因此受到影響。

所謂的鎖策略,就是在鎖的開銷和數(shù)據(jù)的安全性之間尋求平衡,這種平衡當然有會影響到性能,大多數(shù)商業(yè)數(shù)據(jù)庫系統(tǒng)沒有提供更多的選擇,一般都是在表上施加行級鎖(row-level lock),并以各種復雜的方式來實現(xiàn),以便在鎖比較多的情況下盡可能地提供更好的性能。

而MySQL則提供多種選擇,每種MySQL存儲引擎都可以實現(xiàn)自己的鎖策略和鎖粒度。在存儲引擎的設計中,鎖管理是個非常重要的決定。將鎖粒度固定在某個級別,可以為某些特定的應用場景提供更好的性能。但是同事卻會失去對另外一些應用場景的良好支持。好在Mysql支持多個存儲引擎的架構,所以不需要單一的通用解決方案。下面介紹兩種最重要的鎖策略。

表鎖(table lock)

表鎖是MySQL中最基本的鎖策略,并且是開銷最小的策略。表鎖非常類似于前文描述的郵箱加鎖機制:它會鎖定整張表。一個用戶在對表進行寫操作(插入,刪除,更新等等)前,需要先獲得寫鎖,這個會阻塞其他用戶對該表的所有讀寫操作。只有沒有寫鎖的時候,其他讀取的用戶才能獲得讀鎖,讀鎖之間是不互相阻塞的。

在特定的場景中,表鎖也可能有良好的性能。例如,read local表鎖支持某些類型的并發(fā)寫操作。另外,寫鎖也比讀鎖有更高的優(yōu)先級,因此一個寫鎖請求可能會被插入到讀鎖隊列的前面(寫鎖可以插入到鎖隊列中讀鎖的前面,反之讀鎖則不能插入寫鎖的前面)。

盡管存儲引擎可以管理自己的鎖,Mysql本身還是會使用各種有效的表鎖來實現(xiàn)不同的目的。例如服務器會諸如alter table之類的語句使用表鎖,而忽略存儲引擎的鎖機制。

【備注:鎖機制是存儲引擎管理的,但是MySQL本身也會有時候強制管理這個鎖機制】

行級鎖(row lock)

行級鎖可以最大程度地支持并發(fā)處理(同時也帶來了最大的鎖開銷)。眾多周知,在InnoDB和XtraDB,以及其他一些存儲引擎中實現(xiàn)了行級鎖。行級鎖只在存儲引擎層實現(xiàn),而MySQL服務器層(如果有必要,請回顧上篇的邏輯架構圖)沒有實現(xiàn)。服務器層完全不了解存儲引擎中的鎖實現(xiàn)。在本章的后續(xù)內(nèi)容以及全書中,所有的存儲引擎都以自己的方式顯示了鎖機制

?以上就是【MySQL】并發(fā)控制的內(nèi)容,更多相關內(nèi)容請關注PHP中文網(wǎng)(www.php.cn)!

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