当前位置: 首页 > 科技观察

震惊,一条SQL让Oracle崩溃

时间:2023-03-22 13:02:59 科技观察

本文转载自微信公众号《程序员jinjunzhu》,作者jinjunzhu。转载本文请联系程序员jinjunzhu公众号。jinjunzhu可以让oracle单条sql挂掉。我真的不敢相信。前几天在生产中确实出现过这样的故障。让我们一起重播该事件。系统介绍系统架构如下图所示:application1和application2是分布式系统中的两个应用,application1连接的数据库是database1,application2连接的数据库是database2,application2产生的数据用于批量运行通过申请1。application1要获取database2的数据,不是通过接口,而是直接连接到database2,所以application1也有database2的读权限。database2中有一张表table_b,里面存放的数据就是application1运行批处理所需要的数据。application1找到table_b的数据后,先保存到database1的数据库表table_a中,运行批处理的时候取出来使用。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操作接口类,第三个和第四个文件用于存储sqlxml文件对应前两个文件,最后一个java文件是do类。事故现场TableBMapper中有一个方法selectForPage,用于逐页查询table_b中的数据,每页10000条数据,然后将列表结果合并到table_a中,看代码://每页10000条从table_b中查询DataListlist=tableBMapper.selectForPage(startPage,10000);//将查到的数据一次性合并到table_a中tableAMapper.mergeFromTableB(list);我们再看一下TableAMapper.xml中的mergeFromTableB方法,代码如下:"close=";end;"open="begin">MERGEINTOtable_ataUSING(select#{item.a}asa,#{item.b}asb,#{item.c}asc,#{item.d}asdfromdual)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)注:为了文章的排版,我简化了表格结构。在真实情况下,table_a有60多个字段。这条sql执行完后,截取了一些oracle的日志,如下:图中可以看到oracle报了ORA-07445错误。分析日志发现sql绑定变量数达到了79010个,而oracle不允许超过65535个。前面分析解决方案确定oraclehangup的原因是绑定变量数超过65535,所以该案例有3种解决方案:业务系统解决方案1.merge语句单循环执行。优点是修改方便,缺点是业务系统和数据库交互过多,会影响批任务运行效率。2.分批调用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条以上3.将临时表的数据合并到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)值(tb.a,tb.b,tb.c,tb.d)4.最终业务代码修改如下://queryListlistfromtable_b=tableBMapper.selectForPage(startPage,10000);//批量插入table_a_temp临时表for(inti=0;i