UDN-企业互联网技术人气社区

板块导航

浏览  : 1223
回复  : 0

[讨论交流] 并发环境下mysql插入检查方案

[复制链接]
菊花一朵朵的头像 楼主
发表于 2016-3-4 16:12:13 | 显示全部楼层 |阅读模式

  业务背景:

  基本业务场景是这样的,请求数据(车辆vin信息)进入到接口中,需要先判断其在数据库中的状态,如果库中不存在该vin,或者该vin状态位为“1(已完成)”,则执行一些检查操作后,将数据插入到数据库中,此时新增vin状态为0,调用人工处理接口,十分钟后返回结果,将状态置为1。如果其状态位为“0(正在处理)”则驳回操作,返回提示信息。

  在单线程环境下,这样的业务没有问题,然而当并发访问接口时,会出现同时进入两条vin相同的请求AB,正常情况应该插入一条A,驳回一条B。然而并发环境下,B执行检查状态时A还没有插入,因此AB都进入到了数据库中,数据就错误了。

  解决方案一:

  首先想到的是使用sql处理,对数据库对应字段加唯一索引,保证一致性。如果插入重复的数据,则catch该异常,做出提示。
  1. ALTER tableName ADD UNIQUE [indexName] ON (tableColumns(length))
复制代码

  但是由于业务限制,vin在库中是可以重复的,多条重复数据查询最新,所以不能再vin上添加唯一索引。

  解决方案二:

  使用mysql事务操作,将检查是否存在和插入作为一个事务进行处理,当检查失败的时候,不进行插入。从网上搜索了一下,大致思路如下:
  1. public static void StartTransaction(Connection con, String[] sqls) throws Exception {  
  2.         try {  
  3.             // 事务开始  
  4.             con.setAutoCommit(false);   // 设置连接不自动提交,即用该连接进行的操作都不更新到数据库  
  5.             sm = con.createStatement(); // 创建Statement对象  

  6.             //依次执行传入的SQL语句  
  7.             for (int i = 0; i < sqls.length; i++) {  
  8.                 sm.execute(sqls[i]);// 执行添加事物的语句  
  9.             }  
  10.             con.commit();   // 提交给数据库处理  
  11.             // 事务结束  

  12.         //捕获执行SQL语句组中的异常      
  13.         } catch (SQLException e) {  
  14.             try {  
  15.                 System.out.println("事务执行失败,进行回滚!\n");  
  16.                 con.rollback(); // 若前面某条语句出现异常时,进行回滚,取消前面执行的所有操作  
  17.             } catch (SQLException e1) {  
  18.                 e1.printStackTrace();  
  19.             }  
  20.         } finally {  
  21.             sm.close();  
  22.         }  
  23.     }
复制代码

  但是这样实际上还是没有解决并发的问题,这样只是把两个操作变成了一个原子的sql操作,可以用于同时插入两条数据一致性的情况,但并不适合需求。

  既然sql层面没有解决问题,就考虑从java的并发编程方向解决。

  解决方案三:

  java解决并发问题,首先想到的是使用内置锁或者可重入锁,基本语法如下:

  ·内置锁:

  由于是在Servlet中进行的处理,因此使用synchronized(this)直接处理业务代码,使得并发情况下,只能有一个线程访问到该段业务代码:
  1. synchronized(this){
  2.     //todo1:检查vin是否存在
  3.     //todo2:如果不存在插入vin
  4. }
复制代码

  ·可重入锁:

  相当于更灵活的内置锁,在这里与内置锁基本相同
  1. public class DashengCallBack extends HttpServlet {
  2.     private static ReentrantLock lock= new ReentrantLock();
  3.     protected void doGet(HttpServletRequest request, HttpServletResponse response){
  4.         lock.lock();
  5.         try{
  6.             //todo1:检查vin是否存在
  7.             //todo2:如果不存在插入vin
  8.         }finally{
  9.             lock.unlock();
  10.         }
  11.     }
  12. }
复制代码

  经过测试,这个方案是可行的,最终没有采用的原因是因为直接使用这种方式加锁,加锁的代码太多,影响效率。

  解决方案四:

  设置一个查询Map,插入前存储数据,插入后删除数据,代码如下:
  1. ConcurrentHashMap<String, String> vinMap=new ConcurrentHashMap<String,String>();
  2.         if(vinMap.containsKey(vin)){
  3.             // todo1: vin 请求完毕后, 从vinInRequestMap里删掉这个vinNo
  4.             // todo2: 返回正在查询
  5.         }
  6.         vinMap.put(vin, "");
  7.         //todo3:插入vin到数据库
  8.         vinMap.remove(vin);
  9.     }
复制代码

  这个方案基本满足了业务需求,唯一的问题是要求接口的更新时间要与业务时间错开,否则更新接口会清空vinMap,导致库中数据混乱,出现错误。

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部