基于上一篇文章,我想到如果在数据库之间拥有大量数据的表数据的导入导出,该如何快速完成表的导入导出工作呢?
思路一:使用db软件工具导出数据,然后向新数据库导入数据。我用的是dbserver22.3.4,不出意外的是,报了内存不足的异常。故这条路走不通
思路二:使用java程序按量读取表数据,写入txt文件存储,循环此过程,直至表数据全部写入txt文件,然后切换数据源url,使用java程序读取txt文件,最后使用原生jdbc向数据库中插入数据。说干就干!!!
1.按量读取表数据
使用 selext * from example 是不行的 别问我怎么知道的,要问就是试过,光这一条sql的读取时间就不止0.5h,果断放弃!!!
一条路不行,就换一条路!! 我想到的是换一条路,即按量读取数据,10w条数据读入在写入txt文件,循环此过程,直至读取写入完毕。
原生jdbc是最底层的,故效率是最高的。参考了大神博客和大神博客这两篇博客,写了以下代码
@Autowired
private ExampleController exampleController;
@Autowired
private SysMenuService sysMenuService;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Test
void context() throws SQLException{
// List<Example> examples = exampleController.getList(null);
// examples.size();
Long start = System.currentTimeMillis();
String sql = "select * from Example";
Connection conn = DriverManager.getConnection(url,username,password);
conn.setAutoCommit(false);
PreparedStatement ps = conn.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
ps.setFetchSize(100000);
ps.setFetchDirection(ResultSet.FETCH_REVERSE);
int totleCount = 0 ;
ResultSet resultSet = ps.executeQuery();
while(resultSet.next()){
totleCount ++;
}
resultSet.close();
ps.close();
conn.close();
Long end = System.currentTimeMillis();
log.info("耗时"+(end-start)+"ms");
log.info("耗时"+(end-start)+"ms");
}
结果如下:
读取1000w条数据,耗时18s。原生jdbc yyds!!!
接下来就是做写入操作。
继续思考,读出来的数据写入到文件,这个时间估计会很长,如果不写入,直接在程序中使用新的数据源插入新数据库,这个时间预计会比较短,说干就干!!
@Test
void context() throws SQLException{
// List<Example> examples = exampleController.getList(null);
// examples.size();
List<Example> exampleList = new ArrayList<>();
Example example = null;
Long start = System.currentTimeMillis();
String sql = "select * from Example";
// 数据源1
String url = "jdbc:mysql://127.0.0.1:3306/springboot_base?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&rewriteBatchedStatements=true";
Connection conn = DriverManager.getConnection(url,username,password);
conn.setAutoCommit(false);
// 数据源2
String urlNew = "jdbc:mysql://127.0.0.1:3306/springboottest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&rewriteBatchedStatements=true";
Connection connNew = DriverManager.getConnection(urlNew,username,password);
// 注意一定要指明数据库 我这是本地数据库
String sqlNew = "insert into springboottest.example (id,name,status,money,num,create_time) values (?,?,?,?,?,?)";
PreparedStatement psNew = connNew.prepareStatement(sqlNew);
connNew.setAutoCommit(false);
// 数据源1的查询操作
PreparedStatement ps = conn.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
ps.setFetchSize(100000);
ps.setFetchDirection(ResultSet.FETCH_REVERSE);
int totleCount = 0 ;
ResultSet rs = ps.executeQuery();
Boolean isEnd = false;
while(rs.next()){
totleCount ++;
example = new Example();
example.setId(rs.getLong("id"));
example.setName(rs.getString("name"));
example.setStatus(rs.getString("status"));
if (rs.getObject("money")!=null){
example.setMoney(rs.getBigDecimal("money"));
}
if (rs.getObject("num")!=null){
example.setNum(BigInteger.valueOf(rs.getLong("num")));
}
example.setCreateTime(rs.getTimestamp("create_time"));
exampleList.add(example);
if (totleCount % 100000 == 0){
// 修改数据源
if (totleCount == 10100000){
isEnd = true;
}
insertBatch(exampleList,isEnd,connNew,psNew);
exampleList.clear();
if (isEnd){
break;
}
}
}
rs.close();
ps.close();
psNew.close();
conn.close();
Long end = System.currentTimeMillis();
log.info("耗时"+(end-start)+"ms");
}
/**
* 向数据库中插入数据
* @throws SQLException
*/
void insertBatch(List<Example> exampleList,Boolean isEnd,Connection conn,PreparedStatement ps) throws SQLException {
for (Example e:exampleList){
ps.setLong(1,e.getId());
ps.setString(2,e.getName());
ps.setString(3,e.getStatus());
if (e.getMoney() != null){
ps.setObject(4,e.getMoney());
}else {
// ps设置money为空 第二个参数为数据库字段类型
ps.setNull(4, Types.DECIMAL);
}
if (e.getNum() != null){
ps.setObject(5,e.getNum());
}else {
ps.setNull(5, Types.BIGINT);
}
ps.setObject(6,e.getCreateTime());
ps.addBatch();
}
ps.executeBatch();
conn.commit();
ps.clearParameters();
if (isEnd){
conn.close();
}
}
结果如下,耗时966762ms,即16分钟左右
转移拥有1000w条数据的表到另外一个数据库,耗时16min
若使用文件作为媒介,则大概率会降低效率,因为写入1000w条数据到文件,然后再读取有1000w条数据的文件,会很耗时,这里就放弃使用文件作为媒介了。
说一说优化吧,在sql传参的时候判断一下设置的值是否为NULL,这样会提高效率!!thanks!