起步软件技术论坛
搜索
 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 2297|回复: 1

[分享] 起步官方的SQL操作,没有考虑并发冲突

[复制链接]

64

主题

471

帖子

1127

积分

金牌会员

Rank: 6Rank: 6

积分
1127
发表于 2016-6-16 15:58:12 | 显示全部楼层 |阅读模式
版本: 小版本号:
数据库: 服务器操作系统: 应用服务器:
客户端操作系统: 浏览器:
安装了反编译工具,一步小心看到了官方的BizUtils.getNextSequence函数的源码,再此附上给大家分析(闭源的东西其实问题最多)
  1.     public static int getNextSequence(String paramString)
  2.   {
  3.     String str1 = "update SA_KVSEQUENCE set k='" + paramString + "' where k='" + paramString + "' and $clientFilter(NULL)";
  4.     String str2;
  5.     if (updateKey(str1) > 0)
  6.     {
  7.       str2 = "update SA_KVSEQUENCE set v=v+1 where k='" + paramString + "'  and $clientFilter(NULL)";
  8.       if (updateKey(str2) > 0) {
  9.         return getCurrentSequence(paramString);
  10.       }
  11.     }
  12.     else
  13.     {
  14.       str2 = "insert into SA_KVSEQUENCE (k, v, $clientName) values('" + paramString + "', 1, $clientValue)";
  15.       if (updateKey(str2) > 0) {
  16.         return 1;
  17.       }
  18.     }
  19.     throw BizSystemException.create("JUSTEP180317", new Object[] { paramString });
  20.   }
  21.   
  22.   private static int updateKey(String paramString)
  23.   {
  24.     Connection localConnection = null;
  25.     Statement localStatement = null;
  26.     try
  27.     {
  28.       localConnection = ModelUtils.getConnectionInTransaction("/system/data");
  29.       localStatement = localConnection.createStatement();
  30.       int i = localStatement.executeUpdate(paramString);
  31.       return i;
  32.     }
  33.     catch (Exception localException1)
  34.     {
  35.       throw BizSystemException.create(localException1, "JUSTEP180318", new Object[] { paramString });
  36.     }
  37.     finally
  38.     {
  39.       try
  40.       {
  41.         if (localStatement != null) {
  42.           localStatement.close();
  43.         }
  44.       }
  45.       catch (Exception localException2) {}
  46.       try
  47.       {
  48.         if (localConnection != null) {
  49.           localConnection.close();
  50.         }
  51.       }
  52.       catch (Exception localException3) {}
  53.     }
  54.   }
  55.   
  56.   public static int getCurrentSequence(String paramString)
  57.   {
  58.     String str = "select v from SA_KVSEQUENCE where k='" + paramString + "' and $clientFilter(NULL)";
  59.     Connection localConnection = null;
  60.     Statement localStatement = null;
  61.     try
  62.     {
  63.       localConnection = ModelUtils.getConnection("/system/data");
  64.       localStatement = localConnection.createStatement();
  65.       ResultSet localResultSet = localStatement.executeQuery(str);
  66.       if (localResultSet.next())
  67.       {
  68.         i = localResultSet.getInt("v");
  69.         return i;
  70.       }
  71.       int i = 0;
  72.       return i;
  73.     }
  74.     catch (NamingException localNamingException)
  75.     {
  76.       throw new ModelException(localNamingException.getMessage(), localNamingException);
  77.     }
  78.     catch (SQLException localSQLException)
  79.     {
  80.       throw new ModelException(localSQLException.getMessage(), localSQLException);
  81.     }
  82.     finally
  83.     {
  84.       try
  85.       {
  86.         if (localStatement != null) {
  87.           localStatement.close();
  88.         }
  89.       }
  90.       catch (Exception localException1) {}
  91.       try
  92.       {
  93.         if (localConnection != null) {
  94.           localConnection.close();
  95.         }
  96.       }
  97.       catch (Exception localException2) {}
  98.     }
  99.   }
复制代码

这里贴了4个函数,因为它们之间有相互的调用关系。显然如果发生高并发,这里的执行将可能出现问题,分析如下:
1、如果localConnection变量得到的连接是含事务的,且事务不会关闭,那么这个获取序号的操作将锁死数据库的那条记录,导致同类操作被阻塞,无法并发。
2、如果localConnection变量得到的连接不含事务,那么由于序号是在更新执行后才查询获取的这就可能导致同时被多个请求更新,最后得到了相同的序号。

64

主题

471

帖子

1127

积分

金牌会员

Rank: 6Rank: 6

积分
1127
 楼主| 发表于 2016-6-16 16:04:16 | 显示全部楼层
下面我给出合理的解决方案,是我在BAAS里面写的:
  1.         public static int getSeqInner(String key, Connection conn) throws SQLException {
  2.                 String sql1 = "SELECT V FROM sa_kvsequence WHERE K=? FOR UPDATE";
  3.                 PreparedStatement pstat1 = conn.prepareStatement(sql1);
  4.                 String sql2 = "INSERT INTO sa_kvsequence(K,V) VALUES (?,?)";
  5.                 PreparedStatement pstat2 = conn.prepareStatement(sql2);
  6.                 String sql3 = "UPDATE sa_kvsequence SET V=? WHERE K=? AND V=?";
  7.                 PreparedStatement pstat3 = conn.prepareStatement(sql3);
  8.                 pstat1.setString(1, key);
  9.                 ResultSet rs = pstat1.executeQuery();
  10.                 int seq = 0;
  11.                 int count = 0;
  12.                 if (rs.next()) {
  13.                         seq = rs.getInt(1);
  14.                         count = 1;
  15.                 }
  16.                 rs.close();
  17.                 pstat1.close();
  18.                 if (count == 0) {
  19.                         pstat2.setString(1, key);
  20.                         pstat2.setInt(2, seq + 1);
  21.                         count = pstat2.executeUpdate();
  22.                         pstat2.close();
  23.                 } else {
  24.                         pstat3.setInt(1, seq + 1);
  25.                         pstat3.setString(2, key);
  26.                         pstat3.setInt(3, seq);
  27.                         count = pstat3.executeUpdate();
  28.                         pstat3.close();
  29.                 }
  30.                 if (count == 1)
  31.                         return seq + 1;
  32.                 else
  33.                         return 0;
  34.         }
复制代码

这段代码执行前,需要将自动提交事务设置为false,执行后调用conn.Commit()手动提交事务,确保整个执行过程是在同一个事务中完成的。
这段代码的执行可能报错,也就是发生了冲突,但这不可怕,因为我在获取序号时就考虑到了这点,让它报错,可以在出错时重试(唯一出错可能性是在insert新的序号时并发插入)。如果不支持事务,getSeqInner有可能返回0,说明产生了并发冲突,可以再次重试获取序号。
整个代码比官方写的简洁多了,而且考虑到更新操作的安全性,全部采用了参数化查询,杜绝SQL注入。
回复 支持 反对

使用道具 举报

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

本版积分规则

小黑屋|手机版|X3技术论坛|Justep Inc.    

GMT+8, 2024-5-5 19:39 , Processed in 0.065702 second(s), 24 queries .

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表