數據庫中并發(fā)存在的問題
數據庫能夠讓應用程序并發(fā)訪問,在并發(fā)訪問數據庫實例過程中可能會出現以下4種現象旁仿。
- 丟失更新。兩個事務都同時更新一行數據赠制,一個事務對數據的更新把另一個事務對數據的更新覆蓋了;
- 臟讀。一個事務讀取了另外一個事務還沒有提交的操作溪胶,這比較危險,因為另一個事務的操作可能會回滾稳诚;
- 不可重復讀。一個事務對同一行數據進行重復讀取瀑踢,但是每次讀取卻得到不同的數據扳还;
- 幻讀。一個事務對某個范圍的數據進行兩次讀取橱夭,得到的記錄數卻不相同氨距,這是因為兩次讀取之前,另外一個事務可能增加或刪除了一條范圍之內的數據棘劣。
這4種現象即能把它們看成是并發(fā)產生的問題俏让,也可以不把它們理解成并發(fā)問題。具體還要聯(lián)系實際應用場景去分析茬暇,主要看應用中是否允許某種現象的存在首昔。大部分情況下我們認為丟失更新和臟讀是個問題,應該要避免糙俗,可以通過應用不同的隔離級別來實現勒奇。
談談丟失更新
MySQL本身不會產生丟失更新,因為在對某一行進行更新操作時(此時事務尚未提交)巧骚,Innodb存儲引擎會在該行上加一個X鎖赊颠,這是其他事務再對該行進行更新操作時格二,會被阻塞直到該行上的X鎖被釋放后才能夠執(zhí)行。即使在Read Uncommit的事務隔離級別下竣蹦,也不會產生丟失更新的現象顶猜。
雖然數據本身不會產生丟失更新,但是實際應用程序中可能會產生另外一種邏輯意義上的丟失更新問題痘括。例如出現下面的情況時长窄,就會產生丟失更新:
- 事務T1查詢某一行數據,放入本地內存远寸,并顯示給終端用戶User1進行操作抄淑;
- 事務T2查詢同一行數據,放入本地內存驰后,并顯示給終端用戶User2進行操作肆资;
- User1修改這行記錄,更新數據庫并提交灶芝;
- User2修改這行記錄郑原,更新數據庫并提交。
這個過程中User1的修改就相當于“丟失了”夜涕。作為程序員而言犯犁,編寫上面這種邏輯的代碼還是很常見的。
那如何解決這個問題呢女器?這種情況下事務不能并發(fā)的操作酸役,要讓事務變成串行化執(zhí)行。具體做法為:步驟1和步驟2的查詢語句中都加入for update
結尾驾胆,這樣用戶讀取同一行記錄時就會加上X鎖涣澡。如果步驟1獲取到了X鎖時,步驟2就會阻塞直到步驟1和步驟3執(zhí)行完釋放鎖后才開始執(zhí)行丧诺。
隔離級別
對于上面講到的并發(fā)中存在的問題入桂,可以通過設置不同的隔離級別來解決。事務的特性中有一大特性是隔離性驳阎,什么是隔離性抗愁?隔離性指的是一個事務所做的修改,對其他事務是不可見的呵晚,就像這兩個事務是串行執(zhí)行蜘腌。
隔離級別可以看成是對隔離性不同程度的實現,嚴格意義上講只有隔離級別為Serializable的事務才具有隔離性劣纲。如果所有的數據庫操作都串行化執(zhí)行逢捺,那性能就會很低,所以出于對性能的考慮才會有其他隔離性稍差但是性能較好的隔離級別癞季。
SQL標準中定義了4種隔離級別
- Read Uncommitted劫瞳,該隔離級別不會有丟失更新倘潜;
- Read Committed,該隔離級別不會有丟失更新志于、臟讀涮因;
- Repeatable Read,該隔離級別不會有丟失更新伺绽、臟讀养泡、不可重復讀;
- Serializable奈应,該隔離級別不會有丟失更新澜掩、臟讀、不可重復度杖挣、幻讀肩榕。
MySQL實現了這4種隔離級別,默認的隔離級別在Innodb中是Repeatable Read惩妇。而Oracle數據庫只實現了Read Committed和Serializable兩種株汉,它的默認隔離級別是Read Committed。
Innodb在Repeatable Read隔離級別中歌殃,通過Next-Key Lock
鎖的算法來避免幻讀的產生乔妈,這與其他關系型數據庫不同。所以說氓皱,InnoDB存儲引擎在默認的Repeatable Read
隔離級別下已經能完全保證事務的隔離性要求路召,即達到SQL標準的Serializable
隔離級別。
隔離級別的設置與查看
可以通過set [ global | session ] transaction_isolation='read-uncommitted | read-committed | repeatable-read | serializable'來設置事務的隔離級別波材。
如果要在mysql啟動的時候設置事務的默認隔離級別优训,可以修改Mysql的配置文件my.cnf,在[mysqld]中添加如下行:
[mysqld]
transaction_isolation=read-committed
查看當前的事務隔離級別可以使用select @@transaction_isolation
查看全局的事務隔離級別可以使用select @@global.transaction_isolation
.
不同隔離級別下鎖的實現
隔離性通過鎖來實現各聘,不同的隔離級別對應不同的加鎖策略。下面來看看不同事務隔離級別的加鎖策略:
Read Uncommitted
讀操作(不顯式加鎖的select語句)不加鎖抡医;
寫操作加行級X鎖躲因,并到事務結束之后釋放。
Read Committed
讀操作使用MVCC;
寫操作使用使用行鎖中的記錄鎖忌傻,且RC隔離級別下沒有GAP鎖(唯一性的檢查約束和外鍵約束的檢查需要使用gap lock)
注意:設置了RC隔離級別后大脉,binlog_format要設置為row,否則會出現主從不一致的現象水孩。
Repeatable Read
讀操作使用MVCC;
寫操作使用行鎖镰矿,包括記錄鎖、GAP鎖俘种、Next-key Lock
Serializable
讀操不再使用MVCC,InnoDB引擎會自動在每個select語句后加上
lock in share mode
秤标。
寫操作使用行鎖绝淡。