Sql 避免重复更新
我在一个IO绑定的系统中工作(这不会改变)。因此,我正在重写一些sql,以便只在需要时进行更新,而且进展非常顺利。我看到性能大约提高了70%。唯一的问题是sql更加臃肿,这并不是世界末日,只是需要维护更多的代码 所以我的问题是。。与添加where子句相比,是否有更简单的方法让Oracle仅在需要时更新:Sql 避免重复更新,sql,oracle,plsql,Sql,Oracle,Plsql,我在一个IO绑定的系统中工作(这不会改变)。因此,我正在重写一些sql,以便只在需要时进行更新,而且进展非常顺利。我看到性能大约提高了70%。唯一的问题是sql更加臃肿,这并不是世界末日,只是需要维护更多的代码 所以我的问题是。。与添加where子句相比,是否有更简单的方法让Oracle仅在需要时更新: update table_name set field_one = 'one' where field_one != 'one'; 注意:实际代码要复杂得多,因此添加这样的“where
update table_name
set field_one = 'one'
where field_one != 'one';
注意:实际代码要复杂得多,因此添加这样的“where”有时会使查询的长度增加一倍
使用11g,我想没有更简单的方法……尝试使用merge语句。它可以减少更新查询的运行时间 上面的查询可以这样重写
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';
在表上创建视图并编写自定义 编辑,测试结果 我已经在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;
/
声明
l_开始编号;
l_端号;
l_diff数;
行更新int;
开始
rows2update:=100000;
l_start:=dbms_实用程序.get_时间;
影响路径:=0;
对于1..rows2update循环中的i
rows2update:=rows2update-1;
更新视图\u名称--此行为有一个简单的解释:
任何更新,即使具有相同的值,也必须触发触发器(外部和内部)
因此,如果没有这种“空闲”更新,您的系统可能会停止按照设计的方式工作
进一步资料:
PS.“非修饰视图+替代触发器”解决了访问问题,但替代触发器始终是行级触发器,因此这种方法可能会破坏性能。我知道您正在寻找一个想法而不是代码,所以这是我的
您可以使用组合更新查询。让我们来看下一个查询:
UPDATE table_name SET field_one = 'one', set field_two = 'where,=' WHERE id = 1
您应该调用管理查询的过程,而不是调用update语句。此过程应拆分SET
子句,查找第一个SET
,直到WHERE
为止,或者如果语句中没有WHERE,则不查找任何内容。由于作业中可能包含WHERE
field_one = 'one', set field_two = 'where,'
然后用和替换每个,
,用替换每个=
=代码>。处理好作业
field_one <> 'one' AND set field_two <> 'where,'
通过这种方式,您可以无需修改就可以创建当前查询,只需稍微换行调用update_wrapper(“您的查询”)代码>
PD:根据您的评论,我假设您不使用NULL
值。查看您的声明:
update table_name
set field_one = 'one'
where field_one != 'one';
问题是“field_one!='one'”谓词。使用任何传统的索引方案,这种类型的不相等谓词都会导致全表扫描,这(很可能)会导致大量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;
现在,更新应该进行一次完整的索引扫描,但这将比FTS少很多I/O,特别是当需要更新的行数相对较少时。这个想法是FBI将只拥有需要更新的行。只要行数明显少于表中的总行数,这应该是一种有效的索引策略。在更新之前,我会使用临时表。找到需要更新的记录,然后使用temp表用所需的值更新这些记录。我是否正确理解这里的问题是重复查询中的值“一”
如果是,则可以使用以下选项:
update (select field_one, 'one' new_field_one from table_name)
set field_one = new_field_one
where field_one != new_field_one;
鉴于SQL的工作原理,这正是您需要做的。如果你告诉它:
update table_name
set field_one = 'one';
这意味着在SQL中与
update table_name
set field_one = 'one'
where field_one != 'one';
在第一种情况下,数据库只能处理您告诉它要处理的内容,因为没有where子句,您已经告诉它要处理所有记录
在第二种情况下,您在其上放置了一个过滤器,以便只处理一些特定的记录
查询的内容由代码编写者决定,而不是由数据库决定。如果你不想让每一条记录都更新,你就不应该让它这样做。数据库对于您给它的命令非常严格。是的,第二组查询更长,因为它们更具体。它们的含义与最初的查询不同。这一切都是好的,因为更新您感兴趣的十条记录要比表中所有1000000条记录快得多
您需要克服这样的想法:在数据库查询中,更长的时间不知何故是一件坏事。通常这是一件好事,因为你的要求更加正确。您最初的查询完全不正确。现在,你必须付出代价来纠正这一系统性的错误做法。实现你的目标不应该有一个简单的方法。但是,如果您有Enterprise Edition,您将有一种可能做到这一点……无论如何,这是非常困难的:通过使用调用以下函数的策略在具有性能更新的表上实现虚拟专用数据库(VPD)功能:
通过加入一些dynam来检测实际执行更新的SQL
update table_name
set field_one = 'one';
update table_name
set field_one = 'one'
where field_one != 'one';
UPDATE <table> t
SET t.<col1> = 5,
<col2> = :named_bind,
<col3> = (SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)
("t.<col1>", "5"),
("<col2>", ":named_bind"),
("<col3>", "(SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)")
t.<col1> != 5
AND <col2> != :named_bind
AND <col3> != (SELECT o.<col5> FROM <oth_table> o WHERE t.<col4> = o.<col6>)
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>)
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