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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

如何处理高并发写入mysql_如何处理高并发情况下的DB插入

發(fā)布時間:2024/10/8 数据库 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何处理高并发写入mysql_如何处理高并发情况下的DB插入 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載以便以后學習使用,謝謝!

插入數據庫,在大家開發(fā)過程中是很經常的事情,假設我們有這么一個需求:

1、??我們需要接收一個外部的訂單,而這個訂單號是不允許重復的

2、??數據庫對外部訂單號沒有做唯一性約束

3、??外部經常插入相同的訂單,對于已經存在的訂單則拒絕處理

對于這個需求,很簡單我們會用下面的代碼進行處理(思路:先查找數據庫,如果數據庫存在則直接退出,否則插入)

packagecom.yhj.test;

importcom.yhj.dao.OrderDao;

importcom.yhj.pojo.Order;

/**

*?@Description:并發(fā)測試用例

*?@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:插入測試

*?@paramobject?要插入的object實例

*?@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);

}

}

這樣很顯然,在單線程下是沒問題的,但是多線程情況下就會出現一個問題,線程1先去訪問DB,查找沒有,開始插入,這時候線程2又來查找DB,而此時線程1插入的事務還沒有提交,線程2沒有查到該數據,也進行插入,于是,問題出現了,插入了2條一樣訂單。

對于這種情況,好像如果不用數據庫做唯一性約束又不借助外部其他的一些工具,是沒有辦法實現的。那怎么做呢?

引入緩存,我們看下面的代碼

packagecom.yhj.test;

importcom.yhj.dao.OrderDao;

importcom.yhj.pojo.Order;

importcom.yhj.util.MemcacheUtil;

importcom.yhj.util.MemcacheUtil.UNIT;

/**

*?@Description:并發(fā)測試用例

*?@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:插入測試

*?@paramobject?要插入的object實例

*?@authorYHJ create at 2011-7-7?上午08:43:15

*?@throwsException

*/

publicvoiddoTestForInsert(Order order){

String key=null;

try{

Order orderInDB =?orderDao.findByName(order.getOrderNo());

//查DB,如果數據庫已經有則拋出異常

if(null!= orderInDB)

thrownewException("the order has been exist!");

key=order.getOrderNo();

//插緩存,原子性操作,插入失敗?表明已經存在

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);

}

}

}

運行步驟如下:

1、??查找數據庫,如果數據庫已經存在則拋出異常

2、??插入緩存,如果插入失敗則表明緩存中已經存在,拋出異常

3、??如果上述2步都沒有拋出異常,則執(zhí)行插入數據庫的操作

4、??刪除緩存

在并發(fā)的情況下,線程1先查找數據庫,發(fā)現沒有,繼續(xù)執(zhí)行,寫緩存,這時候線程2開始查找數據庫,發(fā)現沒有,則寫緩存,結果緩存中已經存在,寫緩存失敗,拋出異常,返回已存在。線程1執(zhí)行插入數據庫成功,刪除緩存。以后再來的線程發(fā)現數據庫已經存在了,則不在向下執(zhí)行,直接返回.。

機器異常情況下,不能執(zhí)行finally語句,但是放在memcache中的數據會在1分鐘后超時。

貌似沒有問題。使用LodeRunner測試100個并發(fā)的操作,發(fā)現仍然有重復的訂單插入,這個是為什么呢?我們再來看這段代碼!

publicvoiddoTestForInsert(Order order){

String key=null;

try{

Order orderInDB =?orderDao.findByName(order.getOrderNo());

//查DB,如果數據庫已經有則拋出異常

if(null!= orderInDB)

thrownewException("the order has been exist!");

key=order.getOrderNo();

//插緩存,原子性操作,插入失敗?表明已經存在

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);

}

}

我們所預料的是2個線程同時操作,假設有更多的并發(fā)線程呢?

時刻1:

線程1到達,查數據庫,發(fā)現沒有

時刻2

線程1寫緩存

線程2到達,查數據庫發(fā)現沒有

時刻3

線程1緩存寫入成功,開始寫數據庫

線程2開始寫緩存

線程3到達,查數據庫,發(fā)現沒有

時刻4

線程1繼續(xù)插入數據庫

線程2寫緩存失敗,拋出異常,執(zhí)行finally

線程3開始寫緩存

時刻5

線程1插入數據庫成功,開始構建返回結果

線程2執(zhí)行finally,刪除緩存,開始構建返回結果

線程3發(fā)現緩存不存在(被線程2刪除),寫緩存

時刻6

線程1成功返回

線程2成功返回

線程3寫緩存成功,開始寫數據庫

時刻7

線程3寫數據庫成功,返回

因此上述代碼仍然有插入多條重復記錄的可能,我們在并發(fā)20的測試中發(fā)現成功插入了5筆訂單,其中4筆是不應該插入的!

那我們應該怎么解決呢?其實只要解決一個問題,只有插入DB時候的異常是可以刪除的,其他地方不應該刪除,那能不能將代碼改成下面的呢?

publicvoiddoTestForInsert(Order order){

String key=null;

try{

Order orderInDB =?orderDao.findByName(order.getOrderNo());

//查DB,如果數據庫已經有則拋出異常

if(null!= orderInDB)

thrownewException("the order has been exist!");

key=order.getOrderNo();

//插緩存,原子性操作,插入失敗?表明已經存在

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成功了才會刪除緩存,但是當插入DB的時候發(fā)生了一個異常,刪除緩存就不會再執(zhí)行,雖然我們有一分鐘超時,但意味著我們一分鐘內該筆訂單是不能再被處理的,而實際上這邊訂單并沒有處理成功,所以這樣是不滿足需求的!

繼續(xù)改進

代碼如下:加一個標志位

publicvoiddoTestForInsert(Order order){

String key=null;

booleanneedDel=false;

try{

Order orderInDB =?orderDao.findByName(order.getOrderNo());

//查DB,如果數據庫已經有則拋出異常

if(null!= orderInDB)

thrownewException("the order has been exist!");

key=order.getOrderNo();

//插緩存,原子性操作,插入失敗?表明已經存在

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í)行的時候是不會刪除緩存的,我們套在之前的代碼上,線程2判斷緩存中存在拋出異常執(zhí)行finally的時候是不會刪除緩存的,因此線程3沒有機會執(zhí)行寫緩存的操作,從而保證了線程1是唯一能夠插入DB的。

還有沒有其他漏洞呢?期待大家發(fā)現……

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的如何处理高并发写入mysql_如何处理高并发情况下的DB插入的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。