use*_*807 21 java mysql spring jdbctemplate spring-batch
我正在尝试找到更快的批量插入方法.
我试图用jdbcTemplate.update(String sql)插入几个批处理,其中sql是由StringBuilder 构建的,如下所示:
INSERT INTO TABLE(x, y, i) VALUES(1,2,3), (1,2,3), ... , (1,2,3)
Run Code Online (Sandbox Code Playgroud)
批量大小正好是1000.我插入了近100批.我使用StopWatch检查了时间并找出了插入时间:
min[38ms], avg[50ms], max[190ms] per batch
Run Code Online (Sandbox Code Playgroud)
我很高兴,但我想让我的代码变得更好.
之后,我尝试使用jdbcTemplate.batchUpdate,如:
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
// ...
}
@Override
public int getBatchSize() {
return 1000;
}
});
Run Code Online (Sandbox Code Playgroud)
sql的样子
INSERT INTO TABLE(x, y, i) VALUES(1,2,3);
Run Code Online (Sandbox Code Playgroud)
我很失望!jdbcTemplate以分开的方式执行1000行批处理的每个插入.我在mysql_log上找到了,发现有一千个插入.我使用StopWatch检查了时间并找出了插入时间:
min [900ms],avg [1100ms],每批最大[2000ms]
那么,任何人都可以向我解释一下,为什么jdbcTemplate在这个方法中做了单独的插入?为什么方法的名称是batchUpdate?或者可能是我以错误的方式使用这种方法?
小智 16
JDBC连接URL中的这些参数可以对批处理语句的速度产生很大影响 - 根据我的经验,它们可以加快速度:
?useServerPrepStmts =假rewriteBatchedStatements =真
请参阅:JDBC批处理插入性能
Rak*_*oni 12
我也遇到过与Spring JDBC模板相同的问题.可能在Spring Batch中,语句在每个插件或块上执行并提交,这会减慢速度.
我用原始JDBC批处理插入代码替换了jdbcTemplate.batchUpdate()代码,并找到了主要的性能改进.
DataSource ds = jdbcTemplate.getDataSource();
Connection connection = ds.getConnection();
connection.setAutoCommit(false);
String sql = "insert into employee (name, city, phone) values (?, ?, ?)";
PreparedStatement ps = connection.prepareStatement(sql);
final int batchSize = 1000;
int count = 0;
for (Employee employee: employees) {
ps.setString(1, employee.getName());
ps.setString(2, employee.getCity());
ps.setString(3, employee.getPhone());
ps.addBatch();
++count;
if(count % batchSize == 0 || count == employees.size()) {
ps.executeBatch();
ps.clearBatch();
}
}
connection.commit();
ps.close();
Run Code Online (Sandbox Code Playgroud)
检查此链接以及 JDBC批量插入性能
我发现在调用中设置argTypes数组有一个重大改进。
就我而言,对于Spring 4.1.4和Oracle 12c,要插入具有35个字段的5000行:
jdbcTemplate.batchUpdate(insert, parameters); // Take 7 seconds
jdbcTemplate.batchUpdate(insert, parameters, argTypes); // Take 0.08 seconds!!!
Run Code Online (Sandbox Code Playgroud)
argTypes参数是一个int数组,您可以通过这种方式设置每个字段:
int[] argTypes = new int[35];
argTypes[0] = Types.VARCHAR;
argTypes[1] = Types.VARCHAR;
argTypes[2] = Types.VARCHAR;
argTypes[3] = Types.DECIMAL;
argTypes[4] = Types.TIMESTAMP;
.....
Run Code Online (Sandbox Code Playgroud)
我调试了org \ springframework \ jdbc \ core \ JdbcTemplate.java,发现大部分时间都花在试图了解每个字段的性质上,而这是针对每条记录进行的。
希望这可以帮助 !
小智 8
只需使用交易.在方法上添加@Transactional.
如果使用多个数据源@Transactional("dsTxManager"),请务必声明正确的TX管理器.我有一个插入60000记录的情况.大约需要15秒.没有其他调整:
@Transactional("myDataSourceTxManager")
public void save(...) {
...
jdbcTemplate.batchUpdate(query, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
...
}
@Override
public int getBatchSize() {
if(data == null){
return 0;
}
return data.size();
}
});
}
Run Code Online (Sandbox Code Playgroud)
将您的sql插入更改为INSERT INTO TABLE(x, y, i) VALUES(1,2,3).框架为您创建一个循环.例如:
public void insertBatch(final List<Customer> customers){
String sql = "INSERT INTO CUSTOMER " +
"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";
getJdbcTemplate().batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Customer customer = customers.get(i);
ps.setLong(1, customer.getCustId());
ps.setString(2, customer.getName());
ps.setInt(3, customer.getAge() );
}
@Override
public int getBatchSize() {
return customers.size();
}
});
}
Run Code Online (Sandbox Code Playgroud)
如果你有这样的东西.Spring会做类似的事情:
for(int i = 0; i < getBatchSize(); i++){
execute the prepared statement with the parameters for the current iteration
}
Run Code Online (Sandbox Code Playgroud)
框架首先从查询(sql变量)创建PreparedStatement,然后调用setValues方法并执行语句.重复的次数与getBatchSize()方法中指定的次数相同.因此,编写insert语句的正确方法是只有一个values子句.你可以看看http://docs.spring.io/spring/docs/3.0.x/reference/jdbc.html
我不知道这是否适合您,但这是我最终使用的一种无 Spring 方式。它比我尝试过的各种 Spring 方法要快得多。我什至尝试使用另一个答案描述的 JDBC 模板批量更新方法,但即使这样也比我想要的要慢。我不确定交易是什么,互联网也没有很多答案。我怀疑这与提交的处理方式有关。
这种方法只是使用 java.sql 包和 PreparedStatement 的批处理接口的直接 JDBC。这是我将 24M 记录放入 MySQL 数据库的最快方法。
我或多或少只是建立了“记录”对象的集合,然后在批量插入所有记录的方法中调用以下代码。构建集合的循环负责管理批量大小。
我试图将 24M 记录插入到 MySQL 数据库中,并且使用 Spring 批处理每秒可以达到约 200 条记录。当我切换到这种方法时,它达到了每秒约 2500 条记录。所以我的 24M 记录负载从理论上的 1.5 天变成了大约 2.5 小时。
首先建立一个连接...
Connection conn = null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(connectionUrl, username, password);
}catch(SQLException e){}catch(ClassNotFoundException e){}
Run Code Online (Sandbox Code Playgroud)
然后创建一个准备好的语句并使用批量插入值加载它,然后作为单个批量插入执行...
PreparedStatement ps = null;
try{
conn.setAutoCommit(false);
ps = conn.prepareStatement(sql); // INSERT INTO TABLE(x, y, i) VALUES(1,2,3)
for(MyRecord record : records){
try{
ps.setString(1, record.getX());
ps.setString(2, record.getY());
ps.setString(3, record.getI());
ps.addBatch();
} catch (Exception e){
ps.clearParameters();
logger.warn("Skipping record...", e);
}
}
ps.executeBatch();
conn.commit();
} catch (SQLException e){
} finally {
if(null != ps){
try {ps.close();} catch (SQLException e){}
}
}
Run Code Online (Sandbox Code Playgroud)
显然我已经删除了错误处理,并且查询和记录对象是名义上的等等。
编辑: 由于您的原始问题是将插入到 foobar 值 (?,?,?), (?,?,?)...(?,?,?) 方法与 Spring 批处理进行比较,因此这里有一个更直接的响应:
看起来您的原始方法可能是将批量数据加载到 MySQL 中而不使用“LOAD DATA INFILE”之类的方法的最快方法。引自 MysQL 文档(http://dev.mysql.com/doc/refman/5.0/en/insert-speed.html):
如果您同时从同一客户端插入多行,请使用带有多个 VALUES 列表的 INSERT 语句一次插入多行。这比使用单独的单行 INSERT 语句快得多(在某些情况下快很多倍)。
您可以修改 Spring JDBC 模板 batchUpdate 方法以使用每个 'setValues' 调用指定的多个 VALUES 进行插入,但您必须在迭代插入的一组内容时手动跟踪索引值。当插入的内容总数不是准备好的语句中的 VALUES 列表数量的倍数时,你会在最后遇到一个令人讨厌的边缘情况。
如果您使用我概述的方法,您可以做同样的事情(使用带有多个 VALUES 列表的准备好的语句),然后当您最终遇到那个边缘情况时,处理起来会容易一些,因为您可以构建和执行具有完全正确数量的 VALUES 列表的最后一个语句。这有点hacky,但大多数优化的东西都是。
我在使用 Spring JDBC 批处理模板时也遇到了一些不愉快。就我而言,使用纯 JDBC 会很疯狂,所以我使用NamedParameterJdbcTemplate. 这在我的项目中是必须的。但是在数据库中插入成百上千行的速度很慢。
为了了解发生了什么,我在批量更新期间使用 VisualVM 对其进行了采样,瞧:

减慢进程的原因是,在设置参数时,Spring JDBC 正在查询数据库以了解每个参数的元数据。而在我看来,这是查询每个参数的每一行数据库每次。所以我只是教 Spring 忽略参数类型(正如Spring 文档中关于批量操作对象列表的警告):
@Bean(name = "named-jdbc-tenant")
public synchronized NamedParameterJdbcTemplate getNamedJdbcTemplate(@Autowired TenantRoutingDataSource tenantDataSource) {
System.setProperty("spring.jdbc.getParameterType.ignore", "true");
return new NamedParameterJdbcTemplate(tenantDataSource);
}
Run Code Online (Sandbox Code Playgroud)
注意:必须在创建 JDBC 模板对象之前设置系统属性。可以只设置
application.properties,但这解决了,我再也没有碰过这个
| 归档时间: |
|
| 查看次数: |
60174 次 |
| 最近记录: |