zoukankan      html  css  js  c++  java
  • 作为一个程序员,有些知识你应该知道关于并发和数据库封锁

    [原创]作为一个程序员,有些知识你应该知道----关于并发和数据库封锁

             近来碰上件很郁闷的事,国内某CMM3级的开发公司答应给我们的业务系统进行改造,但从4月拖到现在,效率真是低,新的系统又需要旧系统的支持,花了百来万就做出这么个垃圾的JAVA程序,实在忍无可忍,自己操刀上阵,一个人花了一周把他们用j2ee做了半年的程序改造成了 asp .net 程序。
     
     第一步,解决一个他们郁闷了很久,但是连开发人员自己都弄不明白的问题。
     
             现存问题:系统老是会生成2个以上同样的案号。过去一年中发生了5次左右,但是开发人员认为自己写的代码是不可能发生这种事的。
     
     业务需求如下:
             取案号:格式为4位年份+2位月份+2位日期+4位顺序号(超过4位不补0)
     其中顺序号要求每天从1开始,一般一天不会发生1万件以上的案件,所以要求<999的案号前面补0,如果超过1万件,就直接返回顺序号。
     
     表结构
     表名 TradeSerial
     字段1 字符型日期
     字段2 当前顺序号
     
    例如:
         TradeSerial
    20050701    23
     

    以下是他们写的JAVA代码

    public int getTradeSerial() {
        DateTime dt 
    = new DateTime();
        String nowDate 
    = dt.getSystemDate();
        SQL 
    = "select * from TradeSerial";
        ResultSet rs 
    = null;
        
    try {
          rs 
    = query(SQL);
          
    while (rs.next()) {
            TradeSerial 
    = rs.getString("SerialNo");
            TradeDate 
    = rs.getString("TradeDate");
          }

        }

        
    catch (Exception e) {
          
    return -1;
        }

        
    finally {
          closeConnection();
        }


        
    //校验表中日期是否和系统日期相同,相同编号加1否则编号从1计算
        if (TradeDate.equals(nowDate)) {
          TradeSerial 
    = Integer.toString(Integer.parseInt(TradeSerial) + 1);
          SQL 
    = "update TradeSerial set SerialNo=" + TradeSerial;
        }

        
    else{
          TradeSerial
    ="1";
          SQL
    ="update TradeSerial set SerialNo=" + TradeSerial+" , TradeDate="+nowDate;
        }


        
    try {
          execute(SQL);
        }

        
    catch (Exception e) {
          
    return -1;
        }

        
    finally {
          closeConnection();
        }

        
    //日期加编号生成真正业务编号
        TradeSerial="00"+nowDate+fillZero(TradeSerial,3);
        
    return 0;
      }


      
    public String fillZero(String input,int len){
        
    int l=input.length();
        
    if (l>=len)
          
    return input;
        
    while (l<len){
          l
    ++;
          input
    ="0"+input;
        }

        
    return input;
      }



    复查完这段JAVA代码,我查点吐血。居然连事务都没有用。项目组成员JAVA水平差到这个地步无语了。

    问题分析:
     出错原因是没有认识到b/s程序某些情况下是一个多线程程序,当2个用户几乎在同时取案号时,发生了并发问题。

    问题的解决办法:
     临界区封锁,注意,仅用事务并不一定能解决这个问题。
     
     针对这个问题给出以下几个解决的方案:
     
     解决方案1.在程序中用锁技术,使用lock 对临界区代码加锁,保证同一时间只有一个线程执行取案号的代码。
     
     //生成案件编号
      public string  GetAJBH()
      {
      ...  
       lock(this)
       {
        try
        {
         //取案号
        }
        .......
       
      但是这里有一个问题。今后的系统可能不仅仅是一个B/S结构的系统,有些功能可能要在PDA上完成。这时这种方法就不能保证案号不冲突了。
     
      方案2:在数据库里实现。
       显然,这个是最好的解决办法。数据库一但选定,不太会改变。
     
      但是这里又出现2个问题。
      1.MS Sqlserver数据库:对于Sqlserver,只要用事务就可以完成以上功能,如果没有记错的话,SQLSERVER一但开始事务就是使用表封锁了。
     
      2.Oracle数据库。很遗憾,Oracle中仅用事务仍然可能会出现脏读现象。需要人工加锁。
      其DML锁有如下三种封锁方式:
      (1)、共享封锁方式(SHARE)
      (2)、独占封锁方式(EXCLUSIVE)   (注:lock table 表名 in exclusive mode;)
      (3)、共享更新封锁(SHARE UPDATE)(注:只要select ... from 表  for update即可进行封锁)
      这里又有一个问题。那就是你选用那个锁,答案应该是独占封锁方式。
     
      因为共享更新封锁是一种行封锁。针对取案号假设写了如下存储过程:


    create or replace procedure getbh(ajbh  out varcharis
    int;
    bh 
    number default -99;--假设没有值
    CURSOR C1 IS select SERIALNO into bh from TRADESERIAL  where TRADEDATE=to_char(sysdate,'yyyyMMdd'for update;
    begin
         
    FOR I IN C1 LOOP--当天有值,那么值加1
            update TradeSerial set SerialNo=SerialNo+1;
            bh:
    =I.SERIALNO+1;
         
    END LOOP;
         
    if(bh <> -99then--当天有值,返回        
               fillzero(bh,4,ajbh);     
               ajbh:
    =to_char(sysdate,'yyyyMMdd')||ajbh;
               
    return;
         
    else--新的一天了
             bh:=1;
             
    update TradeSerial set SerialNo=1,TRADEDATE=to_char(sysdate,'yyyyMMdd');
         
    end if;
         fillzero(bh,
    4,ajbh); 
         ajbh:
    =to_char(sysdate,'yyyyMMdd')||ajbh;
    end getbh;

    经单步调试试验证明依然无法避免重号,因为如果新的一天开始了,那么表中的日期与当前日期不同,所做的select语句值为空,行封锁就不存在。如果有2个用户同时取案号。那么会取到2个当天的1号案件。所以要用 独占封锁方式。

    create or replace procedure getbh(ajbh  out varcharis
    int;
    bh 
    number default -99;--假设没有值
    CURSOR C1 IS select SERIALNO into bh from TRADESERIAL  where TRADEDATE=to_char(sysdate,'yyyyMMdd');
    begin
    lock 
    table TRADESERIAL in exclusive mode;
         
    FOR I IN C1 LOOP--当天有值,那么值加1
            update TradeSerial set SerialNo=SerialNo+1;
            bh:
    =I.SERIALNO+1;
         
    END LOOP;
         
    if(bh <> -99then--当天有值,返回        
               fillzero(bh,4,ajbh);     
               ajbh:
    =to_char(sysdate,'yyyyMMdd')||ajbh;
               
    return;
         
    else--新的一天了
             bh:=1;
             
    update TradeSerial set SerialNo=1,TRADEDATE=to_char(sysdate,'yyyyMMdd');
         
    end if;
         fillzero(bh,
    4,ajbh); 
         ajbh:
    =to_char(sysdate,'yyyyMMdd')||ajbh;
    end getbh;
     


    感想:上面这个问题只是举的一个小例子,要求看似简单,以为会点数据库操作就OK了,但是深究下去,问题是很复杂的。
            可是目前IT人员大多有一种浮燥的心理。技术层出不穷,什么都想学,但常常浮于表面。而且大学生多了,心态大多都有点问题。学了一周的JSP,asp就号称精通,连学纺织,化工,机械类的人都招来当程序员。
             以前说重学历,但是现在大学教育质量普遍下降,刚出来的毕业生啥也不懂。学历考不住了。
             以前说重经验,但是很多情况是,很多非科班出身的程序员基础差,而且一天到晚忙着挣钱做项目,技术上的东西一点没心思学。结果经验是丰富了。可是所谓的经验却是错误的经验,而且还在不断的积累错误的经验,项目搞糟了,跳槽走人。

  • 相关阅读:
    Linux ansible 常用模块二
    Linux之ansible 常用模块
    flask websocket实现用例
    flask--上下文原理
    python Django
    python 并发编程 锁 / 信号量 / 事件 / 队列(进程间通信(IPC)) /生产者消费者模式
    并发编程 process 模块的方法及运用 僵尸与孤儿
    python 并发编程 操作系统 进程 并发.并行 及 同步/异步,阻塞/非阻塞
    python 网络编程粘包解决方案2 + ftp上传 + socketserver
    第一节 机器学习基础
  • 原文地址:https://www.cnblogs.com/tongzhenhua/p/199129.html
Copyright © 2011-2022 走看看