我正在使用IO绑定系统(这不会改变)。所以我正在重写一些sql只在需要的时候进行更新,而且它的表现非常好。我发现性能提升了70%。唯一的问题是sql更臃肿,这不是世界末日,只需要维护更多的代码。
所以我的问题是..是否有更简单的方法让Oracle只在需要与添加where子句进行比较时进行更新:
update table_name
set field_one = 'one'
where field_one != 'one';
注意:实际代码要复杂得多,因此像这样添加“where”有时会使查询的长度加倍。
使用11g
答案 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。结果如下:
重新测试测试的步骤:
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;更新您的系统可能会停止按照设计的方式工作......
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');
。
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)功能,使用调用性能更新的策略以这种方式工作的功能:
示例流程:
您的应用程序执行更新:
UPDATE <table> t
SET t.<col1> = 5,
<col2> = :named_bind,
<col3> = (SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)
系统激活VPD策略并调用刚才描述的策略 执行更新前的功能
在动态性能视图上执行查询后,此函数获取当前正在执行的语句
然后解析语句将返回3对(column_name,column_value):
AA
("t.<col1>", "5"),
("<col2>", ":named_bind"),
("<col3>", "(SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)")
AA
t.<col1> != 5
AND <col2> != :named_bind
AND <col3> != (SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)
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)
我会在更新之前使用临时表。找到需要更新的记录,然后使用临时表更新这些记录,并使用您想要的值。