樂觀鎖與悲觀鎖不同的是,它是一種邏輯上的鎖,而不需要數(shù)據(jù)庫提供鎖機(jī)制來支持
成都創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站設(shè)計(jì)、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的清水網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
當(dāng)數(shù)據(jù)很重要,回滾或重試一次需要很大的開銷時(shí),需要保證操作的ACID性質(zhì),此時(shí)應(yīng)該采用悲觀鎖
而當(dāng)數(shù)據(jù)對即時(shí)的一致性要求不高,重試一次不太影響整體性能時(shí),可以采用樂觀鎖來保證最終一致性,同時(shí)有利于提高并發(fā)性
通常,樂觀鎖采用版本號/時(shí)間戳的形式實(shí)現(xiàn):給數(shù)據(jù)額外增加一個(gè)版本號字段進(jìn)行控制;更新時(shí),若提交的數(shù)據(jù)所帶的版本號與當(dāng)前記錄的版本號一致,則允許變更執(zhí)行并更新版本號;若不一致,則意味著產(chǎn)生沖突,根據(jù)業(yè)務(wù)需求直接丟棄并返回失敗,或者嘗試合并
在MySQL的實(shí)踐中,常見的一種使用樂觀鎖的方法,是在需要使用樂觀鎖的表中,新增一個(gè)version字段
例如:
create table product_amount (
id int not null primary key auto_increment,
product_name varchar(64) not null,
selling_amount int not null,
storing_amount int not null,
version int not null
);
當(dāng)需要更新銷售中的商品數(shù)量(selling_amount)時(shí),使用如下的SQL語句:
update product_amount set selling_amount = #{selling_amount}, version = #{new_version} where id=#{id} and version = #{old_version};
若該語句返回1,則表示更新成功;若返回0,則表示前后的version不一致,產(chǎn)生沖突,更新失敗
對于更新倉庫中的商品數(shù)據(jù)(storing_amount)時(shí),也是同理
不過,這樣為每行記錄都統(tǒng)一設(shè)置一個(gè)version字段的樂觀鎖方式,存在一個(gè)問題:上例中,如果同時(shí)需要單獨(dú)對selling_amount及storing_amount進(jìn)行update(兩條SQL語句分別單獨(dú)執(zhí)行),那么后執(zhí)行的一條會(huì)因?yàn)橄葓?zhí)行的一條更新了version字段而失敗,而這種失敗顯然是沒有必要的,白白浪費(fèi)了開銷
一種比較好的方式是為每個(gè)需要樂觀鎖的字段單獨(dú)設(shè)置版本號,例如對上例的改造:
create table product_amount (
id int not null primary key auto_increment,
product_name varchar(64) not null,
selling_amount int not null,
selling_version int not null,
storing_amount int not null,
storing_version int not null
);
selling_amount和storing_amount分別擁有自己的樂觀鎖版本號(selling_version和storing_version),更新時(shí)分別只關(guān)注自己的版本號,這樣就不會(huì)因?yàn)榘姹咎柋黄渌侄涡薷亩?,提高了并發(fā)性
Record Lock: 單個(gè)行記錄上的鎖
Gap Lock :間隙鎖,鎖定一個(gè)范圍,但不包含記錄本身
Next-Key Lock:Gap Lock + Record Lock,鎖定一個(gè)范圍,并且包含記錄本身
Record Lock會(huì)鎖住索引記錄,如果建表時(shí)沒有設(shè)置添加索引,Innodb會(huì)去鎖定隱式的主鍵。
如果兩個(gè)程序都向表中寫數(shù)據(jù)顯然會(huì)造成很大的麻煩,甚至?xí)幸馔馇闆r發(fā)生。如果表正由一個(gè)程序?qū)懭?,同時(shí)進(jìn)行讀取的另一個(gè)程序也會(huì)產(chǎn)生混亂的結(jié)果。
鎖定表的方法
防止客戶機(jī)的請求互相干擾或者服務(wù)器與維護(hù)程序相互干擾的方法主要有多種。如果你關(guān)閉數(shù)據(jù)庫,就可以保證服務(wù)器
和myisamchk和isamchk之間沒有交互作用。但是停止服務(wù)器的運(yùn)行并不是一個(gè)好注意,因?yàn)檫@樣做會(huì)使得沒有故障的數(shù)據(jù)庫和表也不可用。本節(jié)主
要討論的過程,是避免服務(wù)器和myisamchk或isamchk之間的交互作用。實(shí)現(xiàn)這種功能的方法是對表進(jìn)行鎖定。
服務(wù)器由兩種表的鎖定方法:
1.內(nèi)部鎖定
內(nèi)部鎖定可以避免客戶機(jī)的請求相互干擾——例如,避免客戶機(jī)的SELECT查詢被另一個(gè)客戶機(jī)的UPDATE查詢所干擾。也可以利用內(nèi)部鎖定機(jī)制防止服務(wù)器在利用myisamchk或isamchk檢查或修復(fù)表時(shí)對表的訪問。
語法:鎖定表:LOCK TABLES
tbl_name {READ | WRITE},[ tbl_name {READ | WRITE},…]
解鎖表:UNLOCKTABLESLOCKTABLES為當(dāng)前線程鎖定表。UNLOCK TABLES釋放被當(dāng)前線程持有的任何鎖。當(dāng)線程發(fā)出另外一個(gè)LOCK
TABLES時(shí),或當(dāng)服務(wù)器的連接被關(guān)閉時(shí),當(dāng)前線程鎖定的所有表自動(dòng)被解鎖。
如果一個(gè)線程獲得在一個(gè)表上的一個(gè)READ鎖,該線程(和所有其他線程)只能從表中讀。如果一個(gè)線程獲得一個(gè)表上的一個(gè)WRITE鎖,那么只有持鎖的線程READ或WRITE表,其他線程被阻止。
每個(gè)線程等待(沒有超時(shí))直到它獲得它請求的所有鎖。
WRITE鎖通常比READ鎖有更高的優(yōu)先級,以確保更改盡快被處理。這意味著,如果一個(gè)線程獲得READ鎖,并且然后另外一個(gè)線程請求一個(gè)WRITE鎖,
隨后的READ鎖請求將等待直到WRITE線程得到了鎖并且釋放了它。
顯然對于檢查,你只需要獲得讀鎖。再者鐘情跨下,只能讀取表,但不能修改它,因此他也允許其它客戶機(jī)讀取表。對于修復(fù),你必須獲得些所以防止任何客戶機(jī)在你對表進(jìn)行操作時(shí)修改它。
2.外部鎖定
服務(wù)器還可以使用外部鎖定(文件級鎖)來防止其它程序在服務(wù)器使用表時(shí)修改文件。通常,在表的檢查操作中服務(wù)器
將外部鎖定與myisamchk或isamchk作合使用。但是,外部鎖定在某些系統(tǒng)中是禁用的,因?yàn)樗荒芸煽康倪M(jìn)行工作。對運(yùn)行myisamchk或
isamchk所選擇的過程取決于服務(wù)器是否能使用外部鎖定。如果不使用,則必修使用內(nèi)部鎖定協(xié)議。
如果服務(wù)器用--skip-locking選項(xiàng)運(yùn)行,則外部鎖定禁用。該選項(xiàng)在某些系統(tǒng)中是缺省的,如Linux??梢酝ㄟ^運(yùn)行mysqladmin
variables命令確定服務(wù)器是否能夠使用外部鎖定。檢查skip_locking變量的值并按以下方法進(jìn)行:
◆
如果skip_locking為off,則外部鎖定有效您可以繼續(xù)并運(yùn)行人和一個(gè)實(shí)用程序來檢查表。服務(wù)器和實(shí)用程序?qū)⒑献鲗Ρ磉M(jìn)行訪問。但是,運(yùn)行任何
一個(gè)實(shí)用程序之前,應(yīng)該使用mysqladmin flush-tables。為了修復(fù)表,應(yīng)該使用表的修復(fù)鎖定協(xié)議。
◆
如果skip_locaking為on,則禁用外部鎖定,所以在myisamchk或isamchk檢查修復(fù)表示服務(wù)器并不知道,最好關(guān)閉服務(wù)器。如果堅(jiān)
持是服務(wù)器保持開啟狀態(tài),月確保在您使用此表示沒有客戶機(jī)來訪問它。