現在我有乙個購買商品的需求,我們知道當我們購買商品時,後台會進行減庫存和增加購買記錄的操作。我們分別在無鎖和樂觀鎖和悲觀鎖進行相應的**演示來說明問題。
建表語句如下:
create table `stock` (
`id` int(11) unsigned not null auto_increment,
`name` varchar(50) not null default '' comment '名稱',
`count` int(11) not null comment '庫存',
`sale` int(11) not null comment '已售',
`version` int(11) not null comment '樂觀鎖,版本號',
primary key (`id`)
) engine=innodb auto_increment=2 default charset=utf8
create table `stock_order` (
`id` int(11) unsigned not null auto_increment,
`sid` int(11) not null comment '庫存id',
`name` varchar(30) not null default '' comment '商品名稱',
`create_time` timestamp not null default current_timestamp on update current_timestamp comment '建立時間',
primary key (`id`)
) engine=innodb auto_increment=981 default charset=utf8
一、無鎖的mysql:
先看**:
public class test2 catch (exception e)}};
}for (int i=0;i<100;i++)
} public void service() throws exception
}
從上述**可以看到,有一百個執行緒去模擬一百個使用者購買商品,資料庫中只有10個商品,所以當商品賣完時,應該增加10條購買記錄。為了讓大家看個清楚,我在**中加入了執行緒的睡眠。
我們看到,增加了11條記錄,也就是所謂的超賣現象,商家絕不可能允許這種情況的發生。
mysql的樂觀鎖:
我們在使用樂觀鎖時會假設在極大多數情況下不會形成衝突,只有在資料提交的時候,才會對資料是否產生衝突進行檢驗。如果資料產生衝突了,則返回錯誤資訊,進行相應的處理。
實現:mysql最經常使用的樂觀鎖時進行版本控制,也就是在資料庫表中增加一列,記為version,當我們將資料讀出時,將版本號一併讀出,當資料進行更新時,會對這個版本號進行加1,當我們提交資料時,會判斷資料庫表中當前的version列值和當時讀出的version是否相同,若相同說明沒有進行更新的操作,不然,則取消這次的操作。
public class test };}
for (int i=0;i<200;i++)
}public void service()catch (exception e)
}private void createorder(connection connection) throws sqlexception
private void updatecountbyopti(connection connection,stock stock) throws sqlexception
public stock checkstock(connection connection) throws sqlexception
if (stock.getcount()<1)
throw new runtimeexception("沒有庫存了");
return stock;}}
上述在提交時,對version欄位進行了比較,當資料庫中的version和之前讀取的version一樣才會進行提交,否則提交失敗,接下來進行測試。
可以看到,只有10條記錄,樂觀鎖保證了資料的一致性。
三、悲觀鎖
mysql的悲觀鎖就是開啟事務,當啟動事務時,如果事務中的sql語句涉及到索引並用索引進行了條件判斷,那麼會使用行級鎖鎖定所要修改的行,否則使用表鎖鎖住整張表。
public class test catch (sqlexception e) }};
}for (int i=0;i<200;i++)
}public void service() throws sqlexception catch (exception e)
}private void createorder(connection connection) throws sqlexception
private void updatecountbyopti(connection connection,stock stock) throws sqlexception
public stock checkstock(connection connection) throws sqlexception, interruptedexception
if (stock.getcount()<1)
throw new runtimeexception("沒有庫存了");
return stock;}}
開啟事務並不難,所以使用悲觀鎖很簡單,讓我們看一下結果
結果還是10條記錄
我們可以在不同的場合使用不同的處理方法,樂觀鎖併發高並且效能也很好,而悲觀鎖雖然併發不是很高,但是它不允許髒讀,所以各有各的優點。
樂觀鎖和悲觀鎖看名稱挺高大上的,面試的時候一些面試官最喜歡拿這個來考應聘者。這個也是有意義的,乙個php程式設計師如果沒有接觸過樂觀鎖和非觀鎖,那麼他根本沒有接觸過像樣的業務了。
什麼是悲觀鎖?看意思就是比較,不相信其它的人不會改,所以查詢的時候就加鎖,然後自己更新資料再釋放鎖。可以有效防止減庫存衝突問題。
什麼是樂觀鎖?相反就是認為沒幾個人用,基本不會碰到有人修改,所以查詢時就不加鎖,然後更新的時候判斷一下資料是不是被改掉。
在php+mysql程式中怎麼實現呢?**也很簡單。
悲觀鎖的業務流程(以商品表和sku表減庫存並進行其它操作為類):
---事務開始
1,查詢商品表、鎖定表:for update
2,判斷商品庫存是否大於購買資料,
3,如果庫存滿足,減少商品表庫存。(不滿足就回滾事務了。)
4,減少商品sku表庫存
5,記錄訂單操作記錄等
--事務提交(事務提交時即釋放鎖)。
可見上面的流程中整個被鎖的週期是比較長的。如果改為樂觀鎖呢:
----事務開始
1,查詢商品表
2,判斷商品庫存是否大於購買資料,
3,如果庫存滿足,減少商品表庫存(條件是商品表的庫存=1中查詢出的庫存)
比如:update product set store = store - where store = ($product->store為1查詢得到的庫存)
4,減少商品sku表庫存(同樣需要判斷條件)
5,記錄訂單操作記錄等
--事務提交(事務提交時即釋放鎖)。
可以看到樂觀鎖整個過程中實際並未執行任何加鎖,我也不知道為什麼會有這樣的稱呼。實際它是一種判斷衝突的有效手段。
在上面的樂觀鎖的執行流程中,如果3、4、5這三步中的任何一步發生異常,都會因滾事務。這樣就不會出現減庫存衝突導致庫存髒資料了。
在使用樂觀鎖時,要考慮進一步,就是在樂觀鎖時如果發現資料被修改,更新失敗時,要考慮再重新獲取資料,重新判斷重新更新。這樣就不會因為更新失敗導致此筆業務失敗,而相當於把它立即加進到下一步的佇列而在同步請求中即能得到解決。
兩種鎖各有各的好,建議電商**起步時訪問量不大,不會造成壓力時使用悲觀鎖。因為這時沒有什麼高併發,但也要好好檢查**防止出現死鎖。
對於成熟的電商**,必須面對高併發的情況下,應該使用樂觀鎖。
mysql悲觀鎖和樂觀鎖
mysql鎖機制分為表級鎖和行級鎖,本文就和大家分享一下我對mysql中行級鎖中的共享鎖與排他鎖進行分享交流。共享鎖又稱為讀鎖,簡稱s鎖,顧名思義,共享鎖就是多個事務對於同一資料可以共享一把鎖,都能訪問到資料,但是只能讀不能修改。排他鎖又稱為寫鎖,簡稱x鎖,顧名思義,排他鎖就是不能與其他所並存,如乙...
MySQL悲觀鎖和樂觀鎖
引言 之所以叫做悲觀鎖,是因為這是一種對資料的修改抱有悲觀態度的併發控制方式。我們一般認為資料被併發修改的概率比較大,所以需要在修改之前先加鎖。例子 0.開始事務 begin 1.查詢出商品庫存資訊 select quantity from items where id 1 for update 2...
mysql的悲觀鎖和樂觀鎖
丟失更新 兩個使用者 或以上 對同乙個資料物件操作引起的資料丟失。解決方案 1.悲觀鎖,假設丟失更新一定存在 sql後面加上for update 這是資料庫的一種機制。2.樂觀鎖,假設丟失更新不一定發生。update時候存在版本,更新時候按版本號進行更新。樂觀鎖不是資料庫自帶的,需要我們自己去實現。...