計算機程序鎖
- 控制對共享資源進行並發訪問
- 保護數據的完整性和一致性


lock 主要是事務,數據庫邏輯內容,事務過程
latch/mutex 內存底層鎖;
更新丟失
原因:
B的更改還沒有提交時,A已經再次修改了數據。
此時A使用原來的元數據作為基礎更新后,B的更新便會丟失;


解決辦法:
在修改數據上加寫鎖,當有鎖時,A會等B更新提交完,才可以繼續在B的基礎上繼續更新;


事務鎖粒度
行鎖: innodb ,oracle
頁鎖:sql server
表鎖:Myisam ,memory
獲取innodb行鎖爭用情況
mysql> show status like '%innodb_row_lock%'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 0 | | Innodb_row_lock_time_avg | 0 | | Innodb_row_lock_time_max | 0 | | Innodb_row_lock_waits | 0 | +-------------------------------+-------+ 5 rows in set (0.00 sec)
如果發現鎖爭用比較嚴重,如innodb_row_lock_waits 和 innodb_row_lock_time_avg的值比較高,
還可以通過設置innodb monitor 來進一步觀察發生鎖沖突的表,數據行等,並分析鎖爭用的原因:
innodb鎖模式與粒度
四種基本鎖模式
- 共享鎖(S)-讀鎖-行鎖
- 排他鎖(X)-寫鎖-行鎖
- 意向共享鎖(IS)-表級 :事務想要獲得一張表中某幾行的共享鎖
- 意向排他鎖(IX)-表級:事務想要獲得一張表中某幾行的排他鎖
意向鎖,簡單來說就是:
如需要對頁上的記錄R進行X鎖,那么分別需要對該記錄所在的數據庫,表,頁,上意向鎖IX,最后對記錄R上X鎖。
若其中任何一個部分導致等待,那么該操作需要等待粗粒度鎖的完成。
innodb支持意向鎖設計比較簡練,其意向鎖即為表級別的鎖。設計目的主要是為了在一個事務中揭示下一行將被請求的鎖類型。
意向鎖:
- 意向鎖總是自動先加,並且意向鎖自動加自動釋放
- 意向鎖提示數據庫這個session將要在接下來將要施加何種鎖
- 意向鎖和X/S 鎖級別不同,除了阻塞全表級別的X/S鎖外其他任何鎖
自動施加,自動釋放,
innodb鎖模式互斥


數據庫加鎖操作
一般的select語句不加任何鎖,也不會被任何事物鎖阻塞
讀的隔離性由MVCC確保
undo log 用來幫助事務回滾及MVCC(多版本並發控制 ,即select時可以使用行數據的快照,而不用等待鎖資源)
S鎖
手動:select * from tb_test lock in share mode;
自動:insert前
X鎖
手動:
select * from tb_test for update;
自動:update,delete 前
線上環境中:


鎖等待時間:innodb_lock_wait_timeout
mysql>show global variables like "%wait%"
innodb 行鎖
通過索引項加鎖實現
- 只有條件走索引才能實現行級鎖 a)
- 索引上有重復值,可能鎖住多個記錄 b)
- 查詢有多個索引可以走,可以對不同索引加鎖 c)
- 是否對索引加鎖實際上取決於Mysql執行計划
自增主鍵做條件更新,性能做好;
通過索引項加鎖實現的例子:
a) 只有,有條件走索引才能實現行級鎖
mysql> show create table t2\G; *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1 mysql> select * from t2; +------+------+ | a | b | +------+------+ | 1 | 2 | | 1 | 3 | +------+------+ 此時A連接 在b =2 時加 寫鎖; mysql> select * from t2 where b =2 for update; +------+------+ | a | b | +------+------+ | 1 | 2 | +------+------+ 而此時再B連接中再對b=3,加寫鎖時,失敗; mysql> select * from t2 where b=3 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
說明,表中沒有索引時,innodb將對整個表加鎖,而不能體現行鎖的特性;
b) 索引上有重復值,可能鎖住多個記錄
mysql> show create table t2\G; *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, KEY `a` (`a`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 1 row in set (0.00 sec) mysql> select * from t2; +------+------+ | a | b | +------+------+ | 1 | 2 | | 1 | 3 | | 2 | 9 | +------+------+ 在A連接中,在a=1,b=2處加一個寫鎖;實際上 是在a=1這個索引上加的鎖 mysql> select * from t2 where a=1 and b=2 for update; +------+------+ | a | b | +------+------+ | 1 | 2 | +------+------+ 1 row in set (0.00 sec) 在B連接中,在a=1 and b=3處加寫鎖失敗,因都是a=1這個索引,而A中已經對a=1這個索引的行加過了鎖; mysql> select * from t2 where a =1 and b=3 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 此時B連接是可以對 a=2 and b =9 這一行中,在a=2 這個索引上加鎖的; mysql> select * from t2 where a=2 and b =9 for update ; +------+------+ | a | b | +------+------+ | 2 | 9 | +------+------+
注意
行鎖升級成表鎖:
mysql> select * from t2 where b =9 for update ;
這句對本意在b=9這行加索引,b又沒有加索引,所以這是對整個表加鎖;因為沒有指定a =2,所以mysql找不到a這個索引的;
c) 查詢有多個索引可以走,可以對不同索引加鎖
mysql> show create table t2\G; *************************** 1. row *************************** Table: t2 Create Table: CREATE TABLE `t2` ( `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, KEY `a` (`a`), KEY `b` (`b`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 mysql> select * from t2; +------+------+ | a | b | +------+------+ | 1 | 2 | | 1 | 3 | | 2 | 9 | +------+------+ 在A連接中對 a=1 and b=2 加鎖; mysql> select * from t2 where a =1 and b =2 for update; +------+------+ | a | b | +------+------+ | 1 | 2 | +------+------+ 此時B連接中對a =1 and b=3 ,也是可以加鎖的;這是因為mysql 可以從a=1這個索引來加鎖,也可以對b=3加鎖; 所以就與上面b)中只能對a=1索引來加鎖 區別開來; mysql> select * from t2 where a =1 and b =3 for update; +------+------+ | a | b | +------+------+ | 1 | 3 | +------+------+
innodb的gap lock 間隙鎖
gap lock消滅幻讀
innodb消滅幻讀僅僅為了確保 statement模式
replicate的主從一致性
小心gap lock
自增主鍵做條件更新,性能最好;
gap lock 間隙鎖 解釋:
mysql> select * from t2; +------+------+ | a | b | +------+------+ | 20 | 2 | | 24 | 4 | | 27 | 5 | | 27 | 6 | | 27 | 8 | | 30 | 6 | | 31 | 4 | | 32 | 9 | +------+------+ 8 rows in set (0.00 sec) 在A連接中給a=27 加鎖(a 是有索引的) mysql> select * from t2 where a=27 for update; +------+------+ | a | b | +------+------+ | 27 | 5 | | 27 | 6 | | 27 | 8 | +------+------+ 3 rows in set (0.00 sec)
此時隔離等級是Repeatable Read,標准的是可以出現幻讀現象的,
即在B連接中 insert into t2 values(27,3),是可以插入成功的,而且B連接提交后,A連接是可以查看到增加的,27,3這一行的。
而innodb 通過間隙鎖是的B連接中 insert into t2 values(27,3) 插入失敗,來消滅幻讀的出現。
但是這種方法是有
局限的,它會將
a=24--29(30-1)中間的任何數都鎖住,所以才叫間隙鎖;
B連接中則只能插入不在這個區間的數據;
鎖升級
- 由一句單獨的sql語句在一個對象上持有的鎖的數量超過了閾值,默認這個閾值為5000.值得注意的是,如果是不同對象,則不會發生鎖升級。
- 鎖資源占用的內存超過了激活內存的40%時就會發生鎖升級
innodb不存在鎖升級的問題。因為其不是根據每個記錄來產生行鎖的,相反,其根據每個事務訪問的每個頁對鎖進行管理的,采用的是
位圖的方式。因此不管一個事務鎖住頁中一個記錄還是多個記錄,其開銷通常都是一致的。
簡單說innodb根據頁進行加鎖,並采用位圖方式,定位到行的,所需資源較小。
例子:


死鎖


死鎖數據庫自動解決
數據庫挑選沖突事務中回滾代價較小的事務回滾
死鎖預防
單表死鎖可以根據批量更新表的更新條件排序
可能沖突的跨表事務盡量避免並發
盡量縮短事務長度
排查死鎖:
- 了解觸發死鎖的sql所在事務的上下文
- 根據上下文語句加鎖的范圍來分析存在爭用的記錄
- 通常改善死鎖的主要方法:
--對同一表的操作根據加鎖條件進行排序
--拆分長事務
業務邏輯加鎖
業務流程中的悲觀鎖(開始的時候,在所有記錄加鎖,直到最后釋放;而樂觀鎖開始不加鎖,只是在最后提交中看提交有沒有成功,沒成功返回給應用程序)
悲觀鎖開始就給所有記錄加鎖,一般等所有業務流程完成,才釋放鎖;因此會對並發性能有一定的影響;
如何縮短鎖的時間?
1)開始的時候讀取要修改的數據,amount(金額)
2)做業務流程
3)在update時,加鎖且判斷,現在的amount和開始的amount是否為一個值,如果是,說明這期間amount為改變,則更新;如果amount值改了,則不更新,交給業務來判斷該怎么做。
這樣僅是在update這個語句加鎖,大大的縮短的鎖的時間提高了並發性;
但是如果業務十分的繁忙,amount的值在不斷改變,此時這個update 就不斷的失敗,整個事務就不斷的失敗,反而影響了 性能。那么該如何做呢?
在開始的時候不讀取數據,等到要提交的時候讀取並加鎖提交;
總結
- 更新丟失
- innodb意向鎖:
-
- 表鎖
- 自動施加、自動釋放
- 為了揭示事務下一行將被請求的鎖類型
- S鎖:in share mode
- X鎖:for update
- innodb行鎖特點:
-
- 只有條件走索引才能實現行鎖
- 索引上有重復值可能鎖住多個記錄
- 查詢有多個索引可以走,可以對不同索引加鎖
- gap lock:間隙鎖,消滅幻讀
- 死鎖解決:數據庫挑回滾代價較小的事務回滾;
- 死鎖預防:
-
- 單表,更新條件排序
- 避免跨表事務,縮短事務長度
- 鎖升級:
-
- 單獨sql語句在單個對象的鎖數量超過闕值
- 鎖資源占用的內存超過了激活內存的40%;
- innodb根據頁進行加鎖,並采用位圖方式,定位到行的,所需資源較小