我现在感到愚蠢。
我必须在我无法直接访问的数据库上更新100.000行。该表的总行数大约为500.000行。 更新只是为字段添加一个字符,以防它的长度为<所以基本上:
UPDATE X SET VALUE ='0'|| VALUE WHERE LENGTH(VALUE)< 3
所以我将此更新发送给DBA,他们将它返回给我说该语句的性能成本太高(因为完整访问表和100k提交) 而且我应该写一个过程。然后他们为我提供了一个代码示例,以防我不知道如何制作一个代码示例。
我说WTF,一个进程如何比单个更新语句运行得更快?在做一些测试之后,我的更新需要30秒才能运行, 按照代码示例,该过程需要10分钟。
因此,在所有这些挫折之后,真正的问题是:在where子句中使用这样的函数时,有没有办法避免完整的访问表? (该列已编入索引)
答案 0 :(得分:9)
您的陈述已经过优化。它基于集合并以最有效的方式查询表(全表扫描)。您将无法编写以较少资源/时间执行相同工作的程序。您 CAN 编写一个性能不佳的程序,在发生错误时不可重启(即:每100行提交一次)并且将垄断更多资源。
- 如果可能,您应该在单个SQL语句中执行此操作。
- 如果您无法在单个SQL语句中执行此操作,请在PL / SQL中执行此操作。
- 如果您无法在PL / SQL中执行此操作,请尝试使用Java存储过程。
- 如果您不能在Java中执行此操作,请在C外部过程中执行此操作。
- 如果您不能在C外部例行程序中执行此操作,您可能需要认真对待 想想为什么你需要这样做
答案 1 :(得分:4)
按索引从500k(即20%)中访问100k行几乎肯定需要更多逻辑IO然后全扫描。
我相信你的陈述没问题。
答案 2 :(得分:2)
另一方面,它可能更强大:
更新X. SET VALUE = LPAD(VALUE,3,'0') 在哪里(价值)< 3
......以防万一。
答案 3 :(得分:1)
在一个语句中不执行此操作的唯一原因是当您必须更新这么多行时,您的回滚段会变得太小。
在这种情况下(仅限!),牺牲一些速度,你可以在PL / SQL中这样做:
DECLARE mylimit 10000; /* Arbitrary limit */
BEGIN
LOOP
UPDATE X SET VALUE = '0'||VALUE WHERE LENGTH(VALUE) < 3 and ROWNUM<=mylimit;
EXIT WHEN SQL%ROWCOUNT<mylimit;
END LOOP;
END;
但这也不能很好地工作,因为长度(VALUE)= 1的行将更新两次,直到它们不再满足WHERE条件。可悲的是,这不容易避免......
答案 4 :(得分:0)
看起来你唯一的选择是以块的形式执行UPDATE。例如,如果您在语句中放置LIMIT 1000
,则性能不会显着降低(我假设此查询必须在实时数据库上执行)。
你说你自己没有直接访问;如果这些人能够运行Bash脚本,你可以根据需要多次使用LIMIT循环语句,在循环中放置sleep #
。也许这将是一个可行的解决方法。
正如其他人指出的那样 - 是的,单个UPDATE已经是最快的方法。但似乎他的问题是,即使这需要太长时间,所以我建议大块地做这个块。
分块运行将运行更长时间,直到它完成,但不应占用数据库并使其无法访问其他用户(如果您选择一个好的间隔,那就是)。问题是写入数据库,而不是搜索(即使用WHERE LENGTH(name) < 3
)。因此,虽然这种方法会增加数据库的总应变,但它会随着时间的推移而扩散,因此不会阻塞数据库。你可以,例如以100块的速度运行并在每个块之后暂停两秒钟。让这个过夜,没有人会注意到。
答案 5 :(得分:0)
如果在一个事务中更新整个表不是一个选项(例如由于回滚空间问题),另一种方法是将表分解为块(例如,在PK值的范围内)并一次更新它们一个块
尝试选择一种“分块”方法,该方法往往具有相同块内的所有行 - 例如如果行通常按照ID的顺序大致插入,那么这将是一个很好的候选者 - 这样更新将倾向于一次性更新块中的所有行。
如果您的谓词涵盖了表中的大多数行,我希望每次更新都能进行全表扫描,这应该没问题。 (您甚至可以通过查询v $ session_longops来监控其进度。)
答案 6 :(得分:0)
基于函数的索引可以帮助加速udpates。
create index x_idx1 on x(length(value));
这是一个例子。
sqlplus>create table t
2 ( id NUMBER(9) PRIMARY KEY,
3 name VARCHAR2(100)
4 );
Table created.
sqlplus>insert into t select object_id, object_name from user_objects;
2188 rows created.
sqlplus>exec dbms_stats.gather_table_stats(ownname=>'test',tabname =>'t');
PL/SQL procedure successfully completed.
sqlplus>create index t_idx3 on t(length(name));
Index created.
sqlplus>explain plan for update t set name = name || '1' where length(name) < 25;
Explained.
sqlplus>select * from table(dbms_xplan.display);
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 109 | 2616 | 4 (0)| 00:00:01 |
| 1 | UPDATE | T | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| T | 109 | 2616 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | T_IDX3 | 20 | | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access(LENGTH("NAME")<25)
15 rows selected.