java面试题分享

假如有一张表里面有几亿条数据,现在呢我要把数据都查询出来,再把每条记录的某几个字段,经过一定的加工,

再存到另外一张表里面去,为了方便大家能够听懂,我就假设这个表里面有一个性别字段,存了1或者2这两个值,

现在呢我要把1转成男,2呢转成女,再存到另外一张表里面去,请你能否写一段伪代码?

/**
   存在的问题:当你第一次查询的时候,你难道直接把几亿条数据都查出来吗?
   放到内存里面吗?很明显有问题,这么大的一个数据量,会直接进入老年代
   如果老年代内存不够就会触发full gc,如果gc之后还没有空间,就会直接导致
   oom
*/

public class Test {
    public static void main(String[] args) {
        /*
           Person 类sex字段 存的是 1 或者2
           Person 类 sex字段  存的是男 或者女
           第一种答案
        */
        List<Person> list = db.queryPerson(); // 查询所有数据
        for (Person person : list) {
            Person1 person1 = new Person1();
            person1.setName(person.getName());
            //转换
            if (person.getSex()==1) {
                person1.setSexCh("男");
            }else {
                person1.setSexCh("女");
            }
            //插入
            db.insertPerson1(person1);
        }
    }
    
    
}
/**
     这道题目考查:第一点是对数据大小的敏感程度   已经通过分页查询解决了
                  第二点就是对sql  limit的理解程度,所有的数据库,不管是sql还是no sql, limit的本质呢就是跳过前面n条数据
                  当你用数据条数去limit,分页深度呢会越来越深,那么跳过的数据也会越来越多,查询性能随之而来就是越来越慢,
                  那么最好的方式呢,就是用游标去解决查询的问题,但这边要注意一点,我们查询啊,一定要按照某个顺序去排序,
                  比如按照ID去排序,这样的游标才有意义。
                  而且要说明一点,我不建议用UUID去当主键
                  MySQL写入数据时,会把数据存放到索引页中。使用UUID作为主键,新行的主键值不一定比之前的主键值大,所以innoDb无法做到总是把新行插入到索引的最后,
                  而需要为新行寻找合适的位置来分配新的空间(因为是B+树方式存储的)。
要分配新的空间,就要知道应该分到哪个页。
                  如果用自增主键等,直接顺序增加在后面。而UUID类,就需要排序后这儿插一个,哪儿插一个。
                  不够的话,还要页分裂。而且,随机值载入到聚簇索引,有时候会需要做一次OPTIMEIZE TABLE来重建标并优化页的填充,这将又需要一定的时间消耗。
                  第三点就是单挑数据插入的性能问题,那每一次插入啊,都会去获取一次数据库连接,其实啊是很不必要的
                  可以用mybatisplus里面的批量插入,或者手动拼写批量插入的sql
                  第四点就是异常处理的问题,1亿条数据插入啊,但凡遇到一点网络的抖动或者说明情况的话,都会出现异常情况,那么怎么做?
                  那么批量插入啊你需要至少去记录初始的ID, 用于后面进行数据补偿
                  第五点就是插入效率的问题,那么单线程插入啊肯定效率比较慢,可以用多线程插入,这里啊你要注意一个问题
                  如果你用多线程插入,你如何来分配每一个线程应该插入哪一个阶段的数据呢?其实也不难,你要先保证ID是自增的,那么你可以取出最后一条
                  数据的ID除以你准备分配的线程数,我们这里假设有5个线程,数据量呢正好有一亿条,第一条数据ID呢也是1亿,用1亿除以5等于两千万,你就可以
                  用两千万的间隔来分配数据段了,只要是自增ID,那么这种方式啊,在任何情况下都是适用的
                  
                  把list的声明,person1的new啊可以放到循环外面去,防止oom,可以尽量避免循环创建对象
                  但是,这个场景里面,我把这个堆内存设的很小只有128MB,启动之后呢,一直在这个循环里面去声明这个对象
                  发现啊,JVM可以正常进行GC,把这些垃圾啊进行回收掉
                  但是,我认为把声明,new放到循环外面去,是一种比较好的编程习惯,可以尽量避免频繁创建对象
                  
                  
*/
public class Test {
    public static void main(String[] args) {
        /*
           Person 类sex字段 存的是 1 或者2
           Person 类 sex字段  存的是男 或者女
           第二种答案
        */
        int batchSize = 10000; // 定义一个批处理量
        //分段查询,查询次数等于总数量除以10000
        int count = db.queryPersonCount()/batchSize + 1;
        for (int i = 0;i<count;i++) {
            //每次查询10000条数据
            List<Person> list = db.queryPerson(i*batchSize,batchSize);
            for (Person person : list) {
                Person1 person1 = new Person1();
                person1.setName(person.getName());
                //转换
                if (person.getSex()==1) {
                    person1.setSexCh("男");
                }else {
                    person1.setSexCh("女");
                }
                //插入
                db.insertPerson1(person1);
            }
        }
    }
    
}
import java.util.ArrayList;
import java.util.List;

/**
 * @author 7575
 * @version 1.0
 * 我亦无他,唯手熟尔!
 */
public class Test {
    public static void main(String[] args) {
        /*
           Person 类sex字段 存的是 1 或者2
           Person 类 sex字段  存的是男 或者女
           第三种相对比较完善的答案,没有用多线程,多线程可以自己改造下
        */
        int lastId = 0;
        int batchSize = 10000; // 定义一个批处理量
        List<Person> batchList = new ArrayList<>();
        //SQL : select * from person limit 1 order by id desc
        Person lastPerson = db.getLastPerson();
        while (true) {
            try {
                //SQL: select * from person where id > #{lastId} limit 10000 order by id;
                List<Person> list = db.queryPersonByCursor(lastId,batchSize);
                log.info("lastId:{},批次查询成功",lastId);
                for (int i=0;i<list.size();i++) {
                    Person person = list.get(i);
                    
                    Person1 person1 = new Person1();
                    person1.setName(person.getName());
                    //转换
                    if (person.getSex()==1) {
                        person1.setSexCh("男");
                    }else {
                        person1.setSexCh("女");
                    }
                    batchList.add(person1);
                    if (i == list.size() - 1) {
                        lastId = person.getId(); // 如果i 是循环最后一个把最后person的ID 赋值给lastId
                    }
                }
                //批量插入
                //插入
                db.batchInsert(batchList);
                log.info("lastId:{},批次插入成功",lastId);
                //情况批量插入的
                batchList.clear();
                if (lastId==lastPerson.id) {
                    break; // 判断是否处理完毕 处理完毕break
                }
            } catch (Exception e) {
                //用日志记录插入失败的批次,可以用数据库记录,用于后期补偿
                log.error("lastId:{},批次插入失败",lastId)
            }
        }
      
    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值