1. 鎖
1.1. 鎖的簡介
1.1.1. 為什么需要鎖?
到淘寶上買一件商品,商品只有一件庫存,這個時候如果還有另一個人買,那么如何解決是你買到還是另一個人買到的問題?
1.1.2. 鎖的概念
l 鎖是計算機協調多個進程或線程並發訪問某一資源的機制。
l 在數據庫中,數據也是一種供許多用戶共享的資源。如何保證數據並發訪問的一致性、有效性是所有數據庫必須解決的一個問題,鎖沖突也是影響數據庫並發訪問性能的一個重要因素。
l 鎖對數據庫而言顯得尤其重要,也更加復雜。
1.1.3. MySQL中的鎖
l 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,並發度最低。
l 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,並發度也最高。
l 頁面鎖(gap鎖,間隙鎖):開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,並發度一般。
在這個部分只講表級鎖、行級鎖,gap鎖放到事務中講
1.1.4. 表鎖與行鎖的使用場景
表級鎖更適合於以查詢為主,只有少量按索引條件更新數據的應用,如OLAP系統
行級鎖則更適合於有大量按索引條件並發更新少量不同數據,同時又有並發查詢的應用,如一些在線事務處理(OLTP)系統。
很難籠統地說哪種鎖更好,只能就具體應用的特點來說哪種鎖更合適
1.2. MyISAM鎖
MySQL的表級鎖有兩種模式:
表共享讀鎖(Table Read Lock)
表獨占寫鎖(Table Write Lock)
1.2.1. 共享讀鎖
語法:lock table 表名 read
1. lock table testmysam READ 啟動另外一個session select * from testmysam 可以查詢
2. insert into testmysam value(2);
update testmysam set id=2 where id=1;
報錯
3.在另外一個session中
insert into testmysam value(2); 等待
4.在同一個session中
insert into account value(4,'aa',123); 報錯
select * from account ; 報錯
5.在另外一個session中
insert into account value(4,'aa',123); 成功
6.加索在同一個session 中 select s.* from testmysam s 報錯
lock table 表名 as 別名 read;
1.2.2. 獨占寫鎖
1.lock table testmysam WRITE
在同一個session中
insert testmysam value(3);
delete from testmysam where id = 3
select * from testmysam
2.對不同的表操作(報錯)
select s.* from testmysam s
insert into account value(4,'aa',123);
3.在其他session中 (等待)
select * from testmysam
1.2.3. 總結:
l 讀鎖,對MyISAM表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求
l 讀鎖,對MyISAM表的讀操作,不會阻塞當前session對表讀,當對表進行修改會報錯
l 讀鎖,一個session使用LOCK TABLE命令給表f加了讀鎖,這個session可以查詢鎖定表中的記錄,但更新或訪問其他表都會提示錯誤;
l 寫鎖,對 MyISAM表的寫操作,則會阻塞其他用戶對同一表的讀和寫操作;
l 寫鎖,對 MyISAM表的寫操作,當前session可以對本表做CRUD,但對其他表進行操作會報錯
1.3. InnoDB鎖
在mysql 的 InnoDB引擎支持行鎖
共享鎖又稱:讀鎖。當一個事務對某幾行上讀鎖時,允許其他事務對這幾行進行讀操作,但不允許其進行寫操作,也不允許其他事務給這幾行上排它鎖,但允許上讀鎖。
排它鎖又稱:寫鎖。當一個事務對某幾個上寫鎖時,不允許其他事務寫,但允許讀。更不允許其他事務給這幾行上任何鎖。包括寫鎖。
1.3.1. 語法
上共享鎖的寫法:lock in share mode
例如: select * from 表 where 條件 lock in share mode;
上排它鎖的寫法:for update
例如:select * from 表 where 條件 for update;
1.3.2. 注意:
1.兩個事務不能鎖同一個索引。
2.insert ,delete , update在事務中都會自動默認加上排它鎖。
3.行鎖必須有索引才能實現,否則會自動鎖全表,那么就不是行鎖了。
CREATE TABLE testdemo (
`id` int(255) NOT NULL ,
`c1` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`c2` int(50) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
INDEX `idx_c2` (`c2`) USING BTREE
)
ENGINE=InnoDB;
insert into testdemo VALUES(1,'1',1),(2,'2',2);
1.
BEGIN
select * from testdemo where id =1 for update
在另外一個session中
update testdemo set c1 = '1' where id = 2 成功
update testdemo set c1 = '1' where id = 1 等待
2.BEGIN
update testdemo set c1 = '1' where id = 1
在另外一個session中
update testdemo set c1 = '1' where id = 1 等待
3.
BEGIN
update testdemo set c1 = '1' where c1 = '1'
在另外一個session中
update testdemo set c1 = '2' where c1 = '2' 等待
4.第一個session中
select * from testdemo where id =1 for update
第二個session
select * from testdemo where id =1 lock in share mode
回到第一個session UNLOCK TABLES 並不會解鎖
使用commit 或者 begin或者ROLLBACK 才會解鎖
5.再來看下表鎖
lock table testdemo WRITE
使用commit,ROLLBACK 並不會解鎖
使用UNLOCK TABLES 或者begin會解鎖
1.4. 鎖的等待問題
那么現在來說一個實際的問題,在工作中經常一個數據被鎖住,導致另外的操作完全進行不下去。
你肯定碰到過這問題,有些程序員在debug程序的時候,經常會鎖住一部分數據庫的數據,而這個時候你也要調試這部分功能,卻發現代碼總是運行超時,你是否碰到過這問題了,其實這問題的根源我相信你也知道了。
舉例來說,有兩個會話。
程序員甲,正直調試代碼
BEGIN
SELECT * FROM testdemo WHERE id = 1 FOR UPDATE
你正直完成的功能也要經過那部分的代碼,你得上個讀鎖
BEGIN
SELECT * FROM testdemo WHERE id = 1 lock in share mode
這個時候很不幸,你並不知道發生了什么問題,在你調試得過程中永遠就是一個超時得異常,而這種問題不管在開發中還是在實際項目運行中都可能會碰到,那么怎么排查這個問題呢?
這其實也是有小技巧的。
select * from information_schema.INNODB_LOCKS;
真好,我通過這個sql語句起碼發現在同一張表里面得同一個數據有了2個鎖其中一個是X(寫鎖),另外一個是S(讀鎖),我可以跳過這一條數據,使用其他數據做調試
可能如果我就是繞不過,一定就是要用這條數據呢?吼一嗓子吧(哪個缺德的在debug這個表,請不要鎖這不動),好吧,這是個玩笑,其實還有更好的方式來看
select * from sys.innodb_lock_waits
我現在執行的這個sql語句有了,另外看下最下面,kill命令,你在工作中完全可以通過kill吧阻塞了的sql語句給干掉,你就可以繼續運行了,不過這命令也要注意使用過,如果某同事正在做比較重要的調試,你kill,被他發現可能會被暴打一頓。
上面的解決方案不錯,但如果你的MySQL不是5.7的版本呢?是5.6呢,你根本就沒有sys庫,這個時候就難辦了,不過也是有辦法的。
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread
FROM
information_schema.innodb_lock_waits w
INNER JOIN
information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN
information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
看到沒有,接下來你是否也可以執行kill 29 這樣的大招了。