zoukankan      html  css  js  c++  java
  • ORA01779 & BYPASS_UJVC

    问题陈述

    在调用一个存储过程的时候,发现有个UPDATE语句执行的很慢,这个UPDATE语句是很常见的用一个表中的数据去更新另外一个表中的数据,通常的做法是通过一个关联子查询从"source table" 读取相应的数据来更新"target table"中对应的数据行。同时,为了保证"target table" 中不相关的数据行不会被更新,因此在where语句部分还是要和"source table"做个“join"。很显然,source table会被读取多次,而且由于关联查询的存在,表的连接方式会倾向于Nested Loop Join方式,而不会是Hash Join.

    下面是原始的update语句...

    UPDATE GEM_CURR_AUTHORIZATION GCA

    SET (TIMESTAMP,

    STATUS,

    ORIGINAL_AUTHORIZATION,

    CURRENCY,

    CURRENT_AUTHORIZATION,

    EXPIRY_DATE,

    BLOCK_DATE,

    LIMIT_MODE)
    =

    (
    SELECT SYSDATE,

    STATUS,

    ORIGINAL_AUTHORIZATION,

    CURRENCY,

    CROSS_CCY (ORIGINAL_AUTHORIZATION,

    CURRENCY,

    PACK_GEM_COMMON.GET_METRIC_CURRENCY (METRIC_ID)),

    EXPIRY_DATE,

    BLOCK_DATE,

    LIMIT_MODE

    FROM GEM_ORIG_AUTHORIZATION GOA

    WHERE GOA.COMBINATION_ID = GCA.COMBINATION_ID

    AND NVL (GOA.METRIC_ID, 0) = NVL (GCA.METRIC_ID, 0))

    WHERE ( (GCA.COMBINATION_ID = :B2 AND NVL (GCA.METRIC_ID, 0) = :B1)

    OR EXISTS

    (
    SELECT NULL

    FROM AUTHORIZATION_LENDING LENDER

    WHERE LENDER.SOURCE_COMBINATION_ID = :B2

    AND NVL (LENDER.METRIC_ID, 0) = :B1

    AND LENDER.DEST_COMBINATION_ID = GCA.COMBINATION_ID)

    OR (:B2 IS NULL AND :B1 IS NULL))

    AND EXISTS

    (
    SELECT NULL

    FROM GEM_ORIG_AUTHORIZATION GOA

    WHERE GOA.COMBINATION_ID = GCA.COMBINATION_ID

    AND NVL (GOA.METRIC_ID, 0) = NVL (GCA.METRIC_ID, 0));

    下面是我尝试改写的SQL...

    update

    (

    select

    gca.
    timestamp as dest_timestamp,

    gca.status
    as dest_status,

    gca.original_authorization
    as dest_orig_limit,

    gca.currency
    as dest_currency,

    gca.current_authorization
    as dest_curr_limit,

    gca.expiry_date
    as dest_expiry_date,

    gca.block_date
    as dest_block_date,

    gca.limit_mode
    as dest_limit_mode,

    goa.status
    as src_status,

    goa.original_authorization
    as src_orig_limit,

    goa.currency
    as src_currency,

    goa.metric_id
    as src_metric,

    goa.expiry_date
    as src_expiry_date,

    goa.block_date
    as src_block_date,

    goa.limit_mode
    as src_limit_mode

    from GEM_ORIG_AUTHORIZATION goa,

    GEM_CURR_AUTHORIZATION gca

    where goa.combination_id = gca.combination_id

    and nvl(goa.metric_id, 0) = nvl(gca.metric_id, 0)

    and ( (gca.combination_id = :B2 and nvl(gca.metric_id, 0) = :B1)

    OR (:B2 IS NULL AND :B1 IS NULL)

    OR EXISTS

    (
    SELECT NULL

    FROM AUTHORIZATION_LENDING LENDER

    WHERE LENDER.SOURCE_COMBINATION_ID = :B2

    AND NVL (LENDER.METRIC_ID, 0) = :B1

    AND LENDER.DEST_COMBINATION_ID = GCA.COMBINATION_ID)

    )

    )

    set dest_timestamp = sysdate,

    dest_status
    = src_status,

    dest_orig_limit
    = src_orig_limit,

    dest_currency
    = src_currency,

    dest_curr_limit
    = CROSS_CCY (src_orig_limit, src_currency, PACK_GEM_COMMON.GET_METRIC_CURRENCY (src_metric)),

    dest_expiry_date
    = src_expiry_date,

    dest_block_date
    = src_block_date,

    dest_limit_mode
    = src_limit_mode;

    但是杯具的是,当我执行新的SQL的时候Oracle报出了如下的错误...

    ORA-01779: cannot modify a column which maps to a non key-preserved table

    出错的原因是因为我的那个inline view中的"source table" -- GEM_ORIG_AUTHORIZATION用来同"target table" -- GEM_CURR_AUTHORIZATION做连接操作的数据行有可能不是是唯一的,因此在进行update操作的时候,对于目标表的一行有可能对应源表中的多行,这样在update操作的时候有可能会得到错误的结果。

    错误分析

    下面来测试一下什么情况下位报这种错误...


    drop
    table fang_test_1;

    drop table fang_test_2;

    create table fang_test_1 (id number, name varchar2(10));

    create table fang_test_2 (id number, name varchar2(10));

    insert into fang_test_1 values(1, 'Fang');

    insert into fang_test_1 values(2, 'Fraud');

    insert into fang_test_2 values(1, 'Frank');

     现在用表fang_test_1中的name更新fang_test_2中的name...

    update

    (

    select a.id a_id, a.name a_name, b.id b_id, b.name b_name

    from fang_test_1 a, fang_test_2 b

    where a.id = b.id

    )

    set b_name = a_name;

    执行的时候出现错误ORA-01779!

    尝试给源表fang_test_1的id列(因为两个表是通过ID列做join的)上创建unique index...

    create unique index ui_fang_test_1 on fang_test_1(id);

     然后再执行update操作就没有问题了。如果我创建一个联合的unique index会怎么样...

    create unique index ui_fang_test_1 on fang_test_1(id, name);

    在执行update操作的时候同样报ORA-01779。 这个也是很好理解的,毕竟unique index是建立在id 和name 这两列之上的,而表fang_test_1和表fang_test_2只是通过id列做连接,这个并不能保证fang_test_1的数据行是唯一的。当然如果把f表fang_test_1和fang_test_2的连接条件改成a.id = b.id  and a.name = b.name 那么update 操作就没问题了。

     

    现在把update语句改成如下形式…

    update

    (

    select a.id a_id, a.name a_name, b.id b_id, b.name b_name

    from fang_test_1 a, fang_test_2 b

    where a.id+1 = b.id and a.name = b.name

    )

    set b_name = a_name;

    注意fang_test_1和fang_test_2在id列上的连接条件有点小小的改变 – a.id + 1 = b.id. 这个时候再执行update操作的话,却会报ora-01779! 类似的,如果在列id或name上做一些小小的操作,比如加个函数啥的,如下…

    update

    (

    select a.id a_id, a.name a_name, b.id b_id, b.name b_name

    from fang_test_1 a, fang_test_2 b

    where a.id = b.id and nvl(a.name, 'TEST') = b.name

    )

    set b_name = a_name;

    这个时候即使更新unique index 为

    create unique index ui_fang_test_1 on fang_test_1(id, nvl(name, 'TEST'));

    执行update操作的时候也会报 ora-01779, 虽然我们知道这个连接条件能保证唯一性,但是Oracle却赤裸裸地无视之!

    总结原因

    总结下什么情况会报ora-01779

    (1)   源表在连接的列上没有建立unique index

    (2)   如果unique index是个组合索引,而源表和目标表做join的列只是这些组合索引中的部分列

    (3)   做连接的列有其他操作,比如说函数啊之类。 注意这个列不论是源表还是目标表的都会报错。

    Oracle Hint BYPASS_UJVC 

    对于前两种情况,应该考虑更改SQL语句,对于第三种情况,如果确定Update不会有问题同时又想oracle不报ora-01779这个错误,这个时候可以借助oracle hint /*+ BYPASS_UJVC */. 在inline view的select语句部分加上这个hint, 可以让oracle不做这种数据唯一性的检查,当然结果正确与否只有自己保证了。

    解决问题

    回到刚开始提到的改写的那个Update SQL语句,其实表GEM_ORIG_AUTHORIZATION上面在列(combination_id, metric_id)上有个组合的unique index的,但是因为在做join的时候,列metric_id上用了NVL函数导致了会报错,因此这里面为了执行这个update语句,只好借助于hint /*+ BYPASS_UJVC */, 如下所示…

    update

    (

    select /*+ BYPASS_UJVC */

    gca.
    timestamp as dest_timestamp,

    gca.status
    as dest_status,

    gca.original_authorization
    as dest_orig_limit,

    gca.currency
    as dest_currency,

    gca.current_authorization
    as dest_curr_limit,

    gca.expiry_date
    as dest_expiry_date,

    gca.block_date
    as dest_block_date,

    gca.limit_mode
    as dest_limit_mode,

    goa.status
    as src_status,

    goa.original_authorization
    as src_orig_limit,

    goa.currency
    as src_currency,

    goa.combination_id,

    goa.metric_id
    as src_metric,

    goa.expiry_date
    as src_expiry_date,

    goa.block_date
    as src_block_date,

    goa.limit_mode
    as src_limit_mode

    from GEM_ORIG_AUTHORIZATION goa,

    GEM_CURR_AUTHORIZATION gca

    where goa.combination_id = gca.combination_id

    and nvl(goa.metric_id, 0) = nvl(gca.metric_id, 0)

    and ( (gca.combination_id = :B2 and nvl(gca.metric_id, 0) = :B1)

    OR (:B2 IS NULL AND :B1 IS NULL)

    OR EXISTS

    (
    SELECT NULL

    FROM AUTHORIZATION_LENDING LENDER

    WHERE LENDER.SOURCE_COMBINATION_ID = :B2

    AND NVL (LENDER.METRIC_ID, 0) = :B1

    AND LENDER.DEST_COMBINATION_ID = GCA.COMBINATION_ID)

    )

    )

    set dest_timestamp = sysdate,

    dest_status
    = src_status,

    dest_orig_limit
    = src_orig_limit,

    dest_currency
    = src_currency,

    dest_curr_limit
    = CROSS_CCY (src_orig_limit, src_currency, PACK_GEM_COMMON.GET_METRIC_CURRENCY (src_metric)),

    dest_expiry_date
    = src_expiry_date,

    dest_block_date
    = src_block_date,

    dest_limit_mode
    = src_limit_mode;

    对改写前后的SQL进行了简单的测试,发现还是有很大区别的,如下 (通过设置10046事件 + tkprof得到)

    改写前的SQL 的执行情况…

    call count cpu elapsed disk query current rows

    ------- ------ -------- ---------- ---------- ---------- ---------- ----------

    Parse
    1 0.09 0.09 0 0 0 0

    Execute 1 0.16 0.17 0 213 126 61

    Fetch 0 0.00 0.00 0 0 0 0

    ------- ------ -------- ---------- ---------- ---------- ---------- ----------

    total
    2 0.25 0.26 0 213 126 61

    Misses
    in library cache during parse: 0

    Optimizer mode: ALL_ROWS

    Parsing
    user id: 1182

    Rows Row Source Operation

    ------- ---------------------------------------------------

    0 UPDATE GEM_CURR_AUTHORIZATION (cr=315 pr=0 pw=0 time=168567 us)

    61 HASH JOIN RIGHT SEMI (cr=30 pr=0 pw=0 time=4042 us)

    818 PARTITION RANGE SINGLE PARTITION: 2 2 (cr=7 pr=0 pw=0 time=3588 us)

    818 INDEX FAST FULL SCAN I1_GEM_ORIG_AUTHORIZATION PARTITION: 2 2 (cr=7 pr=0 pw=0 time=1940 us)(object id 362634)

    1209 PARTITION RANGE SINGLE PARTITION: 2 2 (cr=23 pr=0 pw=0 time=6093 us)

    1209 TABLE ACCESS FULL GEM_CURR_AUTHORIZATION PARTITION: 2 2 (cr=23 pr=0 pw=0 time=2460 us)

    61 PARTITION RANGE SINGLE PARTITION: 2 2 (cr=183 pr=0 pw=0 time=2322 us)

    61 TABLE ACCESS BY LOCAL INDEX ROWID GEM_ORIG_AUTHORIZATION PARTITION: 2 2 (cr=183 pr=0 pw=0 time=1694 us)

    61 INDEX RANGE SCAN I1_GEM_ORIG_AUTHORIZATION PARTITION: 2 2 (cr=122 pr=0 pw=0 time=966 us)(object id 362634)

    改写后的SQL执行情况…

    call count cpu elapsed disk query current rows

    ------- ------ -------- ---------- ---------- ---------- ---------- ----------

    Parse
    1 0.01 0.00 0 0 0 0

    Execute 1 0.00 0.00 0 39 126 61

    Fetch 0 0.00 0.00 0 0 0 0

    ------- ------ -------- ---------- ---------- ---------- ---------- ----------

    total
    2 0.01 0.01 0 39 126 61

    Misses
    in library cache during parse: 1

    Optimizer mode: ALL_ROWS

    Parsing
    user id: 1182

    Rows Row Source Operation

    ------- ---------------------------------------------------

    0 UPDATE GEM_CURR_AUTHORIZATION (cr=119 pr=0 pw=0 time=12072 us)

    61 HASH JOIN (cr=39 pr=0 pw=0 time=2601 us)

    818 PARTITION RANGE SINGLE PARTITION: 2 2 (cr=16 pr=0 pw=0 time=3344 us)

    818 TABLE ACCESS FULL GEM_ORIG_AUTHORIZATION PARTITION: 2 2 (cr=16 pr=0 pw=0 time=1696 us)

    1209 PARTITION RANGE SINGLE PARTITION: 2 2 (cr=23 pr=0 pw=0 time=7284 us)

    1209 TABLE ACCESS FULL GEM_CURR_AUTHORIZATION PARTITION: 2 2 (cr=23 pr=0 pw=0 time=4860 us)

     改写后的SQL执行明显效率更高,query数量大幅度降低。

  • 相关阅读:
    java开学第一周测试自我感想
    暑假第八周进度报告
    暑假第七周进度报告
    暑假第六周进度报告
    暑假第五周进度报告
    暑假第四周进度报告
    暑假第三周进度报告
    暑假第二周进度报告
    《大道至简》读后感
    暑假第一周进度报告
  • 原文地址:https://www.cnblogs.com/fangwenyu/p/2016056.html
Copyright © 2011-2022 走看看