如何处理高并发写入mysql_如何处理高并发情况下的DB插入
轉(zhuǎn)載以便以后學(xué)習(xí)使用,謝謝!
插入數(shù)據(jù)庫(kù),在大家開(kāi)發(fā)過(guò)程中是很經(jīng)常的事情,假設(shè)我們有這么一個(gè)需求:
1、??我們需要接收一個(gè)外部的訂單,而這個(gè)訂單號(hào)是不允許重復(fù)的
2、??數(shù)據(jù)庫(kù)對(duì)外部訂單號(hào)沒(méi)有做唯一性約束
3、??外部經(jīng)常插入相同的訂單,對(duì)于已經(jīng)存在的訂單則拒絕處理
對(duì)于這個(gè)需求,很簡(jiǎn)單我們會(huì)用下面的代碼進(jìn)行處理(思路:先查找數(shù)據(jù)庫(kù),如果數(shù)據(jù)庫(kù)存在則直接退出,否則插入)
packagecom.yhj.test;
importcom.yhj.dao.OrderDao;
importcom.yhj.pojo.Order;
/**
*?@Description:并發(fā)測(cè)試用例
*?@AuthorYHJ??create at 2011-7-7?上午08:41:44
*?@FileNamecom.yhj.test.TestCase.java
*/
publicclassTestCase {
/**
* data access object class for deal order
*/
privateOrderDao?orderDao;
/**
*?@Description:插入測(cè)試
*?@paramobject?要插入的object實(shí)例
*?@authorYHJ create at 2011-7-7?上午08:43:15
*?@throwsException
*/
publicvoiddoTestForInsert(Order order)?throwsException {
Order orderInDB =?orderDao.findByName(order.getOrderNo());
if(null!= orderInDB)
thrownewException("the order has been exist!");
orderDao.save(order);
}
}
這樣很顯然,在單線程下是沒(méi)問(wèn)題的,但是多線程情況下就會(huì)出現(xiàn)一個(gè)問(wèn)題,線程1先去訪問(wèn)DB,查找沒(méi)有,開(kāi)始插入,這時(shí)候線程2又來(lái)查找DB,而此時(shí)線程1插入的事務(wù)還沒(méi)有提交,線程2沒(méi)有查到該數(shù)據(jù),也進(jìn)行插入,于是,問(wèn)題出現(xiàn)了,插入了2條一樣訂單。
對(duì)于這種情況,好像如果不用數(shù)據(jù)庫(kù)做唯一性約束又不借助外部其他的一些工具,是沒(méi)有辦法實(shí)現(xiàn)的。那怎么做呢?
引入緩存,我們看下面的代碼
packagecom.yhj.test;
importcom.yhj.dao.OrderDao;
importcom.yhj.pojo.Order;
importcom.yhj.util.MemcacheUtil;
importcom.yhj.util.MemcacheUtil.UNIT;
/**
*?@Description:并發(fā)測(cè)試用例
*?@AuthorYHJ??create at 2011-7-7?上午08:41:44
*?@FileNamecom.yhj.test.TestCase.java
*/
publicclassTestCase {
/**
* data access object class for deal order
*/
privateOrderDao?orderDao;
/**
*?@Description:插入測(cè)試
*?@paramobject?要插入的object實(shí)例
*?@authorYHJ create at 2011-7-7?上午08:43:15
*?@throwsException
*/
publicvoiddoTestForInsert(Order order){
String key=null;
try{
Order orderInDB =?orderDao.findByName(order.getOrderNo());
//查DB,如果數(shù)據(jù)庫(kù)已經(jīng)有則拋出異常
if(null!= orderInDB)
thrownewException("the order has been exist!");
key=order.getOrderNo();
//插緩存,原子性操作,插入失敗?表明已經(jīng)存在
if(!MemcacheUtil.add(key, order, MemcacheUtil.getExpiry(UNIT.MINUTE, 1)))
thrownewException("the order has been exist!");
//插DB
orderDao.save(order);
}catch(Exception e) {
e.printStackTrace();
}finally{
MemcacheUtil.del(key);
}
}
}
運(yùn)行步驟如下:
1、??查找數(shù)據(jù)庫(kù),如果數(shù)據(jù)庫(kù)已經(jīng)存在則拋出異常
2、??插入緩存,如果插入失敗則表明緩存中已經(jīng)存在,拋出異常
3、??如果上述2步都沒(méi)有拋出異常,則執(zhí)行插入數(shù)據(jù)庫(kù)的操作
4、??刪除緩存
在并發(fā)的情況下,線程1先查找數(shù)據(jù)庫(kù),發(fā)現(xiàn)沒(méi)有,繼續(xù)執(zhí)行,寫(xiě)緩存,這時(shí)候線程2開(kāi)始查找數(shù)據(jù)庫(kù),發(fā)現(xiàn)沒(méi)有,則寫(xiě)緩存,結(jié)果緩存中已經(jīng)存在,寫(xiě)緩存失敗,拋出異常,返回已存在。線程1執(zhí)行插入數(shù)據(jù)庫(kù)成功,刪除緩存。以后再來(lái)的線程發(fā)現(xiàn)數(shù)據(jù)庫(kù)已經(jīng)存在了,則不在向下執(zhí)行,直接返回.。
機(jī)器異常情況下,不能執(zhí)行finally語(yǔ)句,但是放在memcache中的數(shù)據(jù)會(huì)在1分鐘后超時(shí)。
貌似沒(méi)有問(wèn)題。使用LodeRunner測(cè)試100個(gè)并發(fā)的操作,發(fā)現(xiàn)仍然有重復(fù)的訂單插入,這個(gè)是為什么呢?我們?cè)賮?lái)看這段代碼!
publicvoiddoTestForInsert(Order order){
String key=null;
try{
Order orderInDB =?orderDao.findByName(order.getOrderNo());
//查DB,如果數(shù)據(jù)庫(kù)已經(jīng)有則拋出異常
if(null!= orderInDB)
thrownewException("the order has been exist!");
key=order.getOrderNo();
//插緩存,原子性操作,插入失敗?表明已經(jīng)存在
if(!MemcacheUtil.add(key, order, MemcacheUtil.getExpiry(UNIT.MINUTE, 1)))
thrownewException("the order has been exist!");
//插DB
orderDao.save(order);
}catch(Exception e) {
e.printStackTrace();
}finally{
MemcacheUtil.del(key);
}
}
我們所預(yù)料的是2個(gè)線程同時(shí)操作,假設(shè)有更多的并發(fā)線程呢?
時(shí)刻1:
線程1到達(dá),查數(shù)據(jù)庫(kù),發(fā)現(xiàn)沒(méi)有
時(shí)刻2
線程1寫(xiě)緩存
線程2到達(dá),查數(shù)據(jù)庫(kù)發(fā)現(xiàn)沒(méi)有
時(shí)刻3
線程1緩存寫(xiě)入成功,開(kāi)始寫(xiě)數(shù)據(jù)庫(kù)
線程2開(kāi)始寫(xiě)緩存
線程3到達(dá),查數(shù)據(jù)庫(kù),發(fā)現(xiàn)沒(méi)有
時(shí)刻4
線程1繼續(xù)插入數(shù)據(jù)庫(kù)
線程2寫(xiě)緩存失敗,拋出異常,執(zhí)行finally
線程3開(kāi)始寫(xiě)緩存
時(shí)刻5
線程1插入數(shù)據(jù)庫(kù)成功,開(kāi)始構(gòu)建返回結(jié)果
線程2執(zhí)行finally,刪除緩存,開(kāi)始構(gòu)建返回結(jié)果
線程3發(fā)現(xiàn)緩存不存在(被線程2刪除),寫(xiě)緩存
時(shí)刻6
線程1成功返回
線程2成功返回
線程3寫(xiě)緩存成功,開(kāi)始寫(xiě)數(shù)據(jù)庫(kù)
時(shí)刻7
線程3寫(xiě)數(shù)據(jù)庫(kù)成功,返回
因此上述代碼仍然有插入多條重復(fù)記錄的可能,我們?cè)诓l(fā)20的測(cè)試中發(fā)現(xiàn)成功插入了5筆訂單,其中4筆是不應(yīng)該插入的!
那我們應(yīng)該怎么解決呢?其實(shí)只要解決一個(gè)問(wèn)題,只有插入DB時(shí)候的異常是可以刪除的,其他地方不應(yīng)該刪除,那能不能將代碼改成下面的呢?
publicvoiddoTestForInsert(Order order){
String key=null;
try{
Order orderInDB =?orderDao.findByName(order.getOrderNo());
//查DB,如果數(shù)據(jù)庫(kù)已經(jīng)有則拋出異常
if(null!= orderInDB)
thrownewException("the order has been exist!");
key=order.getOrderNo();
//插緩存,原子性操作,插入失敗?表明已經(jīng)存在
if(!MemcacheUtil.add(key, order, MemcacheUtil.getExpiry(UNIT.MINUTE, 1)))
thrownewException("the order has been exist!");
//插DB
orderDao.save(order);
MemcacheUtil.del(key);
}catch(Exception e) {
e.printStackTrace();
}//finally{
//?????????MemcacheUtil.del(key);
//?????}
}
這樣顯然不行,為什么呢?
這樣是保證了只有插入DB成功了才會(huì)刪除緩存,但是當(dāng)插入DB的時(shí)候發(fā)生了一個(gè)異常,刪除緩存就不會(huì)再執(zhí)行,雖然我們有一分鐘超時(shí),但意味著我們一分鐘內(nèi)該筆訂單是不能再被處理的,而實(shí)際上這邊訂單并沒(méi)有處理成功,所以這樣是不滿足需求的!
繼續(xù)改進(jìn)
代碼如下:加一個(gè)標(biāo)志位
publicvoiddoTestForInsert(Order order){
String key=null;
booleanneedDel=false;
try{
Order orderInDB =?orderDao.findByName(order.getOrderNo());
//查DB,如果數(shù)據(jù)庫(kù)已經(jīng)有則拋出異常
if(null!= orderInDB)
thrownewException("the order has been exist!");
key=order.getOrderNo();
//插緩存,原子性操作,插入失敗?表明已經(jīng)存在
if(!MemcacheUtil.add(key, order,?MemcacheUtil.getExpiry(UNIT.MINUTE, 1)))
thrownewException("the order has been exist!");
needDel=true;
//插DB
orderDao.save(order);
}catch(Exception e) {
e.printStackTrace();
}finally{
if(needDel)
MemcacheUtil.del(key);
}
}
這樣是不是完美解決了呢?
在其他異常執(zhí)行的時(shí)候是不會(huì)刪除緩存的,我們套在之前的代碼上,線程2判斷緩存中存在拋出異常執(zhí)行finally的時(shí)候是不會(huì)刪除緩存的,因此線程3沒(méi)有機(jī)會(huì)執(zhí)行寫(xiě)緩存的操作,從而保證了線程1是唯一能夠插入DB的。
還有沒(méi)有其他漏洞呢?期待大家發(fā)現(xiàn)……
與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的如何处理高并发写入mysql_如何处理高并发情况下的DB插入的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mysql nosql 同步_使用can
- 下一篇: mysql illegal mix of