概述
數據庫一般都會并發執行多個事務,多個事務可能會并發的對相同的一批數據進行增刪改查操作,可能導致臟讀、臟寫、不可重復度和幻讀。這些問題的本質都是數據庫的多事務并發問題,為了解決事務并發問題,數據庫設計了事務隔離機制、鎖機制、mvcc多版本并發控制隔離機制,用一整套機制來解決多事務并發問題。
事務及其acid屬性
原子性:操作的不可分割;
一致性:數據的一致性;
隔離性:事務之間互不干擾;
持久性:數據的修改時永久的;
并發事務處理帶來的問題
臟寫:丟失更新,最后的更新覆蓋了由其他事務所做的更新;
臟讀:事務a讀取到了事務b已經修改但未提交的數據;
不可重復讀:事務內部相同的查詢在不同時刻結果不一樣,針對的是數據的更新、刪除操作;
幻讀:事務a讀取到了其后開始的事務b提交的新增數據;針對的是數據的插入;
事務隔離級別
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
讀未提交 | √ | √ | √ |
讀已提交 | × | √ | √ |
可重復讀 | × | × | √ |
可串行化 | × | × | × |
read-unconmmitted、read-committed、repeatable-read、serializable
查看當前數據庫的事務隔離級別:
1
|
show variables like 'tx_isolation' |
設置事務隔離級別:
1
|
set tx_isolation= 'repeatable-read' ; |
mysql默認的事務隔離級別是可重復讀,用spring開發程序時,如果不設置隔離級別默認用mysql設置的隔離級別,如果spring設置了就用已設置的隔離級別;
鎖詳解
鎖是計算機協調多個進程或線程并發訪問某一資源的機制。
鎖分類
從性能上分為:樂觀鎖(用版本對比來實現)和悲觀鎖;
從對數據庫操作類型分:讀寫和寫鎖(悲觀鎖);
讀鎖(共享鎖,s鎖(shared)):針對同一份數據,多個讀操作可以同時進行而不會互相影響;
寫鎖(排它鎖,x鎖(exclusive)):當前寫操作沒有完成前,阻斷其他寫鎖和讀鎖;
從對數據庫操作的粒度分:表鎖和行鎖
表鎖:每次操作鎖住整張表,開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,并發度最低;一般用在整表數據遷移的場景。
1
2
3
4
5
6
|
# 手動增加表鎖 lock table 表名稱 read (write),表名稱2 read (write); # 查看表上加過的鎖 show open tables; # 刪除表鎖 unlock tables; |
行鎖:每次操作鎖住一行數據。開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,并發度最高。
innodb與myisam的最大不同點:1、innodb支持事務;2、innodb支持行級鎖。
總結:
myisam在執行查詢語句前,會自動給涉及的表加讀鎖;執行update、insert、delete操作加寫鎖;
innodb在執行查詢語句前(非串行隔離級別),不會加鎖;執行update、insert、delete操作會加行鎖。
讀鎖會阻塞寫,但不會阻塞讀。而寫鎖會把讀寫都阻塞。
行鎖與事務隔離級別案例分析
mysql準備一張表
1、臟讀,事務a讀取到另外一個事務已修改但未提交的數據,此種情形簡單,不具體闡述。對應的事務隔離級別:read uncommitted(讀未提交)。
2、不可重復讀,對應的事務隔離級別:read committed(讀已提交)
事務a:
1
2
3
4
5
|
set session transaction isolation level read committed ; start transaction ; select * from t_user; |
事務b:
1
2
3
4
5
6
7
8
|
set session transaction isolation level read committed ; start transaction ; -- insert into t_user values (1,'張',8); update t_user set age = 9 where id = 1; commit ; |
事務a第一次執行到查詢語句,結果如下:
此時,事務b執行完畢,事務a還未結束,繼續執行一次查詢,結果如下:
產生了不可重復讀的問題,一個事務內前后兩次查詢的數據結果不一致,讀取到了其他事務已經提交的數據。
3、可重復讀,設置事務隔離級別為repeatable read(可重復讀);
事務a第一次執行結果如下:
事務b執行,修改操作,update age=8并提交,結果對比如下
左邊為事務a,查詢結果與開始時一樣,解決了不可重復讀的問題;直接查詢,此時age=8;
可重復讀的隔離級別下使用了mvcc(multi-version concurrency control)機制,select操作不會更新版本號,是快照讀(歷史版本);insert、update和delete會更新版本號,是當前讀(當前版本)。
4、幻讀,在3中,新增一條數據,如下
此時事務a再次查詢,結果如下:
結果依然和開始的一樣,此種場景,可重復讀隔離級別有效的防止了不可重復讀和幻讀的問題;
如果,事務a,在第一次查詢后,執行不加條件的update,這個update會作用在所有的行上面,包括事務b新增加的數據。此時,再執行查詢,結果如下:
出現了幻讀,mysql官方給出的幻讀解釋是:只要在一個事務中,第二次select多出了row計算幻讀。
5、串行化,serializable,innodb的查詢也會被加上行鎖。如果查詢的是一個范圍,那么該范圍內的所有記錄行包括每行記錄所在的間隙區間范圍都會被加鎖,即使該行數據還沒有被插入。
間隙鎖(gap lock)
session_1執行update t_user set name ='哈哈' where id>8 and id<18;則其他session無法在這個范圍包含的所有行記錄以及行記錄所在的間隙里插入或修改任何數據
間隙鎖在可重復讀隔離級別下才會生效
臨鍵鎖(next-key locks)
next-key locks是行鎖與間隙鎖的組合。在間隙鎖(8,18)這個范圍,實際會找到存在的值,比如id距離這個區間最近的是,3,20;則實際在(3,20]這個范圍都處在行鎖范圍內。
無索引行鎖會升級為表鎖
鎖主要是加在索引上,如果對非索引字段更新,行鎖可能會變表鎖。
innodb的行鎖是針對索引加的鎖,不是針對記錄加的鎖。并且該索引不能失效,否則都會從行鎖升級為表鎖
鎖定某一行還可以用lock in share mode(共享鎖)和for update(排它鎖)
結論:
innodb存儲引擎由于實現了行級鎖定,雖然在鎖定機制的實現方面所帶來的性能損耗可能比表級鎖定會更高,但是在整體并發處理能力方面要遠遠優于myisam的表級鎖定。
但是,innodb的行級鎖定同樣有其脆弱的一面,如使用不當,可能會讓整體的性能更差。
行鎖分析
通過檢查innodb_row_lock狀態變量來分析系統上的行鎖的爭奪情況
1
|
show status like 'innodb_row_lock%' ; |
比較重要的主要有:
innodb_row_lock_time_avg(等待平均時長)
innodb_row_lock_waits(等待總次數)
innodb_row_lock_time(等待總時長)
當等待次數很高,且每次等待時長也不小的時候,就需要分析系統中為什么會有如此多的等待,根據分析結果制定優化計劃。
死鎖
1
2
3
4
5
6
|
set session transaction isolation level repeatable read ; start transaction ; select * from t_user where id = 2 for update ; select * from t_user where id = 1 for update ; |
事務a先鎖定id=1,再鎖定id=2;事務b順序相反,出現死鎖,結果如下:
大多數情況mysql可以自動檢測死鎖并回滾產生死鎖的那個事務,但有些情況無招。
查看近期死鎖日志信息:
1
|
show engine innodb status\g; |
鎖優化建議:
1、盡可能讓所有數據檢索都通過索引來完成,避免無索引行鎖升級為表鎖;
2、合理設計索引,盡量縮小鎖的范圍;
3、盡可能減少索引條件范圍,避免間隙鎖;
4、盡量控制事務大小,減少鎖定資源量和時間長度,涉及事務加鎖的sql盡量放在事務最后執行;
5、盡可能低級別事務隔離
到此這篇關于深入理解mysql事務隔離級別與鎖機制的文章就介紹到這了,更多相關mysql事務隔離級別與鎖機制內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/David_bdqn/article/details/120451543