避免冗余更新

时间:2014-07-28 20:09:30

标签: sql oracle plsql

我正在使用IO绑定系统(这不会改变)。所以我正在重写一些sql只在需要的时候进行更新,而且它的表现非常好。我发现性能提升了70%。唯一的问题是sql更臃肿,这不是世界末日,只需要维护更多的代码。

所以我的问题是..是否有更简单的方法让Oracle只在需要与添加where子句进行比较时进行更新:

update table_name
   set field_one = 'one'
 where field_one != 'one';

注意:实际代码要复杂得多,因此像这样添加“where”有时会使查询的长度加倍。

使用11g

12 个答案:

答案 0 :(得分:6)

鉴于SQL的工作方式,这正是您需要做的。如果你告诉它:

update table_name    
   set field_one = 'one';

这意味着SQL中的内容与

完全不同
update table_name
   set field_one = 'one'
 where field_one != 'one';

数据库只能处理你告诉它处理的内容。在第一种情况下,因为没有where子句,你告诉它处理所有记录。

在第二种情况下,您在其上放置了一个过滤器,仅处理某些特定记录。

由代码 writer 决定查询内容的数据库。如果你不想更新每条记录,你就不应该告诉它这样做。数据库对于您提供的命令非常直观。是的,第二组查询更长,因为它们更具体。它们具有与原始查询不同的含义。这就是好事,因为更新您感兴趣的十条记录要比表中的所有1,000,000条记录快得多。

你需要克服这样的想法:在数据库查询中,更长的时间在某种程度上是一件坏事。通常,这是一件好事,因为你在要求的方面更正确。您的原始查询完全不正确。而现在你已经付出了代价来解决系统性的不良行为。

答案 1 :(得分:4)

我想没有更简单的方法......

答案 2 :(得分:2)

在表格上创建一个视图并编写自定义instead of trigger

--DDL create your sample table:
create table table_name (
   field_one varchar2(100)
  );

--DDL create the view
create view view_name as 
select * from table_name;    

--DDL creating instead trigger
CREATE OR REPLACE TRIGGER trigger_name
   INSTEAD OF UPDATE ON view_name
   FOR EACH ROW
   BEGIN
     UPDATE table_name
     SET field_one = :NEW.field_one
     where :OLD.field_one != :NEW.field_one
     ;
   END trigger_name;

--DML testing:
update view_name set field_one = 'one'

已编辑,测试结果

我已经在200K行场景中测试了我的方法,其中可更新行的因子为1/20。结果如下:

  • 脚本时间直接更新表格:1.559,05秒
  • 脚本时间通过触发器更新:1.101,14秒

重新测试测试的步骤:

Creating table, view and trigger:

create table table_name (
   myPK int primary key,
   field_1 varchar2(100),
   field_2 varchar2(100),
   field_3 varchar2(4000)
  );

create view view_name as 
select * from table_name;

CREATE OR REPLACE TRIGGER trigger_name
   INSTEAD OF UPDATE ON view_name
   FOR EACH ROW
   BEGIN
     UPDATE table_name
     SET 
        field_1 = :NEW.field_1,
        field_2 = :NEW.field_2
     where 
        myPK = :OLD.myPK
        AND not ( :OLD.field_1 = :NEW.field_1 and
                  :OLD.field_2 = :NEW.field_2 )
     ;
   END trigger_name;

为可更新行插入具有1/20因子的虚拟数据:

DECLARE
   x NUMBER := 300000;
BEGIN
   FOR i IN 1..x LOOP
      IF MOD(i,20) = 0 THEN     
         INSERT INTO table_name VALUES (i, 'rare', 'hello', 
                                           dbms_random.string('A', 2000));
      ELSE
         INSERT INTO table_name VALUES (i, 'ordinary', 'bye', 
                                           dbms_random.string('A', 2000) );
      END IF;
   END LOOP;
   COMMIT;
END;

测试性能的脚本:

declare
    l_start number;
    l_end number;
    l_diff number;    
    rows2update int;

begin
   rows2update := 100000;
   l_start := dbms_utility.get_time ;
   affectedRows := 0;
   FOR i IN 1..rows2update LOOP

      rows2update := rows2update - 1;
      update view_name    --<---- replace by table_name to test without trigger
         set field_1 = 'ordinary' 
       where myPK = round( dbms_random.value(1,300000) )    ; 
      commit;

   end loop;

   l_end := dbms_utility.get_time ;
   dbms_output.put_line('l_start ='||l_start);
   dbms_output.put_line('l_end ='||l_end);
   l_diff := (l_end-l_start)/100;
   dbms_output.put_line('Elapsed Time: '|| l_diff ||' secs');

end;
/

免责声明:这是在虚拟环境中进行的简单测试,仅作为第一次方法测试。我敢肯定,结果只会改变字段长度或其他参数。

答案 3 :(得分:1)

尝试使用合并语句。它可能会减少更新查询的运行时间。

上述查询可以像这样重写,

MERGE INTO table_name
USING ( SELECT ROWID from table_name Where field_one != 'one') data_table
ON ( table_name.ROWID = data_table.ROWID)
WHEN MATCHED THEN
UPDATE SET table_name.field_one = 'one';

答案 4 :(得分:1)

我是否正确理解这里的问题是在查询中重复'one'这个值?

如果是,那么您可以使用以下内容:

update (select field_one, 'one' new_field_one from table_name)
   set field_one = new_field_one
 where field_one != new_field_one;

答案 5 :(得分:1)

如果您的表格中的列最后只包含一个值 - 为什么您需要该列?

如果出于某种原因需要该列,则可以删除该列,然后使用该列重新创建 你想要的默认值。

答案 6 :(得分:0)

这种行为有一个简单的解释:

  

任何更新,即使具有相同的值,也必须触发触发器(外部和内部)

所以,没有这个&#34;闲置&#34;更新您的系统可能会停止按照设计的方式工作......

更多信息:http://orainternals.wordpress.com/2010/11/04/does-an-update-statement-modify-the-row-if-the-update-modifies-the-column-to-same-value/

PS。解决方案&#34;非编辑视图+ INSTEAD OF触发器&#34;解决了访问问题,但INSTEAD OF触发器始终是行级触发器,因此这种方法可能会破坏性能。

答案 7 :(得分:0)

我认为你正在寻找一个想法而不是代码,所以这是我的。

您可以使用Dynamic SQL Statements撰写更新查询。我们来看下一个问题:

UPDATE table_name SET field_one = 'one', set field_two = 'where,=' WHERE id = 1

您应该调用管理查询的过程,而不是调用update语句。此过程应拆分SET子句,查找第一个SET直到WHERE,如果您的语句中没有位置,则查找任何内容。您应该小心分配中的内容,因为它可能包含WHERE

field_one = 'one', set field_two = 'where,'

然后将每个,替换为AND,将每个=替换为!=。照顾作业。

field_one <> 'one' AND set field_two <> 'where,'

使用WHERE将其重新附加到AND子句的查询中。也许没有WHERE条款,所以你必须添加它。

UPDATE table_name 
SET field_one = 'one', set field_two = 'where,=' 
WHERE id = 1 AND field_one != 'one' AND set field_two != 'where,'

通过这种方式,您可以在不进行任何修改的情况下查看当前查询,只需一个小问题call update_wraper ('Your query');

PD:我在评论中假设您不使用NULL值。

答案 8 :(得分:0)

看看你的陈述:

update table_name
   set field_one = 'one'
 where field_one != 'one';

问题是&#34; field_one!=&#39; one&#39;&#34;谓语。使用任何传统的索引方案,这种类型的不相等谓词将导致FULL TABLE SCAN,这可能会导致大量I / O,这是您试图避免的,特别是如果表是很大。那么,该怎么办?

如果表很大,并且满足谓词的行数(相对)很小,我们可以用基于函数的索引做一些事情,并且(因为你在11g上)巧妙地隐藏了FBI背后的虚拟专栏。

我正在考虑这样的事情:

create table table_name(field_one varchar2(10),
                        field_one_not_one generated always as (case when field_one = 'one' then null else field_one end));
create index field_one_not_one_indx on table_name(field_one_not_one);

现在,只需:

update table_name
   set field_one = 'one'
 where field_one_not_one is not null;

现在,更新应该执行FULL INDEX SCAN,但是它会比FTS少 更少的I / O,特别是如果需要更新的行数相对较小。我们的想法是FBI只会有需要更新的行。只要该行数明显少于表中的总行数,这应该是一种有效的索引策略。

答案 9 :(得分:0)

实现目标不应该是一种简单的方法。但是,如果您拥有企业版,那么您将有可能做到这一点......无论如何都非常困难:通过DBMS_RLS在表上实施虚拟专用数据库(VPD)功能,使用调用性能更新的策略以这种方式工作的功能:

  1. 通过加入一些动态效果视图(V$VPD_POLICYV$SQLAREA来检测实际执行更新的SQL,如果它们也不够V$OPENCURSOR或{{3 }})用于当前的USER或当前的SESSION_ID。
  2. 解析UPDATE语句以检测SET中使用的列 子句及其赋值。您可以使用像V$SQL_MONITOR这样的解析器生成器或类似的工具来构建一个java解析器,一旦您的测试工作正常,您可以用PL / SQL过程包裹antlr
  3. 返回具有连接的附加where条件 old_column_value!=刚刚解析步骤检测到的new_column_value。
  4. 示例流程:

    1. 您的应用程序执行更新:

      UPDATE <table> t
          SET t.<col1> = 5,
              <col2> = :named_bind,
              <col3> = (SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)
      
    2. 系统激活VPD策略并调用刚才描述的策略 执行更新前的功能

    3. 在动态性能视图上执行查询后,此函数获取当前正在执行的语句

    4. 然后解析语句将返回3对(column_name,column_value):

    5. AA

      ("t.<col1>", "5"),
      ("<col2>", ":named_bind"),
      ("<col3>", "(SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)")
      
      1. (5)该函数返回附加的where子句:
      2. AA

        t.<col1> != 5 
        AND <col2> != :named_bind
        AND <col3> != (SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)
        
        1. (6)系统会将语句转换为:
        2. AA

          UPDATE <table> t
              SET t.<col1> = 5,
                  <col2> = :named_bind,
                  <col3> = (SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)
          WHERE t.<col1> != 5 
              AND <col2> != :named_bind
              AND <col3> != (SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)
          

          我还没有测试过这方面的任何部分,要做的工作很长,但是在我看来它应该可以正常工作。最后一件事:这个方法不能使用位置绑定变量来处理语句,命名的方法可能没问题,但我并不十分肯定它。这也可能是一项非常紧张的工作,并且为了处理频繁和(已经)快速更新,应该避免这样做,因此您还需要找到一种方法来排除那些要处理的快速语句。

答案 10 :(得分:0)

这个问题让我想起了一个旧的AskTom对话How to Update millions or records in a table

简而言之,它建议使用以下解决方案来提高表格上大量更新的性能:

  
      
  • CREATE TABLE new_table AS SELECT(执行更新&#34;此处&#34;)FROM old_table
  •   
  • 新表上的INDEX new_table授权
  •   
  • 在new_table等上添加约束
  •   
  • DROP TABLE old_table
  •   
  • RENAME new_table TO old_table;
  •   
     

你可以使用并行查询,使用&#34; nologging&#34;在大多数操作产生非常   小重做,根本没有撤消 - 只需要更新一小部分时间   数据。

但是,我会保持简单:

创建一个临时表,其中包含您要更新的行的键和值;实际上需要过滤行,你可以告诉他们没有更新,因为新值与旧值相同

CREATE GLOBAL TEMPORARY TABLE temp_table_name ON COMMIT DELETE ROWS
AS
SELECT id, 'one' as field_one FROM table_name WHERE field_one != 'one'

从临时表中更新主表

MERGE INTO table_name b
USING 
(
  SELECT id,
         field_one
  FROM temp_table_name
) a ON (a.id = b.id)
WHEN MATCHED THEN UPDATE 
    SET b.field_one = a.field_one

或者

UPDATE a
 SET a.field_one = b.field_one
FROM temp_table_name a
INNER JOIN table_name b
 ON a.ID = b.ID

另见:

答案 11 :(得分:-1)

我会在更新之前使用临时表。找到需要更新的记录,然后使用临时表更新这些记录,并使用您想要的值。