使用 Batch Size 提高 Transaction 性能

我经常看到客户测试SQL Server 的插入(INSERT)或者批量导入大量数据的性能表现。其中有测试大量INSERTTSQL脚本如下:

use myDB

go

createtable t1(id intprimarykeynotnullidentity (1,1),name varchar(200),memo char(500));

go

truncatetable t1

go

declare @i int

set @i=0

declare @beginTime datetime,@endTime datetime

set @beginTime=GETDATE()

while(@i<200000)

begin

  insertintot1(name,memo)values('test'+str(RAND()*100000000),'memo'+str(RAND()*100000000))

  set @i=@i+1

end

set @endTime=GETDATE()

select @endTime-@beginTime

 

上面这个脚本测试插入200000行数据需要的时间。这个脚本有问题么?

语法没有问题,但是写法却不够优化。如果你使用performance  monitor

来观察,就会发现在数据插入期间log flushes/sec的次数非常高。在我的机器上达到5000Log flushes发生一次,意味着SQL server 需要写入事务日志(transaction log)一次。每秒5000次的日志写动作导致磁盘大量的写操作。正是这个磁盘写操作影响了上面的batch的性能。我上面脚本在我的机器上使用了大概40秒左右的时间。

 

如何改进这个脚本的性能呢?如何减少log flushes/sec从而减少磁盘的写操作? 答案是使用Batch Size如下面脚本所示。

 

 

truncatetable t1

go

 

declare @i int

set @i=0

declare @beginTime datetime,@endTime datetime

set @beginTime=GETDATE()

 

declare @batchSize int

set @batchSize=1000

 

while(@i<200000)

begin

  if (@i%@batchSize=0)

    begin

      if (@@TRANCOUNT>0)COMMITTRAN

      BEGINTRAN

    end

   

  insertintot1(name,memo)values('test'+str(RAND()*100000000),'memo'+str(RAND()*100000000))

  set @i=@i+1

end

  if (@@TRANCOUNT>0)COMMITTRAN

set @endTime=GETDATE()

 

select @endTime-@beginTime

 

 

黄色高亮是我改进的地方。我在同样的机器上跑了一下,奇迹发生了,它只使用了7秒多一点。快了5倍多!如果在看下performance

 Log���面的log flushes/sec,已经减少到700左右。我的测试图如下所示:

 

 

上面的这个小实验充分说明了batch size的重要性。它把很多小的transaction合并成一个大的合适的 transaction来减少磁盘写操作,从而获得极大性能提升。Batch size究竟多大才是最佳的呢?这个取决您的机器,需要你自己测试。

 

很多地方需要设置batch size提高性能。比如如果你使用游标(cursor)操作数据,记得使用batch Size。在bcp里面,也需要指定batchsize来提高性能:

 

bcp/?

usage: bcp {dbtable | query} {in | out | queryout | format} datafile

  [-m maxerrors]            [-f formatfile]          [-e errfile]

  [-F firstrow]             [-L lastrow]             [-b batchsize]

  [-n native type]          [-c character type]      [-w wide character type]

  [-N keep non-text native] [-V file format version] [-q quoted identifier]

  [-C code page specifier]  [-t field terminator]    [-r row terminator]

  [-i inputfile]            [-o outfile]             [-a packetsize]

  [-S server name]          [-U username]            [-P password]

  [-T trusted connection]   [-v version]             [-R regional enable]

  [-k keep null values]     [-E keep identity values]

  [-h "load hints"]         [-x generate xml format file]

  [-d database name]

 

如果使用bulk insert,也记得使用batch size

 

bulkinsert t1 from'\t.bcp'

 with (

 fire_triggers,

 datafiletype='native',

 tablock,

 batchsize=1000

 )

 

当然,性能优化里面batchSize并不是唯一需要考虑的因素。还需要考虑其他方面,比如并发开几个窗口运行bcp,使用合适的磁盘的RAID类型(RAID10RAID5的写快多了)等。

<think>好的,我现在需要帮助用户解决Spring Batch在更新Step状态时遇到的PostgreSQL空闲事务超时问题。首先,我需要理解用户的问题。用户在使用Spring Batch时,当尝试更新Step的状态,系统抛出了一个PSQLException,提示连接因为空闲事务超时被终止。这意味着数据库在事务处于空闲状态超过一定时间后,主动终止了连接。 接下来,我需要考虑可能导致这个问题的原因。首先,空闲事务超时通常是由于数据库的事务长时间未提交或回滚,导致连接保持打开但无活动。PostgreSQL默认的idle_in_transaction_session_timeout参数可能就是罪魁祸首,默认情况下可能设置为几分钟,比如5分钟。如果Spring Batch的某个步骤执行时间较长,或者在步骤处理期间有长时间的无数据库操作,就可能触发这个超时。 然后,我需要分析Spring Batch的事务管理机制。Spring Batch在处理步骤时,默认会为每个Step开启一个事务,并在Step完成后提交。如果Step执行时间过长,或者步骤中有分块处理(Chunk Processing),比如每处理一定数量的数据提交一次,但处理间隔较长,可能导致事务长时间处于空闲状态。 接下来,用户可能没有正确配置事务超时,或者数据处理的分块大小不合适。例如,如果Chunk Size设置得过大,处理每个Chunk的时间超过了数据库的空闲超时时间,就会导致事务在等待处理时被终止。 解决方案方面,首先应该考虑调整PostgreSQL的idle_in_transaction_session_timeout参数,增加超时时间。但用户可能没有权限修改数据库配置,所以需要其他方法。 另一个方向是优化Spring Batch的事务管理。比如,确保每个Chunk的处理时间不超过数据库的空闲超时限制,适当减小Chunk Size,或者使用Step作用域的事务,而不是在整个Step期间保持事务开启。此外,检查是否有长时间的非数据库操作在事务内执行,导致事务无法及时提交。 还可以考虑在Spring的配置中设置事务超时,例如在@Transactional注解中指定timeout属性,或者在配置事务管理器时设置默认超时时间。此外,确保连接池的正确配置,比如使用HikariCP,并设置合适的连接超时和空闲时间,避免连接被提前回收。 另外,需要检查代码中是否存在手动开启事务但未正确提交或回滚的情况,特别是在自定义的Tasklet或ItemProcessor中。确保所有事务在操作后及时提交,避免长时间挂起。 最后,建议用户查看Spring Batch的日志,确认事务的开启和提交时间点,以及每个Chunk的处理时长,从而定位具体是哪个环节导致了空闲超时。如果问题仍然存在,可能需要结合具体的代码和配置进行更深入的分析。</think>您遇到的Spring Batch更新Step时出现的`PSQLException: FATAL: terminating connection due to idle-in-transaction timeout`错误,本质是**PostgreSQL数据库的事务空闲超时机制触发了连接终止**。以下是逐步分析与解决方案: --- ### 一、问题根源 1. **PostgreSQL的`idle_in_transaction_session_timeout`参数** PostgreSQL默认会终止长时间处于空闲状态的事务(未提交且无任何操作),防止资源泄漏。 典型场景:Spring Batch的某个Step开启了事务,但长时间未提交(例如处理大量数据时),导致事务空闲超时。 2. **Spring Batch的事务管理机制** - Spring Batch的Step默认在事务中执行(例如`TaskletStep`或基于Chunk的步骤)。 - 如果某个操作(如读取、处理数据)耗时过长,且期间没有数据库交互,事务会处于空闲状态。 --- ### 二、关键排查步骤 #### 1. **确认数据库超时设置** ```sql SHOW idle_in_transaction_session_timeout; -- 默认值可能是5分钟(300000ms) ``` 如果返回值为`0`表示未启用,否则需调整参数或优化代码。 #### 2. **检查Spring Batch的Chunk配置** ```java @Bean public Step myStep() { return stepBuilderFactory.get("myStep") .<Input, Output>chunk(50) // 关键参数:Chunk Size .reader(reader()) .processor(processor()) .writer(writer()) .build(); } ``` - **问题**:若`Chunk Size`过大,处理单批数据耗时超过数据库空闲超时,事务会因无操作被终止。 - **解决**:减小`chunk()`的值(例如从100调整为20),缩短单次事务执行时间。 #### 3. **优化事务范围** - **场景**:自定义`Tasklet`中手动控制事务时,若包含非数据库操作(如复杂计算、外部API调用),事务会处于空闲状态。 - **解决**: ```java @Bean public Step myStep(PlatformTransactionManager txManager) { return stepBuilderFactory.get("myStep") .tasklet(myTasklet()) .transactionManager(txManager) .build(); } @Bean public Tasklet myTasklet() { return (contribution, chunkContext) -> { // 将非数据库操作放在事务外执行 doNonDatabaseWork(); // 事务内仅执行数据库操作 TransactionTemplate transactionTemplate = new TransactionTemplate(txManager); transactionTemplate.execute(status -> { doDatabaseWork(); return RepeatStatus.FINISHED; }); return RepeatStatus.FINISHED; }; } ``` #### 4. **配置事务超时** ```java @Bean public ResourcelessTransactionManager transactionManager() { ResourcelessTransactionManager txManager = new ResourcelessTransactionManager(); txManager.setDefaultTimeout(30); // 设置事务超时时间(秒) return txManager; } ``` 确保事务超时时间 **小于** PostgreSQL的`idle_in_transaction_session_timeout`。 #### 5. **调整PostgreSQL配置(可选)** ```sql ALTER SYSTEM SET idle_in_transaction_session_timeout = '10min'; -- 修改超时时间 SELECT pg_reload_conf(); -- 重载配置 ``` **注意**:需数据库管理员权限,且可能影响其他应用。 --- ### 三、验证方案 1. **日志监控** 启用Spring事务日志,观察事务提交频率: ```properties logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG ``` 2. **性能测试** 使用`chunk(10)`和`chunk(100)`分别测试,对比是否因Chunk Size导致超时。 --- ### 四、补充建议 - **连接池配置**:使用HikariCP等连接池时,确保`maxLifetime`小于数据库超时时间。 - **批量写入优化**:对Writer使用`JdbcBatchItemWriter`并启用`setSql("...")`的批量模式,减少数据库交互次数。 通过上述调整,应能避免事务空闲超时问题。如果问题仍存在,请提供Spring Batch配置和Step代码片段进一步分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值