图片来自Pexels系统介绍。系统架构如下图所示:application1和application2是分布式系统中的两个应用。application1连接的数据库是database1,application2连接的数据库是database2,application2产生的数据要用于application1的批量运行。application1要获取database2的数据,不是通过接口,而是直接连接到database2,所以application1也有database2库的读权限。database2中有一张表table_b,里面存放的数据就是application1运行批处理所需要的数据。application1找到table_b的数据后,先保存到database1的数据库表table_a中,跑batch的时候取出来使用。table_a和table_b的表结构如下:两张表的主键都是字段a。application1查询table_b的数据后,会根据主键a判断数据是否存在。如果数据存在,则更新,否则插入。application1使用的orm框架是MyBatis。为了减少应用程序与数据库的交互,使用了Oracle的merge语句。注意与mybatis相关的文件有5个:TableAMapper.javaTableBMapper.javaTableAMapper.xmlTableBMapper.xmlTableAEntity.java熟悉MyBatis的同学应该知道,前两个Java类是SQL操作接口类,第三个和第四个两个文件是存储SQL的XML文件,对应前两个文件,最后一个Java文件是do类。事故现场TableBMapper中有一个方法selectForPage,用于逐页查询table_b中的数据,每页10000条数据,然后将列表结果合并到table_a中。看代码://每页10000条从table_b中查询数据Listlist=tableBMapper.selectForPage(startPage,10000);//将查到的数据一次性合并到table_a中tableAMapper.mergeFromTableB(list);让我们看一下TableAMapper.xml中的mergeFromTableB方法。代码如下:MERGEINTOtable_ataUSING(select#{item.a}asa,#{item.b}asb,#{item.c}asc,#{item.d}asdfromdual)tbon(ta.a=tb.a)WHENMATCHEDTHENUPDATEsetta.b=待定。b,ta.c=tb.c,ta.d=tb.dWHENNOTMATCHEDTHENinsert(a,b,c,d)values(tb.a,tb.b,tb.c,tb.d)注意:为了文章排版,我简化了表格结构。在真实情况下,table_a有60多个字段。这条SQL执行完后,截取了一些Oracle的日志,如下:图中可以看到Oracle报了ORA-07445错误。分析日志发现SQL绑定变量的个数已经达到了79010个,而Oracle不允许超过65535个。前面分析解决方案,确定Oracle挂掉的原因是绑定变量数超过65535,所以问题解决方案有3种:业务系统解决方案①单循环执行merge语句。优点是修改方便,缺点是业务系统和数据库交互过多,会影响运行批任务的效率。②分批调用mergeFromTableB,比如每1000条调用一次merge方法,改造多一点,但是交互会少很多。DBA解决方案是给Oracle打补丁,需要停止服务。商业计划书2的细节有优势。我是用这个方案改造的,每次1000块,分批合并。代码如下:for(inti=0;iinsertallintotable_a_tempa,b,c,d,#{item.a},#{item.b,jdbcType=VARCHAR},#{item.c,jdbcType=VARCHAR},#{item.d,jdbcType=VARCHAR},select1fromdual注意:Oracle的insertall语句一次只能插入1000条以上③要将临时表的数据合并到table_a中,需要在TableAMapper中添加一个方法.xml:MERGEINTOtable_ataUSING(select*fromtable_a_temp)tbon(ta.a=tb.a)WHENMATCHEDTHENUPDATEsetta.b=tb.b,ta.c=tb.c,ta.d=tb.dWHENNOTMATCHEDTHENinsert(a,b,c,d)values(tb.a,tb.b,tb.c,tb.d)④最终业务代码修改如下://QueryListlistfromtable_b=tableBMapper.selectForPage(startPage,10000);//批量插入table_a_temp临时表for(inti=0;i