惊呆,一条SQL竟然让Oracle奔溃了

运维 数据库运维
一条sql就能让oracle挂了,真的是不敢相信啊,前几天生产上确实出现了这样一个故障,我们来一起做一个事件回放。

[[381485]]

 本文转载自微信公众号「程序员jinjunzhu」,作者jinjunzhu。转载本文请联系程序员jinjunzhu公众号。 jinjunzhu  

一条sql就能让oracle挂了,真的是不敢相信啊,前几天生产上确实出现了这样一个故障,我们来一起做一个事件回放。

系统介绍

系统架构见下图:

application1和application2是一个分布式系统中的2个应用,application1连接的数据库是database1,application2连接的数据库是database2,application2生产的数据要给application1做跑批使用。

application1要获取database2的数据,并不是通过接口来获取的,而是直连database2来获取,因此application1也具有database2库的读权限。

database2中有1张表table_b,里面保存的数据是application1跑批需要的数据。application1查找到table_b的数据后,先保存到database1的数据库表table_a中,等跑批时取出来用。

table_a和table_b的表结构如下:

2个表的主键都是字段a,application1查询出table_b的数据后,会根据主键a来判断这条数据是否存在,如果数据存在,就更新,否则,就插入。

application1使用的orm框架是mybatis,为了减少应用和数据库的交互,使用了oracle的merge语句。

注意:mybatis相关的文件有5个:

TableAMapper.java

TableBMapper.java

TableAMapper.xml

TableBMapper.xml

TableAEntity.java

熟悉mybatis的同学应该都知道,前两个java类是sql操作接口类,第3、4两个文件是存放sql的xml文件,跟前两个文件对应,最后一个java文件是do类。

事故现场

TableBMapper中有一个方法selectForPage,用来按页查询table_b中数据,每页1万条数据,之后把这个list结果merge到table_a,看一下代码:

  1. //从table_b按每页1万条来查询数据 
  2. List<TableAEntity> list = tableBMapper.selectForPage(startPage, 10000); 
  3. //把查到的数据一次性merge到table_a中 
  4. tableAMapper.mergeFromTableB(list); 

我们再看一下TableAMapper.xml中的mergeFromTableB方法,代码如下:

  1. <update id="mergeFromTableB" parameterType="list"
  2.   <foreach collection="list" item="item" index="index" separator=";" close=";end;" open="begin"
  3.       MERGE INTO table_a ta USING(select #{item.a} as a,#{item.b} as b,#{item.c} as c, #{item.d} as d from dual) tb 
  4.       on (ta.a = tb.a) 
  5.       WHEN MATCHED THEN UPDATE set 
  6.       ta.b=tb.b, 
  7.       ta.c=tb.c, 
  8.       ta.d=tb.d 
  9.       WHEN NOT MATCHED THEN insert
  10.       a, 
  11.       b, 
  12.       c, 
  13.       d 
  14.       ) 
  15.       values ( 
  16.       tb.a, 
  17.       tb.b, 
  18.       tb.c, 
  19.       tb.d 
  20.       ) 
  21.     </foreach> 
  22. </update

注意:为了文章排版,我对表结构做了简化,真实案例中table_a这张表有60多个字段。

这条sql执行后,我截取部分oracle的日志,如下:

图中可以看到oracle报了ORA-07445错误。

分析日志后发现,sql绑定变量达到了了79010个,而oracle是不允许超过65535个的。

解决方案

前面的分析确定了导致oracle挂掉的原因是绑定变量超过了65535个,那对症下药,解决的方案有3个:

业务系统方案

1.循环单条执行merge语句,优点是修改简单,缺点是业务系统跟数据库交互太多,会影响跑批任务执行效率。

2.对mergeFromTableB进行分批调用,比如每1000条调用一次merge方法,改造稍微多一点,但是交互会少很多。

DBA方案

给oracle打一个补丁,这个方案需要停服务。

业务方案2明细有优势,我用这个方案进行了改造,每次1000条,批量merge,代码如下:

  1. for (int i = 0; i < list.size(); i += 1000) { 
  2.     if (i + 1000 < list.size()) { 
  3.         tableAMapper.mergeFromTableB(list.subList(i, i + 1000)); 
  4.     } else { 
  5.         tableAMapper.mergeFromTableB(list.subList(i, list.size())); 
  6.     } 

新的问题

按照上面的方案改造完成后,数据库不会奔溃了,但是新的问题出现了。测试的同学发现,每次处理超过1000条数据,非常耗时,有时竟然达到了4分钟,惊呆。

看打印的批量sql,类似于下面的语句:

  1. begin 
  2. merge into table_a ta USING(...; 
  3. merge into table_a ta USING(...; 
  4. end

分析了一下,虽然放在了一个SQL块中,但还是单条执行,最后一起提交。

再做一次优化,把上面多条merge语句合成1条。

我的优化思路是创建一张临时表,先把list中的数据插入到临时表中,然后用一次merge把临时表的数据merge进table_a这张表。

oracle的临时表有2种,一种是会话级别,一种是事务级别:

1.会话级别的临时表,数据会在整个会话的生命周期中,会话结束,临时表数据清空;

2.事务级别的临时表,数据会在整个事务执行过程中,事务结束,临时表数据清空。

下面看具体实施过程。

1.我们创建一张会话临时表,SQL如下:

  1. create global temporary table_a_temp on commit delete rows as select * from table_a; 
  2. comment on table_a_temp is 'table_a表临时表'

2.把table_b查询到的数据list插入临时表,需要在 TableAMapper.xml 增加一个方法:

  1. <insert id="batchInsertTemp" parameterType="list"
  2.   insert all 
  3.   <foreach collection="list" index="index" item="item"
  4.     into table_a_temp 
  5.     <trim prefix="(" suffix=")" suffixOverrides="," > 
  6.       a, 
  7.       <if test="item.b != null" > 
  8.         b, 
  9.       </if> 
  10.       <if test="item.c != null" > 
  11.         c, 
  12.       </if> 
  13.       <if test="item.d != null" > 
  14.         d, 
  15.       </if> 
  16.     </trim> 
  17.     <trim prefix="values (" suffix=")" suffixOverrides="," > 
  18.       #{item.a}, 
  19.       <if test="item.b != null" > 
  20.         #{item.b,jdbcType=VARCHAR}, 
  21.       </if> 
  22.       <if test="item.c != null" > 
  23.         #{item.c,jdbcType=VARCHAR}, 
  24.       </if> 
  25.       <if test="item.d != null" > 
  26.         #{item.d,jdbcType=VARCHAR}, 
  27.       </if> 
  28.     </trim> 
  29.   </foreach> 
  30.   select 1 from dual 
  31. </insert

注意:oracle的insert all语句单次插入不能超过1000条。

3.把临时表的数据merge到table_a中,需要在 TableAMapper.xml 增加一个方法:

  1. <update id="mergeFromTempData"
  2.   MERGE INTO table_a ta 
  3.     USING (select * from table_a_temp) tb 
  4.     on (ta.a = tb.a) 
  5.     WHEN MATCHED THEN UPDATE set 
  6.   ta.b = tb.b, 
  7.   ta.c = tb.c, 
  8.   ta.d = tb.d 
  9.   WHEN NOT MATCHED THEN 
  10.   insert 
  11.   (a, b, c, d) 
  12.   values 
  13.   (tb.a, tb.b, tb.c, tb.d) 
  14. </update

4.最终业务代码修改如下:

  1. //从table_b查询 
  2. List<TableAEntity> list = tableBMapper.selectForPage(startPage, 10000); 
  3. //批量插入table_a_temp临时表 
  4. for (int i = 0; i < list.size(); i += 1000) { 
  5.     if (i + 1000 < list.size()) { 
  6.         tableAMapper.batchInsertTemp(list.subList(i, i + 1000)); 
  7.     } else { 
  8.         tableAMapper.batchInsertTemp(list.subList(i, list.size())); 
  9.     } 
  10. //从table_a_temp把数据merge到table_a 
  11. tableAMapper.mergeFromTempData(); 

总结

在oracle上执行SQL时,如果绑定变量的数量超过了65535,会引发ORA-07445。当然,引发ORA-07445的原因还有其他。

解决这个问题最好的方式是从业务代码层面进行修改。

也可以让DBA可以给oracle打一个补丁,但是oracle必须要停服务。

延伸阅读:

https://community.oracle.com/tech/apps-infra/discussion/2424571/ora-07445-exception-encountered-core-dump-ptmak-106-sigsegv-addres

 

责任编辑:武晓燕 来源: 程序员jinjunzhu
相关推荐

2021-04-16 07:04:53

SQLOracle故障

2020-02-09 16:52:02

睡醒公司倒闭

2021-09-30 08:40:28

Oracle数据库后端开发

2020-10-26 08:02:28

SQL慢查询索引

2020-05-27 11:55:47

Oracle SQL性能优化数据库

2021-04-09 08:13:14

API网关互联网

2010-11-15 14:16:09

Oracle表记录

2024-07-29 09:49:00

SQLMySQL执行

2023-09-01 14:07:00

ChatGPTGPT数据分析

2020-07-01 09:07:52

SQL索引语句

2011-03-23 13:52:09

ORACLESQL十进制

2017-10-23 15:17:42

技术业务职位

2022-10-12 07:38:24

SQL语句异常

2024-02-01 18:07:37

2023-03-26 22:42:02

SQL关联索引

2022-02-11 14:43:53

SQL语句C/S架构

2022-05-31 13:58:09

MySQL查询语句

2021-09-15 06:21:36

Update语句数据库

2023-10-06 15:29:07

MySQL数据库更新

2020-04-17 14:16:10

SQL数据库HTTP
点赞
收藏

51CTO技术栈公众号