背景
- 有这样一张用户别名表,一个用户ID可以对应多个用户别名,但是系统分配的别名只能有一个。
- 由于user_id 行没有设置唯一主键,一个user_id可以有多个number_nick_name和多个string_nick_name。但是系统自动分配希望每个user_id只有一条,实际程序bug导致一个user_id现在存在多个set_type=1的行(实际上当前表中全部都是 set_type=1 的行)。
- 此表为mysql单表,表中已经有9千万数据,user_id为1-16位int64整数型(例:4506666660666666), id为定长19位int64(例:5777050001234567890)
CREATE TABLE `t_user_name_map` (
`id` bigint NOT NULL COMMENT 'ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`number_nick_name` varchar(20) DEFAULT NULL COMMENT '系统自动分配昵称',
`string_nick_name` varchar(20) DEFAULT NULL COMMENT '运营分配靓号昵称',
`set_type` int NOT NULL DEFAULT '1' COMMENT '1-系统分配 2-运营分配',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_num_name` (`number_nick_name`),
UNIQUE KEY `uniq_str_name` (`string_nick_name`),
KEY `idx_user_id_create_time` (`user_id`,`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户别名表'
诉求
- 希望在尽可能快的时间里将表中的重复数据清除,即一个user_id只有一条记录(实际是一个user_id只有一条set_type=1的记录,因为当前表里全是set_type=1)
分析
- 由于表中的数据行数太多,不可能使用mysql的group by 去找出重复的行,执行时间太久会导致数据库卡死,影响线上业务。
- 因此数据需要使用程序在内存中处理,但是又不可能一次性将几千万数据一次性查出来,机器内存势必承受不住,必须得分页拿数据。
- 分页处理数据就存在一个问题了,有可能重复的数据是在不同页,那这样就不好处理了。因为最终的结果是一个 user_id 只保留一行。
方案一
描述
最直观的想法是根据 user_id,id排序进行分页拉去,最后只保留id最小的那行数据。这样需要注意一个细节,可能存在一个user_id 的数据跨页了。例如分页大小为 500,第一次根据user_id 拿到的最后10个user_id都是12345,而user_id为12345的数据原本有30个。所以编写程序需要注意这个点。要记录每次最后一个user_id。
参考代码
待补充
方案二
描述
按照主键id,顺序分