java—提高插入大量数据的速度

xmq68pz9  于 2021-06-25  发布在  Mysql
关注(0)|答案(1)|浏览(563)

我有一个rest服务,它接受包含40万条记录的xml,每条记录包含以下字段: code,type,price .
在db(mysql)中,我有一个名为pricedata的表,其中有2行。rest的目的是:根据 code,type 如果没有pricedata,则用price from xml替换每个pricedata的price code,type 以提供的价格新建。
现在它的工作原理是:从db中选择一个pricedata,然后从xml中选择第一个记录,设置新的pricedata或创建新的pricedata,保存pricedata,这些步骤重复40万次(大约需要5分钟)
我想加快这个过程。
第一次尝试:
从pricedata中逐步选择1000个元素,当选择所有元素时,更新它们:
代码:

private void updateAll(final List<XmlData> prices/*data from xml*/) {
        int end= 1000;
        int begin= 0;
        final List<PriceData> models = new ArrayList<>();
        while(end != prices.size() || begin !=end){
           models.addAll(dao.findByPrices(prices.subList(begin,end)));
           begin = end;
           end +=1000;

       }

        final Map<String,XmlData> xmlData= prices.stream()                         
        .collect(Collectors.toMap(this::keyForPriceDate,e->e));

        final Map<String,PriceData> modelMap = models.stream()
                .collect(Collectors.toMap(this::keyForRowModel,e->e));          
        final List<PriceData> modelsToSave = new ArrayList<>();

        for(final String key : xmlData.keySet()){
            final XmlData price = xmlData.get(key);
            PriceData model = modelMap.get(key);

            if(model == null){
                model = onEmptyPriceData(price);
            }
            model.setPrice(price.getPrice());
            modelsToSave.add(model);
        }
        modelService.saveAll(modelsToSave);
    }

我将两个列表转换为Map,以了解pricedata是否存在(xmldata和modelmap的键创建为 (code+type) )
findbyprices方法按以下格式创建查询

select * from PriceData where (code =123 and type ='qwe') or (...)//and this `Or` repeats 1000 times

现在需要2分钟。
第二次尝试:

Select all PriceData from db (2 millions)

并使用上述算法
需要3分钟。第一次尝试更好,但在未来我的休息可以采取50万,我想知道哪种尝试将更好地在这种情况下,或者可能有更好的方法来完成这项任务。
我的选择方法

public List<PriceData> findBy(final List<XmlData> selectData) {
        final StringBuilder query = new StringBuilder("SELECT * from PriceData ");    
        query.append("WHERE \n");
        final Iterator<PriceRowSelectData> selectDataIterator = selectData.iterator();
        while(selectDataIterator.hasNext()){
            final PriceRowSelectData data = selectDataIterator.next();
            query.append("( \n")
                 .append("productCode = "+ data.getProductId()+" \n")
                 .append(" AND type = "+ data.getPriceind()+" \n")
                 .append(" ) \n");
            if(selectDataIterator.hasNext()){
                query.append("OR \n");
            }
        }
        final SearchResult<PriceRowModel> searchRes = search(query.toString());
        /*
         Here i use custom mapper that map list of result to my object
        */
        return searchRes.getResult();
    }
vq8itlhq

vq8itlhq1#

你应该使用mysql INSERT ... ON DUPLICATE KEY UPDATE 语句,结合jdbc批处理。这当然是假设 code,type 是主键,或者至少是唯一索引。

private void updateAll(final List<XmlData> prices) throws SQLException {
    String sql = "INSERT INTO PriceData (code, type, price)" +
                " VALUES (?,?,?)" +
                " ON DUPLICATE KEY" +
                " UPDATE price = ?";
    try (PreparedStatement stmt = this.conn.prepareStatement(sql)) {
        int batchSize = 0;
        for (XmlData price : prices) {
            if (batchSize == 1000) { // flush batch every 1000
                stmt.executeBatch();
                batchSize = 0;
            }
            stmt.setInt       (1, price.getCode());
            stmt.setString    (2, price.getType());
            stmt.setBigDecimal(3, price.getPrice());
            stmt.setBigDecimal(4, price.getPrice());
            stmt.addBatch();
            batchSize++;
        }
        if (batchSize != 0)
            stmt.executeBatch();
    }
}

您可以调整批大小,但不刷新将占用大量内存。我认为每批1000个语句是好的,但我没有数字支持这一点。

相关问题