线上数据库重复数据处理

一个用户关联了多个账户异常分析与解决方案

一、发现问题
线上发现请求 /acm/queryAc 接口报错,查看日志发现如下报错信息
在这里插入图片描述
二、分析问题
分析日志发现是sql报错,由于sql查询结果集存在多条数据返回,但是selectOne()方法只有一个对象来接收,导致抛出org.apache.ibatis.exceptions.TooManyResultsException 异常。
1、查看dao接口

@Mapper
public interface AcmAcDao extends BaseDao<AcmAcBO> {

    AcmAcBO getByUserId(@Param("userId")String userId);

    AcmAcBO getByUserIdForUpdate(@Param("userId") String userId);
    
}

2、查看sql语句

select

from acm_ac
where USER_ID = #{userId,jdbcType=VARCHAR}

3、查看数据库相关记录
在这里插入图片描述
该用户在数据库中存在两个账户记录,但是dao层接口只接收一条记录。
进一步分析出现一个用户关联多个账户的原因:

通过sql 查看表结构 show create table acm_ac;

CREATE TABLE acm_ac (
AC_ID varchar(64) NOT NULL COMMENT ‘账户ID’,
USER_ID varchar(64) NOT NULL COMMENT ‘用户ID’,
ANDROID_BAL decimal(15,2) NOT NULL DEFAULT ‘0.00’ COMMENT ‘安卓余额’,
APPLE_BAL decimal(15,2) NOT NULL DEFAULT ‘0.00’ COMMENT ‘苹果余额’,
AC_STATUS char(16) NOT NULL DEFAULT ‘NORMAL’ COMMENT ‘账户状态:正常 NORMAL 冻结 FORZEN’,
CREATE_TIME datetime DEFAULT NULL COMMENT ‘创建时间’,
MODIFY_TIME datetime DEFAULT NULL COMMENT ‘修改时间’,
UPDATE_DATE datetime DEFAULT NULL COMMENT ‘修改时间’,
CREATE_DATE datetime DEFAULT NULL COMMENT ‘创建时间’,
PRIMARY KEY (AC_ID),
UNIQUE KEY USER_ID_2 (USER_ID),
KEY AC_ID (AC_ID),
KEY USER_ID (USER_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
结果显示USER_ID字段没有添加唯一约束,
进一步排查出现一个用户存在两条账户信息的原因:
首先检查账户添加的逻辑,在项目全局检查添加账户逻辑出现在账户充值逻辑和查询账户记录两个函数中,
虽然代码逻辑方面没有问题,但是并发场景下还是会出现一个用户关联多个账户的情况。

三、解决问题
由于账户涉及用户金额,紧急的解决方案是修改sql语句,可以先解决代码报错问题,修改如下

select

from acm_ac
where USER_ID = #{userId,jdbcType=VARCHAR} order by create_time desc limit 1

通过增加order by create_time desc limit 1 sql片段可以保证一个用户最多只返回一条记录

另外,需要把数据库重复数据清除掉,由于ANDROID_BAL和APPLE_BAL分别保存的是安卓账户余额和苹果账户余额,需要对多条记录的这2个字段做统计汇总。因此本方案使用mysql的聚合函数处理重复数据,并结合rename函数解决,编写脚本如下:

SET AUTOCOMMIT=0;
begin;
create table acm_ac_new like acm_ac;
rename table acm_ac to acm_ac_old;
insert into acm_ac_new
select t.AC_ID,t.USER_ID ,sum(t.ANDROID_BAL) ANDROID_BAL,sum(t.APPLE_BAL) APPLE_BAL,t.AC_STATUS,t.CREATE_TIME,t.MODIFY_TIME,t.UPDATE_DATE,t.CREATE_DATE from acm_ac_old t GROUP BY USER_ID;
alter table acm_ac_new add UNIQUE (user_id);
rename table acm_ac_new to acm_ac;
commit;

验证:

select count(*) num,t.user_id from acm_ac t GROUP BY t.user_id having num>1;
select count(DISTINCT(user_id)) from acm_ac;
select count(DISTINCT(user_id)) from acm_ac_old;

注意事项:
一、添加唯一约束需要在 rename table acm_ac_new to acm_ac; 之前执行,在脚本执行期间由于rename table acm_ac to acm_ac_old;语句把acm_ac表名改为acm_ac_old,在此期间线上应用程序如果查询这个表会出现表找不到的sql报错,所以需要在运维期间执行此脚本;
二、执行这个脚本之前需要先评估下耗时情况,本例在线上执行之前先在预发数据库执行,多次测试耗时在4秒左右,会造成线上服务器账户模块短暂的异常,需要做好相关处置措施。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值