缓慢更新声明

Posted

技术标签:

【中文标题】缓慢更新声明【英文标题】:Slow Update Statement 【发布时间】:2008-11-17 16:36:51 【问题描述】:

我们正在处理一个 Oracle 项目中的一个非常缓慢的更新语句。

这里有一个小脚本来重现这个问题:

drop table j_test;

CREATE TABLE J_TEST
(
  ID  NUMBER(10) PRIMARY KEY,
  C1   VARCHAR2(50 BYTE),
  C2   VARCHAR2(250 BYTE),
  C3   NUMBER(5),
  C4   NUMBER(10)
);

-- just insert a bunch of rows
insert into j_test (id)
select rownum 
from <dummy_table>
where rownum < 100000;

-- this is the statement that runs forever (longer than my patience allows)
update j_test
set C3 = 1,
    C1 = 'NEU';    

在某些环境中,更新语句只需要大约 20 秒,而在某些环境中,语句会运行几分钟。当使用更多行时,问题会变得更糟。

我们不知道是什么导致了这种行为,并希望在提出解决方案之前了解正在发生的事情。

有什么想法和建议吗? 谢谢 托尔斯滕

【问题讨论】:

"C4 NUMBER(10)" 与 "SET C4 = 'NEU'" 相比让我摸不着头脑。这是有意还是我错过了一个令人兴奋的 Oracle 功能? 请考虑不要更新每一行。通过 CTAS(将表创建为选择)执行此操作 - 见下文。 对不起,希望我解决了这个问题。 【参考方案1】:

性能不佳的一个可能原因是行链接。您的所有行最初都有列 C3 和 C4 为空,然后您将它们全部更新为具有值。新数据不适合现有块,因此 Oracle 必须将行链接到新块。

如果您提前知道您将这样做,您可以像这样预先分配足够的可用空间:

CREATE TABLE J_TEST
(
  ID  NUMBER(10) PRIMARY KEY,
  C1   VARCHAR2(50 BYTE),
  C2   VARCHAR2(250 BYTE),
  C3   NUMBER(5),
  C4   NUMBER(10)
) PCTFREE 40;

... 其中 PCTFREE 指定为更新保留可用空间的百分比。默认值为 10,这对于此示例来说是不够的,其中行的大小或多或少加倍(根据我的数据库,平均长度为 8 到 16 个字节)。

这个测试显示了它的不同之处:

SQL> CREATE TABLE J_TEST
  2  (
  3    ID  NUMBER(10) PRIMARY KEY,
  4    C1   VARCHAR2(50 BYTE),
  5    C2   VARCHAR2(250 BYTE),
  6    C3   NUMBER(5),
  7    C4   NUMBER(10)
  8  );

Table created.

SQL> insert into j_test (id)
  2  select rownum 
  3  from transactions
  4  where rownum < 100000;

99999 rows created.

SQL> update j_test
  2  set C3 = 1,
  3      C2 = 'NEU'
  4  /

99999 rows updated.

Elapsed: 00:01:41.60

SQL> analyze table j_test compute statistics;

Table analyzed.

SQL> select blocks, chain_cnt from user_tables where table_name='J_TEST';

    BLOCKS  CHAIN_CNT
---------- ----------
       694      82034

SQL> drop table j_test;

Table dropped.

SQL> CREATE TABLE J_TEST
  2  (
  3    ID  NUMBER(10) PRIMARY KEY,
  4    C1   VARCHAR2(50 BYTE),
  5    C2   VARCHAR2(250 BYTE),
  6    C3   NUMBER(5),
  7    C4   NUMBER(10)
  8  ) PCTFREE 40;

Table created.

SQL> insert into j_test (id)
  2  select rownum 
  3  from transactions
  4  where rownum < 100000;

99999 rows created.

SQL> update j_test
  2  set C3 = 1,
  3      C2 = 'NEU'
  4  /

99999 rows updated.

Elapsed: 00:00:27.74

SQL> analyze table j_test compute statistics;

Table analyzed.

SQL> select blocks, chain_cnt from user_tables where table_name='J_TEST';

    BLOCKS  CHAIN_CNT
---------- ----------
       232          0

如您所见,使用 PCTFREE 40 更新需要 27 秒而不是 81 秒,生成的表使用 232 个没有链接行的块,而不是 694 个有 82034 个链接行的块!

【讨论】:

很好的演示,但建议更新 100% 的行只会引发坏主意。 马克,我宁愿假设这是一个人为的例子,真正的更新是出于某种商业原因稍后才会出现。显然,如果行需要立即具有这些值,那么它们应该与它们一起插入!【参考方案2】:

试试这个:

insert into j_test (id, C3, C4)
select rownum, 1, 'NEU'
from <dummy_table>
where rownum < 100000;

【讨论】:

【参考方案3】:

您是否真的要使用“NEU”字符值更新数字字段 C4 NUMBER(10)?

假设您正在尝试执行以下操作:

UPDATE j_test
   SET c3 = 3
 WHERE c1 = 'NEU'

您可能需要在搜索字段上创建索引并分析表以加快更新过程。 如果您真的尝试更新整个表,那么更新速度可能会有所不同。这取决于内存、磁盘访问速度、重做日志创建等。

另外,正如在另一个答案中提到的,您需要为使用 PCTFREE 的更新保留一些空间,否则您将在表中获得很多影响更新速度的链接行。

【讨论】:

【参考方案4】:

您确定问题不在于您将“NEU”插入到 Number(10) 字段中吗?在插入之前,它正在执行从“NEU”到数字 (??) 的即时转换。

我的意思是说真的,其他答案都是很好且有用的信息,但是完整更新的 100k 行应该很快。

请记住 - 索引往往会加快选择速度,并减慢插入/更新速度。

【讨论】:

【参考方案5】:

这个问题和我的回答here非常相似。

永远不要更新表中 100% 的行。只需按照该链接中的程序进行操作即可。将“正确答案”构建为新表,然后将新表换成旧表。与删除大部分行相同。使用我概述的场景会更有效。

编辑:如果这对你们中的某些人来说是个坏主意,请知道这是 Tom Kyte 推荐的技术。

【讨论】:

【参考方案6】:

另一种可能是一个 UPDATE 正在等待,因为表被锁定(例如,表上有另一个未提交的 UPDATE)This link 有一条 SQL 语句来显示锁

【讨论】:

以上是关于缓慢更新声明的主要内容,如果未能解决你的问题,请参考以下文章

添加新声明时更新记录的身份声明

旧访问更新声明

更新架构声明后,Mongoose 不会更新新字段

更新架构声明后,Mongoose 不会更新新字段

jwt refresh 不会更新自定义声明

更新 ASP.NET One Core 中的声明值