有一个用户表,字段分别是, id,  username,  password   ,分别记录用户的ID(主键),用户名,和密码。我的业务逻辑中,要求用户名字段,绝对不能有重复。  否则整个系统就完全乱套了。
我之前的办法是,控制层中,在插入新用户之前,先判断一下这个用户名,是否已经存在?如果已经存在,就报错提示给用户,如果不存在,则执行插入操作。伪代码如下:事务开始
if(xxxDao.isUsernameExist("love")){
      return "你输入的用户名已被占用,请尝试其他用户名,谢谢!";
      /** 使用的SQL语句为:
      select * from user where username = 'love'
      */
}else{
      xxxDao.insertUser(user);
      return "用户注册成功,请返回登录页面登录!";
       /** 使用的SQL语句为:
        INSERT INTO user(id,username,password) VALUES('xxxx','love','xxxxxxx')
      */
}
事务结束一开始我自己测试,这段程序运行的灰常完美,什么问题也没有!
但是后来,问题,无情的来了当有多个人,比如100个人,并发访问的时候,就出现问题了:这100个同志,同时注册 "love" 这个用户名,那么他们首先执行 if(xxxDao.isUsernameExist("love")) 这条语句,来判断user表里,是不是存在 love 这个用户名,那么他们同时得到的结果,是不存在,可以注册这个用户名,然后紧接着开始执行 INSERT INTO...操作了。 后果就是我的user表里,出现了100个用户名是 love的记录。
这一下就完蛋了。。  那么我想请教一下各位大神,如果我把以上代码稍微做下修改,修改成下面这样:事务开始
if(xxxDao.isUsernameExist("love")){
      return "你输入的用户名已被占用,请尝试其他用户名,谢谢!";
      /** 使用的SQL语句为:
      select * from user where username = 'love'
      */
}else{
      xxxDao.insertUser(user);
      return "用户注册成功,请返回登录页面登录!";
       /** 使用的SQL语句为:
        INSERT INTO user(id,username,password)   SELECT  'xxxx','love','xxxxxxx'   FROM DUAL WHERE NOT EXISTS ( SELECT * FROM user WHERE username = 'love' )
      */
}
事务结束
这样的话,还会不会存在以上那个并发问题?求大神指点我,尽量的能不用锁就不去用锁,能不去修改事务隔离级别,就尽量不修改,除非实在没办法了

解决方案 »

  1.   

    要做到并发不出问题,必须做到离子级别。只用逻辑代码无法做到防止并发,所以必须用锁。
    这个锁有个技巧,加“动态的锁”。用username这个变量来做锁synchronized(username.intern()){xxxxx},在username后加上intern()方法的调用。这样就可以把相同用户名并发的列为排队,不相同的不用排队。(加在查询判断上面)
      

  2.   

    数据库表设置 username 为 unique 就可以了,重复插入就会插入失败,只有一个能插入成功
      

  3.   

    单用程序的逻辑判断是不够~  username字段加唯一约束~ 包括mysql的锁机制也可以防止并发问题~ 参考:http://blog.csdn.net/speedme/article/details/48525119
      

  4.   

    在isUsernameExist方法中加一个synchronized锁,可以实现100个人方法但是一个一个通过校验
      

  5.   

    不是会有返回的影响的行数吗如果加唯一约束,那你干脆就把判断用户名重复的逻辑去掉,直接用返回的int值来判断好了。
      

  6.   

    不是会有返回的影响的行数吗如果加唯一约束,那你干脆就把判断用户名重复的逻辑去掉,直接用返回的int值来判断好了。+1
      

  7.   


    这个说的准确 。 唯一约束。另外,你的代码也得改下 ,改成如下代码,没必要去else了if(xxxDao.isUsernameExist("love")){
          return "你输入的用户名已被占用,请尝试其他用户名,谢谢!";
    }
    xxxDao.insertUser(user);
    return "用户注册成功,请返回登录页面登录!";
      

  8.   

    个人觉得数据库加唯一约束还是要的,程序层面的synchronized锁,在分布式的环境中还是比较难处理的。如果相同的用户名,数据库会返回影响记录,或者抛对应异常的,程序可以根据这些做出处理提示用户该用户名已被注册。
    至于为什么有数据库约束还要之前用户名的判断的逻辑,个人觉得,主要还是用户体验和性能的问题吧,我们在输入用户名的时候一般这个用户名会通过ajax传送到后台校验,而不至于等用户信息表单填完提交之后再告诉用户说用户名已经存在,表单得重新填。。再者,在第一层面过滤了不合法(用户名重复)的信息的insert,不至于insert之后才发现unique了,在一定的程度上减轻了数据库的负担吧
      

  9.   

    字段加唯一约束完全可以防重,能说之前的系统就这么做过么, tps 300 峰值,外围也没做什么防重机制
      

  10.   

    数据库表设置 username 为 unique 就可以了,重复插入就会插入失败,只有一个能插入成功 
    根据返回的异常类型来处理就可以啦
      

  11.   

    不是会有返回的影响的行数吗如果加唯一约束,那你干脆就把判断用户名重复的逻辑去掉,直接用返回的int值来判断好了。+1虽然数据库加唯一约束,但是去重逻辑我觉得还是可以留下的.这样就不必后头的操作了.毕竟出去并发的情况,重复用户名也还是挺多的.
      

  12.   

    在数据库建唯一约束是简便(lanren)的做法,既然考虑到业务是高并发,为何不妨试试在你的业务逻辑代码里增加一个缓存比如ConcurrentHashMap或者Redis诸如此类 ,获取请求的传参跟缓存里的value值作比较。 不存在就添加进去 存在就直接返回重复(每次连接数据库是要开销的), 然后在验证在数据库中是否重复,不重复插入数据库 并且移除缓存里对应的值。 这样不会锁表也不会影响并发吞吐量。 高并发编程肯定要会考虑性能的优化。