当前位置: 首页 > 科技观察

MySQL去除“关联表”中的重复数据创建联合唯一索引

时间:2023-03-12 00:59:02 科技观察

前言昨天遇到一个问题,需要重构和优化一张关系表。但是这个关系表有很多脏数据,也就是重复数据,因为现有的代码没有注意并发。表名thread_recommend,帖子推荐表,是两个实体user_id和thread_id的(推荐)关系表。表结构很简单如下:/*用户推荐帖子记录表*/CREATETABLE`thread_recommend`(`id`int(11)NOTNULLAUTO_INCREMENT,`thread_id`int(11)DEFAULTNULLCOMMENT'推荐的帖子数user',`user_id`int(11)DEFAULTNULLCOMMENT'推荐帖子的用户数',`status`int(11)DEFAULT'1'COMMENT'status0取消推荐,1推荐',`created`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'推荐时间',PRIMARYKEY(`id`),KEY`userid`(`user_id`)USINGBTREE)ENGINE=InnoDB;问题是由于代码不规则,在高并发时(或者有积压导致数据库压力大的延迟时)会出现多个组合(相同的thread_id和user_id),如下:之后,你会明白会喷出各种与原意不符的神奇bug,比如:我刚刚取消了推荐,为什么还显示我正在推荐!!显示的推荐总数与实际推荐用户有何不同!!方案一:使用insertwherenotexists语句声明:此方案不是***方案,不推荐使用。先上传代码:(这里以另一个关系表的真实查询为例,原理是一样的)*FROM`user_topic`WHERE`user_topic`.`user_id`=:userIdAND`user_topic`.`topic_id`=:topicid)LIMIT1;(同样的方法见http://stackoverflow.com/a/31)如果存在则插入并返回行数为1,如果存在则返回行数为0”,即可完成:只有当返回行数为1时,才执行后续逻辑(如缓存中的统计+1,缓存将此userId添加到帖子的推荐人等)如果返回行数为0,接口返回错误解决方案二:清理脏数据,建立联合唯一索引这个解决方案是本文的核心,也是我们目前认为的最佳实践Step1:找到user_id和thread_id的联合重复SELECTa。*FROM`thread_recommend`aINNERJOIN(SELECT*FROM`thread_recommend`GROUPBY`thread_id`,`user_id`HAVINGCOUNT(id)>1)bONa.`thread_id`=b.`thread_id`ANDa.`user_id`=b.`user_id`ORDERBYa.`user_id`ASC,a.`thread_id`ASC,a.`id`DESC或简单版本SELECT*FROM`thread_recommend`WHERE(`user_id`,`thread_id`)IN(SELECT`user_id`,`thread_id`FROM`thread_recommend`GROUPBY`user_id`,`thread_id`HAVINGCOUNT(1)>1);哇!重复的都在这里了,想马上去掉!现在需要删除重复条目中ID较大的条目,只留下ID最小的条目。删除前获取要删除的项,比较,SELECT*FROM`thread_recommend`WHERE(`user_id`,`thread_id`)IN(SELECT`user_id`,`thread_id`FROM`thread_recommend`GROUPBY`user_id`,`thread_id`HAVINGCOUNT(1)>1)AND`id`NOTIN(SELECTMIN(`id`)FROM`thread_recommend`GROUPBY`user_id`,`thread_id`HAVINGCOUNT(1)>1);接下来SELECT*FROM改为DELETEFROM,DELETEFROM`thread_recommend`WHERE(`user_id`,`thread_id`)IN(SELECT`user_id`,`thread_id`FROM`thread_recommend`GROUPBY`user_id`,`thread_id`HAVINGCOUNT(1)>1)AND`id`NOTIN(SELECTMIN(`id`)FROM`thread_recommend`GROUPBY`user_id`,`thread_id`HAVINGCOUNT(1)>1);哎呀!错误!不能在FROM子句中指定targettable'thread_recommend'forupdate这是Mysql的一个小问题,我们参考解决方案http://stackoverflow.com/a/14...修改SQL之后即可:DELETEFROM`thread_recommend`WHERE(`user_id`,`thread_id`)IN(SELECT`user_id`,`thread_id`FROM(SELECT*FROM`thread_recommend`)aGROUPBY`user_id`,`thread_id`HAVINGCOUNT(1)>1)AND`id`NOTIN(SELECTMIN(`id`)FROM(SELECT*FROM`thread_recommend`)bGROUPBY`用户_id`,`thread_id`HAVINGCOUNT(1)>1);***,添加联合唯一索引!ALTERTABLE`thread_recommend`ADDUNIQUEKEY`thread_id_user_id_unique`(`thread_id`,`user_id`)USINGBTREE;当然如果上面的清理工作没有完成就会报错!结束!