日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Innodb事务和锁

發(fā)布時間:2024/4/14 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Innodb事务和锁 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

事務(wù)概念

數(shù)據(jù)庫操作的最小工作單元,是作為單個邏輯工作單元執(zhí)行的一系列操作,經(jīng)典的事務(wù)場景是轉(zhuǎn)賬,A(id為3)給B(id為1)轉(zhuǎn)賬:

update user_account set balance = balance - 1000 where user_id = 3; update user_account set balance = balance + 1000 where user_id = 1;

這兩個sql要保證必須同時成功或同時失敗,否則數(shù)據(jù)將出現(xiàn)不一致的情況。

mysql中的事務(wù)

查看mysql事務(wù)開啟狀態(tài):

show variables like 'autocommit'

默認(rèn)是ON。

mysql中開啟事務(wù)
會話級別
set session autocommit = on/off;

這個是對當(dāng)前會話設(shè)置自動提交,對其他會話不起作用,如果設(shè)置為off,這時候執(zhí)行完sql后,當(dāng)前會話都要手動加上commit才能提交事務(wù)。

手動開啟

手動執(zhí)行sql:

開啟事務(wù):begin / start transaction; 提交或回滾事務(wù):commit / rollback

JDBC編程中:

connection.setAutoCommit(boolean); connection.commit();

Spring事務(wù)AOP編程:

expression=execution(com.gpedu.dao.*.*(..))

mysql中默認(rèn)是自動提交事務(wù)的。也就是在你執(zhí)行sql語句的時候,它會自動在你sql前邊加上beginstart transaction;在后邊自動加上commit;從而事務(wù)就會自動提交。

當(dāng)手動使用begin或start transaction時,mysql就會取消自動加事務(wù),例如:

begin; update user set name="faith" where id=1;

執(zhí)行后,數(shù)據(jù)庫id為1的記錄并不會改變,因為這時候mysql不會自動提交,當(dāng)手動執(zhí)行commit之后才會進(jìn)行提交。

而因為mysql自動提交事務(wù),所以如下兩個sql實際上是在兩個事務(wù)中的:

update user_account set balance = balance - 1000 where userID = 3; update user_account set balance = balance +1000 where userID = 1;

那么為了保證原子性,我們需要做如下操作:

begin; update user_account set balance = balance - 1000 where userID = 3; update user_account set balance = balance +1000 where userID = 1; commit;

這樣就把這兩個sql放到一個事務(wù)中去了。

在jdbc中將兩個sql放到一個事務(wù)中,如下:

connection.setAutoCommit(boolean); update user_account set balance = balance - 1000 where userID = 3; update user_account set balance = balance +1000 where userID = 1; connection.commit();

Spring事務(wù)AOP編程,實際上也是做了手動開啟操作:

expression=execution(com.faith.dao.*.*(..))

這里設(shè)置了一個切面,基于這個切面的所有方法都會被攔截,這些方法配置事務(wù)的傳播性質(zhì),攔截之后,spring會在方法之前加一個切面,設(shè)置會話手動提交,例如:

connection.setAutoCommit(boolean);

然后在方法之后加一個切面,設(shè)置會話提交,例如:

connection.commit();

當(dāng)catch到異常的時候,就執(zhí)行

connection.rollback();

事務(wù)的特性

原子性(Atomicity)

事務(wù)是最小的工作單元,事務(wù)中的sql要么一起提交成功,要么全部失敗回滾。

一致性(Consistency)

事務(wù)中操作的數(shù)據(jù)及狀態(tài)改變是一致的,更新的數(shù)據(jù)必須完全符合預(yù)設(shè)的規(guī)則,不會因為事務(wù)或系統(tǒng)等原因?qū)е聽顟B(tài)的不一致。

隔離性(Isolation)

一個事務(wù)所操作的數(shù)據(jù)在提交之前,對其他事務(wù)的可見性設(shè)定。如果事務(wù)并發(fā)且相互不隔離,會導(dǎo)致臟讀、不可重復(fù)讀、幻讀等系列問題。

持久性(Durability)

事務(wù)所做的修改會永久保存,不會因為系統(tǒng)意外導(dǎo)致數(shù)據(jù)的丟失。

原子性和一致性是兩個不同的概念。對于原子性來說,如下的兩條語句:

update user_account set balance = balance - 1000 where userID = 3; update user_account set balance = balance +10000 where userID = 1;

只要同時執(zhí)行成功或失敗就是符合原子性的。

而對于一致性來說是不成立的,因為實際給1轉(zhuǎn)賬1000,但是1的賬戶上多了10000,不符合轉(zhuǎn)賬的規(guī)則,導(dǎo)致了數(shù)據(jù)的不一致性。

事務(wù)的隔離級別

SQL92 ANSI/ISO標(biāo)準(zhǔn)

http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt Read Uncommitted(讀未提交)--未解決任何并發(fā)問題,可以讀到其他事務(wù)未提交的數(shù)據(jù),會導(dǎo)致臟讀(dirty read)。Read Committed(讀已提交)--解決臟讀問題,一個事務(wù)開始之后,只能看到己提交的事務(wù)所做的修改,但是未解決不可重復(fù)讀(nonrepeatable read)。Repeatable Read (可重復(fù)讀)--解決不可重復(fù)讀問題,在同一個事務(wù)中多次讀取同樣的數(shù)據(jù)結(jié)果是一樣的,這種隔離級別未定義解決幻讀的問題。Serializable(串行化)--解決所有問題,最高的隔離級別,通過強制事務(wù)的串行執(zhí)行。

innodb對隔離級別的支持

隔離級別臟讀不可重復(fù)讀幻讀
讀未提交可能可能可能
讀已提交不可能不可能可能
可重復(fù)讀不可能不可能==不可能==
串行化不可能不可能不可能

在92標(biāo)準(zhǔn)中,可重復(fù)讀級別可以不解決幻讀問題,但是innodb存儲引擎的可重復(fù)讀解決了幻讀問題。

鎖用來管理不同事務(wù)對共享資源的并發(fā)訪問。

表鎖與行鎖:

鎖定粒度:表鎖 > 行鎖表鎖直接鎖定表,行鎖只鎖定一行。加鎖效率:表鎖 > 行鎖直接對表加鎖塊,而行鎖需要在表中找到指定的行記錄。沖突概率:表鎖 > 行鎖,表鎖鎖定的記錄更多,更容易產(chǎn)生沖突。并發(fā)性能:表鎖 < 行鎖

InnoDB存儲引擎只有行鎖,沒有表鎖,但它也能實現(xiàn)表鎖的效果,因為它的表鎖是把表中所有的行都鎖一遍,就成了表鎖。這個只是實現(xiàn)了表鎖的效果,但是和真正的表鎖效率相比要低下很多。

innodb的鎖類型

InnoDB默認(rèn)select語句不加任何鎖類型,但是delete、update、insert 默認(rèn)會加上X鎖。

innodb共有八種鎖:

共享鎖(行鎖):Shared Locks排它鎖(行鎖):Exclusive Locks意向鎖共享鎖(表鎖):Intention Shared Locks意向鎖排它鎖(表鎖):Intention Exclusive Locks自增鎖:AUTO-INC Locks關(guān)于行鎖的鎖:記錄鎖 Record Locks間隙鎖 Gap Locks臨鍵鎖 Next-key Locks
共享鎖

又稱為讀鎖,簡稱S鎖,多個事務(wù)對于同一數(shù)據(jù)可以共享一把共享鎖,持有共享鎖的事務(wù)都能訪問到數(shù)據(jù),但是只能讀不能修改。

共享鎖示例:

begin; select * from user WHERE id=1 LOCK IN SHARE MODE;

不執(zhí)行commit操作,這時候在另一個窗口執(zhí)行:

select * from user WHERE id=1; update user set name='2' where id=1;

select操作可以查到數(shù)據(jù),但是update會被阻塞,直到最開始申請到共享鎖的事務(wù)執(zhí)行commit或rollback來釋放享鎖,之后update才會繼續(xù)執(zhí)行。

排他鎖

又稱為寫鎖,簡稱X鎖,排他鎖不能與其他鎖并存,如一個事務(wù)獲取了某條記錄的排他鎖,其他事務(wù)就不能再獲取該行的鎖(共享鎖、排他鎖),即不能讀不能寫,只有持有排他鎖的事務(wù)才可以對記錄進(jìn)行讀取和修改(其他事務(wù)要讀取數(shù)據(jù)可來自于快照)。

排它鎖示例:

begin; update user set name='2' where id=1;

不執(zhí)行commit操作,這時候在另一個窗口執(zhí)行:

select * from user WHERE id=1 LOCK IN SHARE MODE; update user set name='3' where id=1;

這兩條操作都會被阻塞,直至持有排它鎖的事務(wù)commit或rollback之后才能繼續(xù)執(zhí)行。

意向共享鎖(IS)

表示事務(wù)準(zhǔn)備給數(shù)據(jù)行加入共享鎖,即一個數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖,意向共享鎖之間是可以相互兼容的。

意向排它鎖(IX)

表示事務(wù)準(zhǔn)備給數(shù)據(jù)行加入排他鎖,即一個數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖,意向排它鎖之間也是可以相互兼容的。

意向鎖(IS 、IX)是InnoDB在數(shù)據(jù)操作之前自動加的,不需要用戶干預(yù),我們編碼時不需要對意向鎖進(jìn)行特殊處理。

意向鎖是表級鎖,之間是相互兼容的,也就是說多個持有鎖的線程,可以同時持有意向鎖。比如update id=1,update id=2,他們可以同時持有意向鎖。

意向鎖存在的意義

只有當(dāng)事務(wù)想去進(jìn)行鎖表時,意向鎖才會發(fā)揮作用,事務(wù)會先判斷意向鎖是否存在,如果存在,說明此時肯定有行鎖存在,這時候不能進(jìn)行表鎖,則可快速返回該表不能啟用表鎖,省略了進(jìn)入底層掃描全表的數(shù)據(jù)。

自增鎖

針對自增列自增長的一個特殊的表級別鎖,可以使用如下語句查看自增鎖的模式:

show variables like 'innodb_autoinc_lock_mode';

此參數(shù)可取的值有三個:0、1、2,默認(rèn)取值1。

取值0:傳統(tǒng)方式,串行自增,并且是連續(xù)的。這種模式下需要語句執(zhí)行完成才釋放自增鎖,所以性能最低。例如:1、2、3、4、5、6,沒有人為刪除情況下,表中id字段一定是連續(xù)的。

取值1:連續(xù)方式,自增的,并且是連續(xù)的。當(dāng)語句申請到自增鎖就釋放自增鎖,自增鎖就可以給其它語句使用,性能會好很多。但因為不會等待語句事務(wù)執(zhí)行完畢就釋放了自增鎖,可能該事務(wù)回滾了,所以id可能會出現(xiàn)斷續(xù)的情況,例如:1、2、6,8,10

2:交錯方式,多語句插入數(shù)據(jù)時,有可能自增的序列號和執(zhí)行先后順不一致,并且中間可能有斷裂。一次分配一批自增值,然后下個語句就再進(jìn)行分配一批自增值,阻塞很小,性能很高。例如:1、2、3、6、5。

設(shè)置為2時,需要確認(rèn)表是否需要連續(xù)的自增值,如果需要,就不要使用這個值。

臨鍵鎖(Next-key locks)

當(dāng)sql執(zhí)行按照索引進(jìn)行檢索,查詢條件為范圍查找(between and、<、>等)并且有數(shù)據(jù)命中,則此時SQL語句加上的鎖為Next-key locks,鎖住索引范圍為記錄的本區(qū)間 + 本區(qū)間下一個區(qū)間(左開右閉)。

mysql會對記錄自動劃分出區(qū)間,如下:

如果為1,2,4,7,10,區(qū)間則為(-&,1],(1,2],(2,4],(4,7],(7,10],(10,+&)。劃分區(qū)間是依據(jù)B+樹節(jié)點之間的間隙來劃分的,1和2之間沒有間隙,但是在樹中,是兩個不同的節(jié)點,它們之間是有間隙的。

update user set name=1 where id>5 and id<10

上面的sql選中的記錄是id=7,這時候鎖住的區(qū)間是(4,7],(7,10],(4,7]是本區(qū)間,而(7,10]是本區(qū)間的下一個區(qū)間。

鎖住本區(qū)間和相鄰區(qū)間就是防止幻讀,例如這里的>5和<9條件,肯定是要鎖定(4,7],(7,10]區(qū)間才能實現(xiàn)的,也就是要鎖住本區(qū)間和相鄰區(qū)間。

鎖住區(qū)間是因為B+樹的特性,如果把這個例子中的id換成age就更好理解了。

因為innodb默認(rèn)隔離級別是可重復(fù)讀,而前邊說了innodb的可重復(fù)讀還捎帶解決了幻讀問題,而幻讀問題就是臨鍵鎖配合mvcc一起解決的。

間隙鎖(Gap locks)

當(dāng)sql執(zhí)行按照索引進(jìn)行檢索,查詢條件為范圍查找并且查詢的數(shù)據(jù)不存在,這時SQL語句加上的鎖即為Gap locks,鎖住索引不存在的區(qū)間(左開右開)。

只在可重復(fù)讀隔離級別存在是因為innodb的可重復(fù)讀解決了幻讀問題。

Record locks

當(dāng)sql執(zhí)行按照唯一性(Primary key、Unique key)索引進(jìn)行檢索,查詢條件為精準(zhǔn)等值匹
配且查詢的數(shù)據(jù)是存在,這時SQL語句加上的鎖即為記錄鎖Record locks,這種情況只針對唯一索引,所以對應(yīng)的是const或equ_ref級別的查詢。

innodb的行鎖鎖住了哪些內(nèi)容

得出結(jié)論之前先做個測試。例如user表中,id,name,age,create_time字段,id和name有索引,create_time沒有加索引。

測試1
begin; select * from user where id=1;

不執(zhí)行commit或rollback。然后在其他線程執(zhí)行:

select * from user where id=1; 阻塞 select * from user where id=2; 非阻塞 select * from user where id=3; 非阻塞
測試2
begin; select * from user where name=‘1’;

不執(zhí)行commit或rollback。然后在其他線程執(zhí)行:

select * from user where name=‘1’; 阻塞 select * from user where name=‘2’; 非阻塞 select * from user where name=‘3’; 非阻塞
測試3
begin; select * from user where create_time=1;

不執(zhí)行commit或rollback。然后在其他線程執(zhí)行:

select * from user where create_time=1; 阻塞 select * from user where create_time=2; 阻塞 select * from user where create_time=3; 阻塞

結(jié)論:InnoDB的行鎖是通過給索引樹中的索引項加鎖來實現(xiàn)的,如果是聚集索引,那么直接鎖住聚集索引的索引項,如果是非聚集索引,那么會鎖住當(dāng)前索引的索引項,以及對應(yīng)的聚集索引中的索引項。

也就是說對于非聚集索引,會在兩棵索引樹中分別上鎖。只有通過索引條件進(jìn)行數(shù)據(jù)檢索,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖(鎖住索引的所有記錄)。

表鎖是非常耗費性能的,所以要避免表鎖。這種特性可以給平時寫sql帶來一些啟發(fā),例如:

delete from user where create_time;導(dǎo)致表鎖 delete from user where id=1;行鎖;

寫刪改的sql時候,要考慮where條件是否命中了索引,要避免表鎖出現(xiàn),刪一條記錄,卻導(dǎo)致整張表被鎖住了,這是一件很郁悶的事。

鎖如何解決并發(fā)問題

臟讀

加上X鎖可以解決臟讀。

不可重復(fù)讀

模擬不可重復(fù)讀:

select name from user where id=1;// name='1' update user set name='2' where id=1; // 執(zhí)行成功 select name from user where id=1;// name='2'

上面兩個select的結(jié)果不同,導(dǎo)致了不可重復(fù)讀。解決方法是給這兩個select加上S鎖:

select name from user where id=1 LOCK IN SHARE MODE;// name='1' update user set name='2' where id=1; // 阻塞 select name from user where id=1 LOCK IN SHARE MODE;// name='1'

這樣在當(dāng)前事務(wù)執(zhí)行完畢之前,不可能被其他事務(wù)更改值,從而解決了不可重復(fù)讀的問題。

幻讀

加臨鍵鎖可以解決幻讀。

死鎖

多個并發(fā)事務(wù)每個事務(wù)都持有鎖,每個事務(wù)都需要再繼續(xù)持有其他事務(wù)持有的鎖,但誰都不釋放自己手中的鎖,產(chǎn)生鎖的循環(huán)等待,這就形成了死鎖。

死鎖的避免
類似的業(yè)務(wù)邏輯以固定的順序訪問表和行。大事務(wù)拆小。大事務(wù)更傾向于死鎖,如果業(yè)務(wù)允許,將大事務(wù)拆小。在同一個事務(wù)中,盡可能做到一次鎖定所需要的所有資源,減少死鎖概率。降低隔離級別,如果業(yè)務(wù)允許,將隔離級別調(diào)低也是較好的選擇為表添加合理的索引??梢钥吹饺绻蛔咚饕龑楸淼拿恳恍杏涗浱砑由湘i(或者說是表鎖),表鎖造成的鎖沖突比行鎖要嚴(yán)重的多

總結(jié)

以上是生活随笔為你收集整理的Innodb事务和锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。