Spring Batch提交SQL与源码不符,Postgres执行计划异常求因
问题排查:Spring Batch SQL被改写导致Postgres性能劣化
正在排查基于Spring Boot、Spring Batch 4.2.0和Postgres 14.5的应用性能问题时,发现异常情况:Postgres端执行的SQL查询与Spring Batch源码(JdbcStepExecutionDao.java)中的查询存在差异——子查询中多了外层表的JE.前缀,这导致Postgres生成的执行计划包含两次全表扫描(FTS),性能较差;而源码中的查询执行计划使用索引扫描,性能更优。现疑问:该SQL改写是由Postgres服务端、JDBC驱动还是其他中间层导致的?
一、Postgres端实际执行的SQL及执行计划
执行SQL
SELECT SE.STEP_EXECUTION_ID, SE.STEP_NAME, SE.START_TIME, SE.END_TIME, SE.STATUS, SE.COMMIT_COUNT, SE.READ_COUNT, SE.FILTER_COUNT, SE.WRITE_COUNT, SE.EXIT_CODE, SE.EXIT_MESSAGE, SE.READ_SKIP_COUNT, SE.WRITE_SKIP_COUNT, SE.PROCESS_SKIP_COUNT, SE.ROLLBACK_COUNT, SE.LAST_UPDATED, SE.VERSION, JE.JOB_EXECUTION_ID, JE.START_TIME, JE.END_TIME, JE.STATUS, JE.EXIT_CODE, JE.EXIT_MESSAGE, JE.CREATE_TIME, JE.LAST_UPDATED, JE.VERSION from N_BATCH_JOB_EXECUTION JE, N_BATCH_STEP_EXECUTION SE where SE.JOB_EXECUTION_ID in (SELECT JOB_EXECUTION_ID from N_BATCH_JOB_EXECUTION where JE.JOB_INSTANCE_ID = $1) and SE.JOB_EXECUTION_ID = JE.JOB_EXECUTION_ID and SE.STEP_NAME = $2 order by SE.START_TIME desc, SE.STEP_EXECUTION_ID desc
执行计划
Sort (cost=154.08..154.11 rows=13 width=217) Sort Key: se.start_time DESC, se.step_execution_id DESC -> Hash Join (cost=69.71..153.83 rows=13 width=217) Hash Cond: (se.job_execution_id = je.job_execution_id) Join Filter: (SubPlan 1) -> Bitmap Heap Scan on n_batch_step_execution se (cost=4.49..88.54 rows=26 width=156) Recheck Cond: ((step_name)::text = 'notf0270f8e50fd95b44da8b34cb1ea829cc74'::text) -> Bitmap Index Scan on n_batch_step_exec_step_name_job_exec_id_idx (cost=0.00..4.48 rows=26 width=0) Index Cond: ((step_name)::text = 'notf0270f8e50fd95b44da8b34cb1ea829cc74'::text) -> Hash (cost=45.10..45.10 rows=1610 width=77) -> Seq Scan on n_batch_job_execution je (cost=0.00..45.10 rows=1610 width=77) SubPlan 1 -> Result (cost=0.00..45.10 rows=1610 width=8) One-Time Filter: (je.job_instance_id = 1) -> Seq Scan on n_batch_job_execution (cost=0.00..45.10 rows=1610 width=8)
二、Spring Batch源码中的SQL及执行计划
源码SQL片段
where SE.JOB_EXECUTION_ID in (SELECT JOB_EXECUTION_ID from N_BATCH_JOB_EXECUTION where JOB_INSTANCE_ID = $1)
对应执行计划
Sort (cost=17.74..17.75 rows=1 width=217) Sort Key: se.start_time DESC, se.step_execution_id DESC -> Nested Loop (cost=0.59..17.73 rows=1 width=217) -> Nested Loop (cost=0.30..16.35 rows=1 width=77) -> Index Scan using n_batch_job_execution_job_instance_id_idx on n_batch_job_execution (cost=0.15..8.17 rows=1 width=8) Index Cond: (job_instance_id = 1) -> Index Scan using n_batch_job_execution_pkey on n_batch_job_execution je (cost=0.15..8.17 rows=1 width=69) Index Cond: (job_execution_id = n_batch_job_execution.job_execution_id) -> Index Scan using n_batch_step_exe_idx on n_batch_step_execution se (cost=0.29..1.37 rows=1 width=156) Index Cond: (job_execution_id = je.job_execution_id) Filter: ((step_name)::text = 'notf0270f8e50fd95b44da8b34cb1ea829cc74'::text)
三、改写原因分析
排除Postgres服务端:Postgres只会对传入的SQL做查询优化(如谓词下推、连接顺序调整),但不会主动将非关联子查询改写成关联子查询(即不会给子查询的条件添加外层表别名)。执行计划中的全表扫描是因为关联子查询的特性导致无法优化,而非服务端改写SQL。
排除JDBC驱动:Postgres JDBC驱动默认仅负责SQL的传输、参数绑定和结果解析,没有改写SQL结构的逻辑。除非配置了特殊的批量插入类参数,但这类参数不会修改查询的关联逻辑。
最可能的来源:应用侧中间组件
- SQL拦截器/监控组件:应用中如果集成了性能监控、SQL审计类组件(如链路追踪框架、自定义AOP切面),可能会在SQL执行前做改写,误将外层表别名引入子查询。
- 动态SQL生成bug:如果应用通过ORM框架或自定义动态SQL逻辑拼接查询,可能在拼接过程中错误地将外层表的别名带入子查询条件。
- Spring Batch扩展改写:若项目对Spring Batch的
JdbcStepExecutionDao做了自定义扩展,可能在重写SQL时引入了错误。
总结:SQL的改写大概率来自应用侧的中间组件,建议排查应用中的SQL拦截逻辑、动态SQL生成代码或Spring Batch的自定义扩展。
内容的提问来源于stack exchange,提问作者tavi
相关产品推荐
相关产品推荐

