记一次遇到由于重复提交导致的问题

Posted dirac

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次遇到由于重复提交导致的问题相关的知识,希望对你有一定的参考价值。

这是新手期间第一个上线功能搞出的bug,同时也明白了一个道理:1. 线上环境总是复杂的,不可预知的,一定要做好各种准备; 2. 重要的功能要做放重复提交;3. 基础要打打牢。

需求

需求其实很简单,就是一个修改密码的入口,用户输入提交表单后,如果之前没有设置过密码,则设置,否则修改。然后验证密码的逻辑就不用说了,查出表里的密码,然后对比。
项目是ibatis+oralce,之前DAO的逻辑如下

...
if (isNotExists()){
    insertPassword;
} else {
    updatePassword;
}

isNotExists()方法就是select看是否存在记录。

public Map queryPassword(Map param) throws DAOException {
    return sqlMapClientTemplate.queryForObject(param);
}

问题

某一天中午突然来了告警日志,发现在queryPassword这个方法这里抛出了too many results异常,很显然,本应该每个用户只存在一条记录的,查出了多条。让运维同事查出数据,果然有一个用户,存在3条记录。查看原代码逻辑,那很显然是在判断是否存在记录这里同时来了三个请求,然后三个线程都发现该用户对应的记录是空的,于是同时插入了三条记录。(后来通过电话回访用户,说当时手机页面卡了,于是瞎点一通,可能页面卡了,等反应后一下子发出去多条请求。)

解决和复盘

  • 首先线上问题紧急的解决方案就是删掉记录就OK了。
  • 然后这种if ... then...的用法是非常危险的,一来一回,在并发情况下就出现问题了。当时想着改个密码对同一个用户哪儿来的多并发,先就这样干吧,事实证明线上环境的复杂是永远超出自己预知的。对于这种场景,最好使用oralce提供的merge方法,把事务处理交给oracle,出错的概率更小一些。

    merge into pwd_table t
    using dual
    on (t.id = #id#)
    when matched then
        update pwd_table t set t.pwd = #pwd#
        where t.id = #id#
    when not matched then
        insert into pwd_table t values(#id#, #pwd#)
  • 做防重复提交限制,考虑到用户的群体,最简单的是在用户提交请求得到响应之前,按钮置为diabled状态
  • 基础一定要打好,当时就是对merge了解不深没有使用,要多学习PL/SQL的东西

以上是关于记一次遇到由于重复提交导致的问题的主要内容,如果未能解决你的问题,请参考以下文章

troubleshooting记一次Kafka集群重启导致消息重复消费问题处理记录

53 记一次自定义 classloader 导致的 metadataspace OOM

53 记一次自定义 classloader 导致的 metadataspace OOM

记一次由于引用第三方服务导致的GC overhead limit exceeded异常

记一次tomcat自动退出问题

记一次truncate导致的锁表处理