<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          驚呆,一條sql竟然讓oracle崩潰了

          共 7835字,需瀏覽 16分鐘

           ·

          2021-05-19 12:37


          來自公眾號:程序員jinjunzhu

          一條sql就能讓oracle掛了,真的是不敢相信啊,前幾天生產(chǎn)上確實出現(xiàn)了這樣一個故障,我們來一起做一個事件回放。

          系統(tǒng)介紹

          系統(tǒng)架構(gòu)見下圖:

          application1和application2是一個分布式系統(tǒng)中的2個應用,application1連接的數(shù)據(jù)庫是database1,application2連接的數(shù)據(jù)庫是database2,application2生產(chǎn)的數(shù)據(jù)要給application1做跑批使用。

          application1要獲取database2的數(shù)據(jù),并不是通過接口來獲取的,而是直連database2來獲取,因此application1也具有database2庫的讀權限。

          database2中有1張表table_b,里面保存的數(shù)據(jù)是application1跑批需要的數(shù)據(jù)。application1查找到table_b的數(shù)據(jù)后,先保存到database1的數(shù)據(jù)庫表table_a中,等跑批時取出來用。

          table_a和table_b的表結(jié)構(gòu)如下:

          2個表的主鍵都是字段a,application1查詢出table_b的數(shù)據(jù)后,會根據(jù)主鍵a來判斷這條數(shù)據(jù)是否存在,如果數(shù)據(jù)存在,就更新,否則,就插入。

          application1使用的orm框架是mybatis,為了減少應用和數(shù)據(jù)庫的交互,使用了oracle的merge語句。

          注意:mybatis相關的文件有5個:
          TableAMapper.java
          TableBMapper.java
          TableAMapper.xml
          TableBMapper.xml
          TableAEntity.java

          熟悉mybatis的同學應該都知道,前兩個java類是sql操作接口類,第3、4兩個文件是存放sql的xml文件,跟前兩個文件對應,最后一個java文件是do類。

          事故現(xiàn)場

          TableBMapper中有一個方法selectForPage,用來按頁查詢table_b中數(shù)據(jù),每頁1萬條數(shù)據(jù),之后把這個list結(jié)果merge到table_a,看一下代碼:

          //從table_b按每頁1萬條來查詢數(shù)據(jù)
          List<TableAEntity> list = tableBMapper.selectForPage(startPage, 10000);
          //把查到的數(shù)據(jù)一次性merge到table_a中
          tableAMapper.mergeFromTableB(list);

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

          <update id="mergeFromTableB" parameterType="list">
            <foreach collection="list" item="item" index="index" separator=";" close=";end;" open="begin">
                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
                on (ta.a = tb.a)
                WHEN MATCHED THEN UPDATE set
                ta.b=tb.b,
                ta.c=tb.c,
                ta.d=tb.d
                WHEN NOT MATCHED THEN insert(
                a,
                b,
                c,
                d
                )
                values (
                tb.a,
                tb.b,
                tb.c,
                tb.d
                )
              </foreach>
          </update>

          注意:為了文章排版,我對表結(jié)構(gòu)做了簡化,真實案例中table_a這張表有60多個字段。

          這條sql執(zhí)行后,我截取部分oracle的日志,如下:

          圖中可以看到oracle報了ORA-07445錯誤。

          分析日志后發(fā)現(xiàn),sql綁定變量達到了了79010個,而oracle是不允許超過65535個的。

          解決方案

          前面的分析確定了導致oracle掛掉的原因是綁定變量超過了65535個,那對癥下藥,解決的方案有3個:

          業(yè)務系統(tǒng)方案

          1.循環(huán)單條執(zhí)行merge語句,優(yōu)點是修改簡單,缺點是業(yè)務系統(tǒng)跟數(shù)據(jù)庫交互太多,會影響跑批任務執(zhí)行效率。
          2.對mergeFromTableB進行分批調(diào)用,比如每1000條調(diào)用一次merge方法,改造稍微多一點,但是交互會少很多。

          DBA方案

          給oracle打一個補丁,這個方案需要停服務。

          業(yè)務方案2明細有優(yōu)勢,我用這個方案進行了改造,每次1000條,批量merge,代碼如下:

          for (int i = 0; i < list.size(); i += 1000) {
              if (i + 1000 < list.size()) {
                  tableAMapper.mergeFromTableB(list.subList(i, i + 1000));
              } else {
                  tableAMapper.mergeFromTableB(list.subList(i, list.size()));
              }
          }

          新的問題

          按照上面的方案改造完成后,數(shù)據(jù)庫不會奔潰了,但是新的問題出現(xiàn)了。測試的同學發(fā)現(xiàn),每次處理超過1000條數(shù)據(jù),非常耗時,有時竟然達到了4分鐘,驚呆。

          看打印的批量sql,類似于下面的語句:

          begin
          merge into table_a ta USING(...;
          merge into table_a ta USING(...;
          end;

          分析了一下,雖然放在了一個SQL塊中,但還是單條執(zhí)行,最后一起提交。

          再做一次優(yōu)化,把上面多條merge語句合成1條。

          我的優(yōu)化思路是創(chuàng)建一張臨時表,先把list中的數(shù)據(jù)插入到臨時表中,然后用一次merge把臨時表的數(shù)據(jù)merge進table_a這張表。

          oracle的臨時表有2種,一種是會話級別,一種是事務級別:
          1.會話級別的臨時表,數(shù)據(jù)會在整個會話的生命周期中,會話結(jié)束,臨時表數(shù)據(jù)清空;
          2.事務級別的臨時表,數(shù)據(jù)會在個事務執(zhí)行過程中,事務結(jié)束,臨時表數(shù)據(jù)清空。

          下面看具體實施過程。

          1.我們創(chuàng)建一張會話臨時表,SQL如下:

          create global temporary table_a_temp on commit delete rows as select * from table_a;
          comment on table_a_temp is 'table_a表臨時表';

          2.把table_b查詢到的數(shù)據(jù)list插入臨時表,需要在 TableAMapper.xml 增加一個方法:

          <insert id="batchInsertTemp" parameterType="list">
            insert all
            <foreach collection="list" index="index" item="item">
              into table_a_temp
              <trim prefix="(" suffix=")" suffixOverrides="," >
                a,
                <if test="item.b != null" >
                  b,
                </if>
                <if test="item.c != null" >
                  c,
                </if>
                <if test="item.d != null" >
                  d,
                </if>
              </trim>
              <trim prefix="values (" suffix=")" suffixOverrides="," >
                #{item.a},
                <if test="item.b != null" >
                  #{item.b,jdbcType=VARCHAR},
                </if>
                <if test="item.c != null" >
                  #{item.c,jdbcType=VARCHAR},
                </if>
                <if test="item.d != null" >
                  #{item.d,jdbcType=VARCHAR},
                </if>
              </trim>
            </foreach>
            select 1 from dual
          </insert>

          注意:oracle的insert all語句單次插入不能超過1000條。

          3.把臨時表的數(shù)據(jù)merge到table_a中,需要在 TableAMapper.xml 增加一個方法:

          <update id="mergeFromTempData">
            MERGE INTO table_a ta
              USING (select * from table_a_temp) tb
              on (ta.a = tb.a)
              WHEN MATCHED THEN UPDATE set
            ta.b = tb.b,
            ta.c = tb.c,
            ta.d = tb.d
            WHEN NOT MATCHED THEN
            insert
            (a, b, c, d)
            values
            (tb.a, tb.b, tb.c, tb.d)
          </update>

          4.最終業(yè)務代碼修改如下:

          //從table_b查詢
          List<TableAEntity> list = tableBMapper.selectForPage(startPage, 10000);
          //批量插入table_a_temp臨時表
          for (int i = 0; i < list.size(); i += 1000) {
              if (i + 1000 < list.size()) {
                  tableAMapper.batchInsertTemp(list.subList(i, i + 1000));
              } else {
                  tableAMapper.batchInsertTemp(list.subList(i, list.size()));
              }
          }
          //從table_a_temp把數(shù)據(jù)merge到table_a
          tableAMapper.mergeFromTempData();

          總結(jié)

          在oracle上執(zhí)行SQL時,如果綁定變量的數(shù)量超過了65535,會引發(fā)ORA-07445。當然,引發(fā)ORA-07445的原因還有其他。
          解決這個問題最好的方式是從業(yè)務代碼層面進行修改。
          也可以讓DBA可以給oracle打一個補丁,但是oracle必須要停服務。

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

          1、最牛逼的 Java 日志框架,性能無敵,橫掃所有對手!
          2、把Redis當作隊列來用,真的合適嗎?
          3、驚呆了,Spring Boot居然這么耗內(nèi)存!你知道嗎?
          4、牛逼哄哄的 BitMap,到底牛逼在哪?
          5、全網(wǎng)最全 Java 日志框架適配方案!還有誰不會?
          6、30個IDEA插件總有一款適合你
          7、Spring中毒太深,離開Spring我居然連最基本的接口都不會寫了

          點分享

          點收藏

          點點贊

          點在看 

          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人色人人色人人色 | 久久久久久久久久一级 | 国产男女日bb的视频 | 狠狠狠狠狠干 | 黄色一级大片在线免费观看 |