删除NOTNULL约束会导致全表扫描

时间:2018-01-15 11:41:13

标签: oracle constraints

我有一个包含大约2500万条记录的表,并且在几个字段上有一些NOTNULL约束。

当我删除其中一个NOTNULL限制时,执行全表扫描(这需要花费很多时间)。我可以在TOAD的第二个实例的会话浏览器中看到(我使用TOAD来删除约束)。

当约束被删除时,有没有办法避免这种全表扫描?

1 个答案:

答案 0 :(得分:3)

这表明导致全表扫描的列具有默认值,可能是在数据已存在的情况下将列添加到表中,并且具有非空约束(only possibly since 11gR1)。

作为演示,没有默认值:

create table t42 (id number);

alter table t42 add (some_col number not null);

select data_default, default_length from user_tab_columns where column_name = 'SOME_COL';

DATA_DEFAULT                                                 DEFAULT_LENGTH
------------------------------------------------------------ --------------


insert into t42 (id, some_col)
select level, 0 from dual
connect by level <= 100000;

insert into t42 (id, some_col)
select 100000 + level, 1 from dual
connect by level <= 10000;

select some_col, count(*) from t42 group by some_col;

  SOME_COL   COUNT(*)
---------- ----------
         1      10000
         0     100000

set timing on

alter table t42 modify (some_col null);

Table T42 altered.

Elapsed: 00:00:00.056

但是使用默认值:

create table t42 (id number);
insert into t42 (id)
select level from dual
connect by level <= 100000;

alter table t42 add (some_col number default 0 not null);

select data_default, default_length from user_tab_columns where column_name = 'SOME_COL';

DATA_DEFAULT                                                 DEFAULT_LENGTH
------------------------------------------------------------ --------------
0                                                                         2

insert into t42 (id, some_col)
select 100000 + level, 1 from dual
connect by level <= 10000;

select some_col, count(*) from t42 group by some_col;

  SOME_COL   COUNT(*)
---------- ----------
         1      10000
         0     100000

set timing on

alter table t42 modify (some_col null);

Table T42 altered.

Elapsed: 00:00:04.734

现在alter需要更长的时间,因为它必须实际更新所有预约束行,物理上具有零值。在alter之后,您会看到相同的数据,即使您更改了默认值(在原始alter之前或之后);但如果您在可能有一个小窗口之前执行此操作,则可能会发生约束违规):

alter table t42 modify (some_col default null);

select data_default, default_length from user_tab_columns where column_name = 'SOME_COL';

DATA_DEFAULT                                                 DEFAULT_LENGTH
------------------------------------------------------------ --------------
null                                                                      4

select some_col, count(*) from t42 group by some_col;

  SOME_COL   COUNT(*)
---------- ----------
         1      10000
         0     100000

除了添加没有默认值的新列(这将至少花费很长时间,并且可能导致其他副作用)之外,除此之外没有任何解决方法。

请注意,默认值已从未完全设置更改为显式为null。当您插入一行时,没有任何实际差异 - 列值以任何方式结束为空 - 但you can't completely remove the default一旦设置完毕。

有趣的是,如果更改列默认而不使用删除约束,这对使用它的行的报告值没有影响 - 它们仍将显示为零。 Oracle似乎将该约束默认存储在其他地方,这是有道理的。

在添加默认/非空列之后插入的任何行都将在表中存储实际值,并且更改默认值将影响后续插入 - 但添加约束之前已存在的行的行为就好像它们实际上已更新为添加约束时指定的默认值。

11g中的这一变化主要是为了加快列的添加,并且停止必须单独添加没有约束的列的步骤,然后更新所有现有的行(这是慢位),然后更改表再次添加约束。这种机制让您(几乎)只需更改元数据即可立即执行此操作。但是,如果随后删除约束,那么更新成本仍然会很痛苦。