如何使用来自另一个表列的值优化列的更新?

Posted

技术标签:

【中文标题】如何使用来自另一个表列的值优化列的更新?【英文标题】:How to optimize an update of a column using values from another table's column? 【发布时间】:2017-09-22 21:15:41 【问题描述】:

我在更新表 t1 中的字段时遇到问题,从表 t2 中获取相同字段的值。我的问题是表t1 有10 万条记录,而表t2 有20000 条记录。

当我运行这个时:

update cajas t1
set t1.anio   = (select anio from tempos_Cajas t2
              where t1.cliente_codigo = t2.cliente_codigo
              and t1.caja_codigo = t2.caja_codigo
              and t1.caja_numero = t2.caja_numero
              and t1.cliente_codigo = '115')
where exists(select * from tempos_Cajas t2
              where t1.cliente_codigo = t2.cliente_codigo
              and t1.caja_codigo = t2.caja_codigo
              and t1.caja_numero = t2.caja_numero)

命令运行了几个小时,我无法更新该字段。

我不是 Oracle 专家,但我想知道是否有任何方法可以优化 SQL 语句?

【问题讨论】:

您确定更新时不涉及锁定吗? 还有where exists应该换成IN,会更快 您的查询没有“table a”或“table b”。 终于摆脱了 t1.anio = inline sql 你可以开始思考 UPDATE ( subquery-with-a-join ) SET cola=colb 我很困惑。要更新,您从 t2 中选择 anio,从相关子查询中您要求两个表中的 cliente_codigo 相同并且 cliente_codigo 是特定值115?那么也许 where 子句是问题所在 - 你没有那个条件(在特定值上),所以当三列值匹配时,但 cliente_codigo 不是 115,那么 anio 将是更新至NULL!这是期望的结果吗?我错过了什么? 【参考方案1】:

对于您的查询,您需要tempos_Cajas(cliente_codigo, caja_codigo, caja_numero, anio) 上的复合索引。

也就是说,我认为你想要的逻辑是:

update cajas t1
    set t1.anio   = (select anio
                     from tempos_Cajas t2
                     where t1.cliente_codigo = t2.cliente_codigo and
                           t1.caja_codigo = t2.caja_codigo and
                           t1.caja_numero = t2.caja_numero
                    )
    where exists (select 1
                  from tempos_Cajas t2
                  where t1.cliente_codigo = t2.cliente_codigo and
                        t1.caja_codigo = t2.caja_codigo and
                        t1.caja_numero = t2.caja_numero
                 ) and
          t1.cliente_codigo = '115';

除了上述索引之外,您还需要cajas(cliente_codigo) 上的索引。如果cliente_codigo 的类型是数字,则去掉常量上的单引号。

【讨论】:

复合索引为(cliente_codigo, caja_codigo, caja_numero);数据类型为:cliente_codigo(varchar)、caja_codigo(varchar)、caja_numero(numeric)。我已经尝试过你写的逻辑。结果是一样的。【参考方案2】:

在过去,建议将IN 用于驱动表 - t1 - 很大而子查询中的表 - t2 - 很小的情况。 WHERE EXISTS 被认为更适合该比率翻转的情况。您的数字(t1 = 10,000,000 和 t2 = 20,000)符合第一种情况。

但是,Oracle 优化器多年来变得更加聪明。鉴于您发布的情况 - t1(cliente_codigo, caja_codigo, caja_numero) 上的复合索引,t2 上没有索引 - 此更新产生与您的 where exists 版本相同的解释计划(在 11gR2 和 12cR2 上):

update cajas t1
set t1.anio   = (select anio from tempos_Cajas t2
              where t1.cliente_codigo = t2.cliente_codigo
              and t1.caja_codigo = t2.caja_codigo
              and t1.caja_numero = t2.caja_numero)
where (t1.cliente_codigo, t1.caja_codigo, t1.caja_numero) in  
       (select t2.cliente_codigo, t2.caja_codigo, t2.caja_numero 
        from tempos_Cajas t2)
;

计划是这样的:

SQL> 
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1411510459

--------------------------------------------------------------------------------------------
| Id  | Operation            | Name        | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT     |             |    30M|  1230M|       |   900M  (4)|999:59:59 |
|   1 |  UPDATE              | T1          |       |       |       |            |          |
|   2 |   MERGE JOIN SEMI    |             |    30M|  1230M|       |   136   (2)| 00:00:02 |
|   3 |    INDEX FULL SCAN   | T1_COMP_IDX |    30M|   801M|       |     0   (0)| 00:00:01 |
|*  4 |    SORT UNIQUE       |             | 20000 |   292K|  1112K|   136   (2)| 00:00:02 |
|   5 |     TABLE ACCESS FULL| T2          | 20000 |   292K|       |    29   (0)| 00:00:01 |
|*  6 |   TABLE ACCESS FULL  | T2          |     1 |    28 |       |    29   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("T1"."CLIENTE_CODIGO"="T2"."CLIENTE_CODIGO" AND
              "T1"."CAJA_CODIGO"="T2"."CAJA_CODIGO" AND "T1"."CAJA_NUMERO"="T2"."CAJA_NUMERO")
       filter("T1"."CAJA_NUMERO"="T2"."CAJA_NUMERO" AND
              "T1"."CAJA_CODIGO"="T2"."CAJA_CODIGO" AND
              "T1"."CLIENTE_CODIGO"="T2"."CLIENTE_CODIGO")
   6 - filter("T2"."CLIENTE_CODIGO"=:B1 AND "T2"."CAJA_CODIGO"=:B2 AND
              "T2"."CAJA_NUMERO"=:B3)

24 rows selected.

SQL> 

这是一个相当灾难性的计划,因为它会影响驾驶台上的每一行。更好的解决方案是改用 MERGE。

merge into t1
    using ( select * from t2 ) t2
    on (t1.cliente_codigo = t2.cliente_codigo
        and   t1.caja_codigo = t2.caja_codigo
        and   t1.caja_numero = t2.caja_numero)  
when matched then
    update set t1.anio = t2.anio ;

这是一个更好的计划:

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 525352362

----------------------------------------------------------------------------------------------
| Id  | Operation                      | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | MERGE STATEMENT                |             | 20000 |   507K|    29   (0)| 00:00:01 |
|   1 |  MERGE                         | T1          |       |       |            |          |
|   2 |   VIEW                         |             |       |       |            |          |
|   3 |    NESTED LOOPS                |             |       |       |            |          |
|   4 |     NESTED LOOPS               |             | 20000 |  1582K|    29   (0)| 00:00:01 |
|   5 |      TABLE ACCESS FULL         | T2          | 20000 |   546K|    29   (0)| 00:00:01 |
|*  6 |      INDEX RANGE SCAN          | T1_COMP_IDX |    30M|       |     0   (0)| 00:00:01 |
|   7 |     TABLE ACCESS BY INDEX ROWID| T1          |     1 |    53 |     0   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   6 - access("T1"."CLIENTE_CODIGO"="T2"."CLIENTE_CODIGO" AND
              "T1"."CAJA_CODIGO"="T2"."CAJA_CODIGO" AND "T1"."CAJA_NUMERO"="T2"."CAJA_NUMERO")

请记住,解释计划是指示性的,对于使用带有伪造统计数据的玩具表的计划来说,这会加倍,因此请在真实数据结构上对不同方法进行基准测试。

【讨论】:

以上是关于如何使用来自另一个表列的值优化列的更新?的主要内容,如果未能解决你的问题,请参考以下文章

添加行时如何获取表列的值

使用另一个列值更新 sql db 表列

如何更改 mysql 表列的默认值?

如何使用sql语句使一个列的值变成另一个字段的值

mysql如何根据一列值更新另一列的值?

如何优化查询以使用oracle中另一个表中的列更新表列