您好,登錄后才能下訂單哦!
事務的必要性
為了避免出現數據不一致問題,需要在存儲過程中引入事務的概念,將更新語句綁在一起,讓它們成為一個”原子性”的操作:更新語句要么都執行,要么都不執行。
一、關閉MySQL自動提交的方法
1、顯示關閉自動提交
set autocommit=0;1
2、隱式關閉自動提交:(推薦)
start transaction1
一般推薦使用隱式的提交方式,因為不會修改到autocommit系統變量。
二、關閉自動提交后,提交更新語句的方法有
1.顯示地提交:(推薦)
commit;1
2.隱式地提交
包括了begin、set autocommit=1、start transaction、rename table、truncate table等語句;
數據定義(create、alter、drop)function、alter procedure、drop database、drop table、drop function、drop index、drop procedure等;
權限管理和賬戶管理語句(grant、revoke、set password、create user、drop user、rename user)
鎖語句(lock tables、unlock tables)
更推薦用顯示提交地方式。
三、事務處理使用方法
在處理錯誤代碼地程序中使用rollback(事務回滾)。
在具有原子性操作的地方,使用start transaction隱式地關閉自動提交,并且在結束的為止上使用commit顯示提交。
四、事務保存點的使用方法
在事務中使用savepoint 保存點名 的格式創建保存點,實現事務的”部分”提交或”部分”撤銷(rollback to savepoint 保存點名)。
保存點是”臨時狀態”,既可以回滾到事務開始前的狀態,也能決定事務的下一個狀態,是介于兩自動提交語句所引發狀態中的一種臨時狀態。
五、鎖機制的必要性
內存中的數據與外存中的數據不同步,其中的表記錄之間存在“同步延遲“。
六、MyISAM表施加表級鎖的語法格式
lock tables 表1 [as 別名] read [local]
[,表2[ as 別名2][low_priority] write] ...12
其中read local與read選項的差別為 READ LOCAL允許在鎖定被保持時,執行非沖突性INSERT語句(同時插入)。
七、鎖的粒度、隱式鎖與顯示鎖、鎖的類型、鎖的鑰匙、鎖的生命周期
1.鎖的粒度指鎖的作用范圍:
多個MySQL客戶機并發訪問同一個數據時,為保證數據的一致性,數據庫管理系統會自動地為該數據加鎖、解鎖,這種是隱式鎖。
而有時單靠隱式鎖無法實現數據的一致性訪問要求(例如對于臨界資源的爭奪上),此時需要手動地加鎖、解鎖,這種稱為顯示鎖。
2.鎖的類型分為:讀鎖(共享鎖)和寫鎖(排他鎖或獨占鎖)
3.鎖的鑰匙: 當多個MySQL客戶機并發訪問同一個數據時、如果MySQL客戶機A對該數據成功地施加了鎖,那么只有MySQL客戶機A擁有這把鎖的鑰匙。
4.鎖的生命周期:在同一個MySQL會話內,對數據加鎖到解鎖之間的時間間隔。鎖的生命周期越長,并發訪問性能越低;鎖的生命周期越短,并發訪問性能越高。
八、InnoDB表施加行級鎖的語法格式
共享鎖 select * from 表 where 條件語句 lock in share mode;
排他鎖 select * from 表 where 條件語句 for update;
九、InnoDB中的間隙鎖、記錄鎖
當檢索條件滿足某區間范圍,但表中不存在的記錄,此時也有共享鎖或排他鎖,即行級鎖會鎖定相鄰的鍵,這種機制就是間隙鎖(next-key鎖)
當事務隔離級別設置為repeatable read,此時InnoDB表施加行級鎖,默認使用間隔鎖(需要索引),
當事務的隔離級別設置為read uncommited或者read commited,此時InnoDB表施加行級鎖,默認情況下使用記錄鎖(record lock)。
與間隙鎖不同,記錄鎖僅僅為滿足該查詢范圍的記錄施加共享鎖或排他鎖。
十、鎖等待與死鎖
一)鎖等待:是為了保證事務可以正常地并發運行,鎖等待不一定導致死鎖問題的發生。而死鎖問題的發生一定伴隨著鎖等待現象。
二)死鎖:
1、定義: 是指兩個或兩個以上的進程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。當線程進入對象的synchronized代碼塊時,便占有了資源,直到它退出該代碼塊或者調用wait方法,才釋放資源,在此期間,其他線程將不能進入該代碼塊。當線程互相持有對方所需要的資源時,會互相等待對方釋放資源,如果線程都不主動釋放所占有的資源,將產生死鎖。
2、死鎖的產生4個必要條件:
1)互斥條件:進程對于所分配到的資源具有排它性,即一個資源只能被一個進程占用,直到被該進程釋放
2)占有且等待:一個進程因請求被占用資源而發生阻塞時,對已獲得的資源保持不放。
3)不可搶占條件:任何一個資源在沒被該進程釋放之前,任何其他進程都無法對他剝奪占用
4)循環等待條件:當發生死鎖時,所等待的進程必定會形成一個環路(類似于死循環),造成永久阻塞。
3、死鎖的另一種:遞歸死鎖,舉例:
遞歸函數就是自調用函數,在函數體內直接或間接的調用自己,即函數的嵌套是函數本身。
遞歸方式有兩種:直接遞歸和間接遞歸,直接遞歸就是在函數中出現調用函數本身。間接遞歸,指函數中調用了其他函數,而該其他函數又調用了本函數。
那什么時候使用遞歸呢?一般來說當你要在某段代碼邏輯中使用循環迭代的時候但是迭代的次數在迭代之前無法知曉的情況下使用遞歸。打個比方你要在一個文件夾中查找某個文件,而這個文件夾底下有N多子文件夾和文件,當你在不知道有多少層文件夾和文件的情況下你就得用到遞歸了。
遞歸的優點就是讓代碼顯得很簡潔,同時有些應用場景不得不使用遞歸比如前面說的找文件。遞歸是個好東西但是在某些時候也會給你帶來一些麻煩。比如在多線程的環境下使用遞歸,遇到了多線程那么就不得不面對同步的問題。而遞歸程序遇到同步的時候很容易出問題。
多線程的遞歸就是指遞歸鏈中的某個方法由另外一個線程來操作,以下代碼的意思都是這個意思即調用recursive()和businessLogic()并非一個線程(如果是在一個線程中就不存在死鎖問題,例如下面的recursive變成private就不存在問題。)
[java] view plain copy
public class Test {
public void recursive(){
this.businessLogic();
}
public synchronized void businessLogic(){
System.out.println("處理業務邏輯");
System.out.println("保存到<a href="http://lib.csdn.net/base/mysql" class='replace_word' title="MySQL知識庫" target='_blank' style='color:#df3434; font-weight:bold;'>數據庫</a>");
this.recursive();
}
}
以上這段代碼就是個能形成死鎖的代碼,事實上這個“synchronized”放在“businessLogic()”和“recursive()”都會形成死鎖,并且是多線程的情況下就會鎖住!他的邏輯順序是先執行recursive()方法然后接下來執行businessLogic()方法同時將businessLogic()方法鎖住,接下來程序進入businessLogic()方法內部執行完打印語句后開始執行recursive(),進入recursive()后準備執行businessLogic(),等等問題來了!之前執行的businessLogic()的鎖還沒有放開這次又執行到這里了,當然是過不去的了,形成了死鎖!從這個例子我們總結出來一個規律就是在遞歸的時候在遞歸鏈上面的方法上加鎖肯定會出現死鎖(所謂遞歸鏈就是指recursive()鏈向businessLogic(),businessLogic()又鏈回recursive()),解決這個問題的方法就是避免在遞歸鏈上加鎖,請看以下的例子
[java] view plain copy
public class Test {
public void recursive(){
this.businessLogic();
}
public void businessLogic(){
System.out.println("處理業務邏輯");
this.saveToDB();
this.recursive();
}
public synchronized void saveToDB(){
System.out.println("保存到數據庫");
}
}
saveToDB()不在這條遞歸鏈上面自然不會出現死鎖,所以說在遞歸中加鎖是件很危險的事情,實在逃不過要加鎖就加在最小的粒度的程序代碼上以減小死鎖的概率。
4、處理死鎖的方法
4.1 預防死鎖
4.2 避免死鎖
4.2.1 常用避免死鎖的方法
4.2.1.1 有序資源分配法
4.2.1.2 銀行家算法
4.2.2 常用避免死鎖的技術
4.2.2.1 加鎖順序
4.2.2.2 加鎖時限
4.2.2.3 死鎖檢測
4.3 檢測死鎖
4.4 解除死鎖
處理死鎖的方法
4.1、死鎖預防 ----- 確保系統永遠不會進入死鎖狀態 產生死鎖需要四個條件,那么,只要這四個條件中至少有一個條件得不到滿足,就不可能發生死鎖了。由于互斥條件是非共享資源所必須的,不僅不能改變,還應加以保證,所以,主要是破壞產生死鎖的其他三個條件。
a、破壞“占有且等待”條件
方法1:所有的進程在開始運行之前,必須一次性地申請其在整個運行過程中所需要的全部資源。
優點:簡單易實施且安全。
缺點:因為某項資源不滿足,進程無法啟動,而其他已經滿足了的資源也不會得到利用,嚴重降低了資源的利用率,造成資源浪費。 使進程經常發生饑餓現象。
方法2:該方法是對第一種方法的改進,允許進程只獲得運行初期需要的資源,便開始運行,在運行過程中逐步釋放掉分配到的已經使用完畢的資源,然后再去請求新的資源。這樣的話,資源的利用率會得到提高,也會減少進程的饑餓問題。
b、破壞“不可搶占”條件 當一個已經持有了一些資源的進程在提出新的資源請求沒有得到滿足時,它必須釋放已經保持的所有資源,待以后需要使用的時候再重新申請。這就意味著進程已占有的資源會被短暫地釋放或者說是被搶占了。 該種方法實現起來比較復雜,且代價也比較大。釋放已經保持的資源很有可能會導致進程之前的工作實效等,反復的申請和釋放資源會導致進程的執行被無限的推遲,這不僅會延長進程的周轉周期,還會影響系統的吞吐量。
c、破壞“循環等待”條件 可以通過定義資源類型的線性順序來預防,可將每個資源編號,當一個進程占有編號為i的資源時,那么它下一次申請資源只能申請編號大于i的資源。如圖所示:這樣雖然避免了循環等待,但是這種方法是比較低效的,資源的執行速度回變慢,并且可能在沒有必要的情況下拒絕資源的訪問,比如說,進程c想要申請資源1,如果資源1并沒有被其他進程占有,此時將它分配個進程c是沒有問題的,但是為了避免產生循環等待,該申請會被拒絕,這樣就降低了資源的利用率
4.2、避免死鎖 ----- 在使用前進行判斷,只允許不會產生死鎖的進程申請資源的死鎖避免是利用額外的檢驗信息,在分配資源時判斷是否會出現死鎖,只在不會出現死鎖的情況下才分配資源。
兩種避免辦法:
1、如果一個進程的請求會導致死鎖,則不啟動該進程
2、如果一個進程的增加資源請求會導致死鎖 ,則拒絕該申請。
注意:預防死鎖和避免死鎖的區別:
預防死鎖是設法至少破壞產生死鎖的四個必要條件之一,嚴格的防止死鎖的出現,
而避免死鎖則不那么嚴格的限制產生死鎖的必要條件的存在,因為即使死鎖的必要條件存在,也不一定發生死鎖。避免死鎖是在系統運行過程中注意避免死鎖的最終發生。
4.2.1 常用避免死鎖的方法
4.2.1.1 有序資源分配法
這種算法資源按某種規則系統中的所有資源統一編號(例如打印機為1、磁帶機為2、磁盤為3、等等),申請時必須以上升的次序。系統要求申請進程:
1、對它所必須使用的而且屬于同一類的所有資源,必須一次申請完;
2、在申請不同類資源時,必須按各類設備的編號依次申請。例如:進程PA,使用資源的順序是R1,R2; 進程PB,使用資源的順序是R2,R1;若采用動態分配有可能形成環路條件,造成死鎖。
采用有序資源分配法:R1的編號為1,R2的編號為2;
PA:申請次序應是:R1,R2
PB:申請次序應是:R1,R2
這樣就破壞了環路條件,避免了死鎖的發生。
詳見銀行家算法.
避免死鎖的具體實現通常利用銀行家算法 銀行家算法a、銀行家算法的相關數據結構
1)可利用資源向量Available:用于表示系統里邊各種資源剩余的數目。由于系統里邊擁有的資源通常都是有很多種(假設有m種),所以,我們用一個有m個元素的數組來表示各種資源。數組元素的初始值為系統里邊所配置的該類全部可用資源的數目,其數值隨著該類資源的分配與回收動態地改變。
2) 最大需求矩陣Max:用于表示各個進程對各種資源的額最大需求量。進程可能會有很多個(假設為n個),那么,我們就可以用一個nxm的矩陣來表示各個進程多各種資源的最大需求量
3)分配矩陣Allocation:顧名思義,就是用于表示已經分配給各個進程的各種資源的數目。也是一個nxm的矩陣。
4)需求矩陣Need:用于表示進程仍然需要的資源數目,用一個nxm的矩陣表示。
系統可能沒法一下就滿足了某個進程的最大需求(通常進程對資源的最大需求也是指它在整個運行周期中需要的資源數目,并不是每一個時刻都需要這么多),于是,為了進程的執行能夠向前推進,通常,系統會先分配個進程一部分資源保證進程能夠執行起來。那么,進程的最大需求減去已經分配給進程的數目,就得到了進程仍然需要的資源數目了。
銀行家算法通過對進程需求、占有和系統擁有資源的實時統計,確保系統在分配給進程資源不會造成死鎖才會給與分配。
注意:死鎖避免的優缺點
1)死鎖避免的優點:不需要死鎖預防中的搶占和重新運行進程,并且比死鎖預防的限制要少。
2)死鎖避免的限制: 必須事先聲明每個進程請求的最大資源量 考慮的進程必須無關的,也就是說,它們執行的順序必須沒有任何同步要求的限制 分配的資源數目必須是固定的。 在占有資源時,進程不能退出
4.2.2 常用避免死鎖的技術:
4.2.2.1 加鎖順序:(線程按照一定的順序加鎖)
4.2.2.2 加鎖時限(線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,并釋放自己占有的鎖)
4.2.2.3 死鎖檢測
當多個線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發生。
如果能確保所有的線程都是按照相同的順序獲得鎖,那么死鎖就不會發生。看下面這個例子:
Thread 1:
lock A
lock B
Thread 2:
wait for A
lock C (when A locked)
Thread 3:
wait for A
wait for B
wait for C
如果一個線程(比如線程3)需要一些鎖,那么它必須按照確定的順序獲取鎖。它只有獲得了從順序上排在前面的鎖之后,才能獲取后面的鎖。
例如,線程2和線程3只有在獲取了鎖A之后才能嘗試獲取鎖C(譯者注:獲取鎖A是獲取鎖C的必要條件)。因為線程1已經擁有了鎖A,所以線程2和3需要一直等到鎖A被釋放。然后在它們嘗試對B或C加鎖之前,必須成功地對A加了鎖。
按照順序加鎖是一種有效的死鎖預防機制。但是,這種方式需要你事先知道所有可能會用到的鎖(譯者注:并對這些鎖做適當的排序),但總有些時候是無法預知的。
另外一個可以避免死鎖的方法是在嘗試獲取鎖的時候加一個超時時間,這也就意味著在嘗試獲取鎖的過程中若超過了這個時限該線程則放棄對該鎖請求。若一個線程沒有在給定的時限內成功獲得所有需要的鎖,則會進行回退并釋放所有已經獲得的鎖,然后等待一段隨機的時間再重試。這段隨機的等待時間讓其它線程有機會嘗試獲取相同的這些鎖,并且讓該應用在沒有獲得鎖的時候可以繼續運行(譯者注:加鎖超時后可以先繼續運行干點其它事情,再回頭來重復之前加鎖的邏輯)。
以下是一個例子,展示了兩個線程以不同的順序嘗試獲取相同的兩個鎖,在發生超時后回退并重試的場景:
Thread 1 locks A
Thread 2 locks B
Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked
Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis) before retrying.
Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis) before retrying.
在上面的例子中,線程2比線程1早200毫秒進行重試加鎖,因此它可以先成功地獲取到兩個鎖。這時,線程1嘗試獲取鎖A并且處于等待狀態。當線程2結束時,線程1也可以順利的獲得這兩個鎖(除非線程2或者其它線程在線程1成功獲得兩個鎖之前又獲得其中的一些鎖)。
需要注意的是,由于存在鎖的超時,所以我們不能認為這種場景就一定是出現了死鎖。也可能是因為獲得了鎖的線程(導致其它線程超時)需要很長的時間去完成它的任務。
此外,如果有非常多的線程同一時間去競爭同一批資源,就算有超時和回退機制,還是可能會導致這些線程重復地嘗試但卻始終得不到鎖。如果只有兩個線程,并且重試的超時時間設定為0到500毫秒之間,這種現象可能不會發生,但是如果是10個或20個線程情況就不同了。因為這些線程等待相等的重試時間的概率就高的多(或者非常接近以至于會出現問題)。
(譯者注:超時和重試機制是為了避免在同一時間出現的競爭,但是當線程很多時,其中兩個或多個線程的超時時間一樣或者接近的可能性就會很大,因此就算出現競爭而導致超時后,由于超時時間一樣,它們又會同時開始重試,導致新一輪的競爭,帶來了新的問題。)
這種機制存在一個問題,在Java中不能對synchronized同步塊設置超時時間。你需要創建一個自定義鎖,或使用Java5中java.util.concurrent包下的工具。寫一個自定義鎖類不復雜,但超出了本文的內容。后續的Java并發系列會涵蓋自定義鎖的內容。
死鎖檢測是一個更好的死鎖預防機制,它主要是針對那些不可能實現按序加鎖并且鎖超時也不可行的場景。
每當一個線程獲得了鎖,會在線程和鎖相關的數據結構中(map、graph等等)將其記下。除此之外,每當有線程請求鎖,也需要記錄在這個數據結構中。
當一個線程請求鎖失敗時,這個線程可以遍歷鎖的關系圖看看是否有死鎖發生。例如,線程A請求鎖7,但是鎖7這個時候被線程B持有,這時線程A就可以檢查一下線程B是否已經請求了線程A當前所持有的鎖。如果線程B確實有這樣的請求,那么就是發生了死鎖(線程A擁有鎖1,請求鎖7;線程B擁有鎖7,請求鎖1)。
當然,死鎖一般要比兩個線程互相持有對方的鎖這種情況要復雜的多。線程A等待線程B,線程B等待線程C,線程C等待線程D,線程D又在等待線程A。線程A為了檢測死鎖,它需要遞進地檢測所有被B請求的鎖。從線程B所請求的鎖開始,線程A找到了線程C,然后又找到了線程D,發現線程D請求的鎖被線程A自己持有著。這是它就知道發生了死鎖。
下面是一幅關于四個線程(A,B,C和D)之間鎖占有和請求的關系圖。像這樣的數據結構就可以被用來檢測死鎖。
4.3 檢測死鎖
死鎖檢測算法。
算法一:
算法使用的數據結構是如下這些:
占有矩陣A:n*m階,其中n表示并發進程的個數,m表示系統的各類資源的個數,這個矩陣記錄了每一個進程當前占有各個資源類中資源的個數。
申請矩陣R:n*m階,其中n表示并發進程的個數,m表示系統的各類資源的個數,這個矩陣記錄了每一個進程當前要完成工作需要申請的各個資源類中資源的個數。
空閑向量T:記錄當前m個資源類中空閑資源的個數。
完成向量F:布爾型向量值為真(true)或假(false),記錄當前n個并發進程能否進行完。為真即能進行完,為假則不能進行完。
臨時向量W:開始時W:=T。
算法步驟:
(1)W:=T,
對于所有的i=1,2,…,n,
如果A[i]=0,則F[i]:=true;否則,F[i]:=false
(2)找滿足下面條件的下標i:
F[i]:=false并且R[i]〈=W
如果不存在滿足上面的條件i,則轉到步驟(4)。
(3)W:=W+A[i]
F[i]:=true
轉到步驟(2)
(4)如果存在i,F[i]:=false,則系統處于死鎖狀態,且Pi進程參與了死鎖。什么時候進行死鎖的檢測取決于死鎖發生的頻率。如果死鎖發生的頻率高,那么死鎖檢測的頻率也要相應提高,這樣一方面可以提高系統資源的利用率,一方面可以避免更多的進程卷入死鎖。如果進程申請資源不能滿足就立刻進行檢測,那么每當死鎖形成時即能被發現,這和死鎖避免的算法相近,只是系統的開銷較大。為了減小死鎖檢測帶來的系統開銷,一般采取每隔一段時間進行一次死鎖檢測,或者在CPU的利用率降低到某一數值時,進行死鎖的檢測。
那么當檢測出死鎖時,這些線程該做些什么呢?
1)一個可行的做法是釋放所有鎖,回退,并且等待一段隨機的時間后重試。這個和簡單的加鎖超時類似,不一樣的是只有死鎖已經發生了才回退,而不會是因為加鎖的請求超時了。雖然有回退和等待,但是如果有大量的線程競爭同一批鎖,它們還是會重復地死鎖(編者注:原因同超時類似,不能從根本上減輕競爭)。
2)一個更好的方案是給這些線程設置優先級,讓一個(或幾個)線程回退,剩下的線程就像沒發生死鎖一樣繼續保持著它們需要的鎖。如果賦予這些線程的優先級是固定不變的,同一批線程總是會擁有更高的優先級。為避免這個問題,可以在死鎖發生的時候設置隨機的優先級。
算法二:
死鎖檢測與解除 ----- 在檢測到運行系統進入死鎖,進行恢復。 允許系統進入到死鎖狀態 死鎖檢測下圖截自《操作系統--精髓與設計原理》
4.4 死鎖的解除
如果利用死鎖檢測算法檢測出系統已經出現了死鎖 ,那么,此時就需要對系統采取相應的措施。
常用的解除死鎖的方法:
1、搶占資源:從一個或多個進程中搶占足夠數量的資源分配給死鎖進程,以解除死鎖狀態。但應防止被掛起的進程長時間得不到資源,而處于資源匱乏的狀態。
2、終止(或撤銷)進程:終止或撤銷系統中的一個或多個死鎖進程,直至打破死鎖狀態。撤銷的原則可以按進程優先級和撤銷進程代價的高低進行。
a、終止所有的死鎖進程。這種方式簡單粗暴,但是代價很大,很有可能會導致一些已經運行了很久的進程前功盡棄。
b、逐個終止進程,直至死鎖狀態解除。該方法的代價也很大,因為每終止一個進程就需要使用死鎖檢測來檢測系統當前是否處于死鎖狀態。要求系統保持進程的歷史信息,設置還原點。另外,每次終止進程的時候終止那個進程呢?每次都應該采用最優策略來選擇一個“代價最小”的進程來解除死鎖狀態。
一般根據如下幾個方面來決定終止哪個進程: 1】進程的優先級
2】進程已運行時間以及運行完成還需要的時間
3】進程已占用系統資源
4】進程運行完成還需要的資源終止進程數目
5】進程是交互還是批處理
十一、MySQL支持的事務隔離級別
4種:
read uncommited –> 讀”臟”數據現象,“臟讀"
read commited –> 不可重復讀現象,
repeatable read(MySQL默認)–>幻讀現象
serializable->可串行化,
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。