批量更新数据之坑——总有遗漏数据没被更新
1、业务场景假设:
要求给没有职业的人添加职业
分批处理:分页查询 + 数据处理
最终结果:所有人都有自己的职业
2、代码模拟
设计两个Map模拟数据库数据
dbMap:数据库数据
queryMap:查询数据
(1)数据库数据模拟
public class Person {
private String name;
private String gender;
private Integer age;
private String occupation;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getOccupation() {
return occupation;
}
public void setOccupation(String occupation) {
this.occupation = occupation;
}
public Person(){}
public Person(String name, String gender, Integer age, String occupation) {
this.name = name;
this.gender = gender;
this.age = age;
this.occupation = occupation;
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("name : ").append(name);
stringBuilder.append(",gender : ").append(gender);
stringBuilder.append(",age : ").append(age);
stringBuilder.append(",occupation : ").append(occupation);
return stringBuilder.toString();
}
}
private static Map<Integer, Person> dbMap = new HashMap<>();
private static Map<Integer, Person> queryMap = new HashMap<>();
private static Person person0 = new Person("大兵0","男",20,"");
private static Person person1 = new Person("大兵1","男",20,"");
private static Person person2 = new Person("大兵2","男",30,"");
private static Person person3 = new Person("大兵3","男",30,"");
private static Person person4 = new Person("大兵4","男",40,"");
private static Person person5 = new Person("大兵5","男",40,"");
/**
* 模拟创建数据库数据
*
* @return
*/
private void buildPersons() {
dbMap.put(0, person0);
dbMap.put(1, person1);
dbMap.put(2, person2);
dbMap.put(3, person3);
dbMap.put(4, person4);
dbMap.put(5, person5);
}
(2)模拟数据库分页查询
/**
* 模拟数据库分页查询
*
* @param currentIndex
* @param pageSize
* @return
*/
private List<Person> pageOccupyIsNull(Integer currentIndex, Integer pageSize) {
List<Person> list = new ArrayList<>();
//queryMap只查询职业为空的人
for (int i = 0, j = 0; i < dbMap.size(); i++) {
Person person = dbMap.get(i);
if("".equals(person.getOccupation())){
queryMap.put(j,person);
j ++;
}
}
for (int i = currentIndex; i < currentIndex + pageSize; i++) {
list.add(queryMap.get(i));
}
return list;
}
(3)业务实现模拟
/**
* 集合中的数据都没有职业,分批次处理,给他们都增加职业信息
*/
public void testPageAndUpdate() {
buildPersons();
dbMap.entrySet().stream().forEach(System.out::println);
//已知没有职业的数据总数为5
int count = dbMap.size();
//分批处理
for (int i = 0; i < count; i += 2) {
List<Person> list = pageOccupyIsNull(i, 2);
if(list == null){
break;
}
list.forEach(person -> {
if(person != null){
person.setOccupation("IT");
}
});
}
System.out.println("分批处理后:");
dbMap.entrySet().stream().forEach(System.out::println);
}
3、问题思考
上面的代码真的可以将所有的人职业都设为IT吗?
答案是:不能!
运行结果:
0=name : 大兵0,gender : 男,age : 20,occupation :
1=name : 大兵1,gender : 男,age : 20,occupation :
2=name : 大兵2,gender : 男,age : 30,occupation :
3=name : 大兵3,gender : 男,age : 30,occupation :
4=name : 大兵4,gender : 男,age : 40,occupation :
5=name : 大兵5,gender : 男,age : 40,occupation :
分批处理后:
0=name : 大兵0,gender : 男,age : 20,occupation : IT
1=name : 大兵1,gender : 男,age : 20,occupation : IT
2=name : 大兵2,gender : 男,age : 30,occupation :
3=name : 大兵3,gender : 男,age : 30,occupation :
4=name : 大兵4,gender : 男,age : 40,occupation : IT
5=name : 大兵5,gender : 男,age : 40,occupation : IT
为什么呢?看图分解:
最后一次虽然sql过滤可以拿到数据,但是在分页查询的时候却漏掉了,大数据级别是万级的时候,一次处理为1000,那么最终也会留有万余的数据没有更新。
4、解决办法
分页查询每次都从 0 查起
将 List<Person> list = pageOccupyIsNull(1, 2);
改为 List<Person> list = pageOccupyIsNull(0, 2);
/**
* 集合中的数据都没有职业,分批次处理,给他们都增加职业信息
*/
public void testPageAndUpdate() {
buildPersons();
dbMap.entrySet().stream().forEach(System.out::println);
//已知没有职业的数据总数为5
int count = dbMap.size();
//分批处理
for (int i = 0; i < count; i += 2) {
List<Person> list = pageOccupyIsNull(0, 2);
if(list == null){
break;
}
list.forEach(person -> {
if(person != null){
person.setOccupation("IT");
}
});
}
System.out.println("分批处理后:");
dbMap.entrySet().stream().forEach(System.out::println);
}
运行结果:
0=name : 大兵0,gender : 男,age : 20,occupation :
1=name : 大兵1,gender : 男,age : 20,occupation :
2=name : 大兵2,gender : 男,age : 30,occupation :
3=name : 大兵3,gender : 男,age : 30,occupation :
4=name : 大兵4,gender : 男,age : 40,occupation :
5=name : 大兵5,gender : 男,age : 40,occupation :
分批处理后:
0=name : 大兵0,gender : 男,age : 20,occupation : IT
1=name : 大兵1,gender : 男,age : 20,occupation : IT
2=name : 大兵2,gender : 男,age : 30,occupation : IT
3=name : 大兵3,gender : 男,age : 30,occupation : IT
4=name : 大兵4,gender : 男,age : 40,occupation : IT
5=name : 大兵5,gender : 男,age : 40,occupation : IT
警告:还有一种特殊情况,我这就简单描述一下,不举例说明了。
假如你要处理一批数据,这批数据里面有一部分会由于某种原因一直无法被处理,但是每次查询都会被查出来,如果这个数量级和你每次处理任务的数量一致或者更多的时候,任务就陷入死循环,永远有处理不完的任务在堆积,而真正需要处理的任务却被挤压在后边无法被查询到。
这种情况,就要具体问题具体分析,根据实际情况,通过某种方法把不能被处理的任务过滤掉,才能从根本上解决问题。