【IT168 Oracle】11g中在线处理功能得到了很大增强,其中包括在线修改表结构,在线创建或重建索引,建立不可见索引,表增加非空字段,在线DDL以及对象依赖性细化等。
在11g以前,DDL操作都要获取对象的排他锁,如果当时无法获取,则会马上报错:
SQL> conn yangtk/yangtk@ytk102已连接。
SQL> create table t (id number);
表已创建。
SQL> insert into t values (1);
已创建 1 行。
在当前会话不提交,保持对t表的锁。然后在另外的session登陆,尝试对t表进行ddl操作:
这就是11g以前的DDL处理方式,在11g中,用户可以为DDL设置一个等待时间,这样DDL在获取不到排他锁时,会根据设置等待相应的时间,如果到时仍然获取不到锁,那么才会报错。
登陆11g进行同样的处理:
SQL> conn yangtk/yangtk@ora11g已连接。
SQL> create table t (id number);
表已创建。
SQL> insert into t values (1);
已创建 1 行。
登陆第二个会话,发出DDL操作:
SQL2> conn yangtk/yangtk@ora11g已连接。
SQL2> set timing on
SQL2> truncate table t;
truncate table t
*第 1 行出现错误:
ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源, 或者超时失效
已用时间: 00: 00: 00.07
DDL仍然马上报错,这是由于控制DDL等待时间的初始化参数DDL_LOCK_TIMEOUT默认值是0,下面修改这个默认值:
SQL2> alter session set ddl_lock_timeout = 5;
会话已更改。
已用时间: 00: 00: 00.06
SQL2> truncate table t;
truncate table t
*第 1 行出现错误:
ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源, 或者超时失效
已用时间: 00: 00: 05.07
如果在等待时间内第一个会话进行提交,那么DDL就可以获取到排他锁,从而开始操作。
SQL2> alter session set ddl_lock_timeout = 60;
会话已更改。
已用时间: 00: 00: 00.04
SQL2> truncate table t;
返回会话1进行提交:
SQL> commit;
提交完成。
会话2在会话1提交后,得到T表的锁,开始TRUNCATE操作:
表被截断。
已用时间: 00: 00: 13.03
SQL2> select * from v$version;
BANNER
--------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production PL/SQL Release 11.1.0.6.0 - Production
CORE 11.1.0.6.0 Production
TNS for Linux: Version 11.1.0.6.0 - Production
NLSRTL Version 11.1.0.6.0 - Production
已用时间: 00: 00: 00.21
注意,将DDL_LOCK_TIMEOUT参数设置过大也可能造成潜在的问题,虽然DDL还没有获取到排他锁,不能进行操作,但是它已经在T上增加了锁信息,这会导致后续的DML都会被锁住,直到DDL等待超时或锁住DDL的会话提交或回滚使得DDL顺利完成。
这对于繁忙的系统可能造成很严重的问题,设置之前需要谨慎考虑。
在11g以前,表中新增一个NOT NULL的字段是十分痛苦的事情,尤其是表很大的情况。不光是执行速度慢,而且由于现有数据长度的变化,很容易造成表中大量的行链接情况。
在11g中,这种情况得到了彻底的改善,Oracle通过在数据字典中记录DEFAULT值,避免了繁重的更新操作,增加非空字段的时间和增加一个可空字段的时间完全一样。
先来看看10g中添加一个包含DEFAULT值的非空字段的情况:
在10g中,5万条记录添加一个CHAR(1000)的新增NOT NULL列,需要17秒,而且更新后表的大小为72M。
下面看看11g中的表现:
将近7万的数据,添加字段仅用了0.15秒。性能完全没有可比性。从更新后表的大小也可以看到明显的区别。这里只看了执行时间和占用空间两个方面,从生成REDO和UNDO的大小看,前者同样远远超过了后者。
Oracle采用了何种方法实现了如此的性能提升,说起来也很简单。Oracle11g中,在添加一个包含DEFAULT值的NOT NULL字段,Oracle不会去更新现有的数据,Oracle需要做的不过是将默认值以及对应的表信息、列信息一起存储在一个新增数据字典表ecol$中。
这张表利用BLOB字段存储ALTER TABLE添加的DEFAULT值:
SQL> SELECT OBJECT_ID FROM DBA_OBJECTS WHERE OBJECT_NAME = 'T';
OBJECT_ID
----------
70482
SQL> SELECT * FROM SYS.ECOL$ WHERE TABOBJ# = 70482;
TABOBJ# COLNUM BINARYDEFVAL
---------- ---------- ----------------------------------------
70482 16 4C4152474520434F4C554D4E
简单转换一下:
SQL> SET SERVEROUT ON
SQL> DECLARE
2 V_BLOB VARCHAR2(32767) DEFAULT '4C4152474520434F4C554D4E';
3 BEGIN
4 FOR I IN 1..LENGTH(V_BLOB)/2 LOOP
5 DBMS_OUTPUT.PUT(CHR(TO_NUMBER(SUBSTR(V_BLOB, (I - 1) * 2 + 1, 2), 'XXX')));
6 END LOOP;
7 DBMS_OUTPUT.NEW_LINE;
8 END;
9 /
LARGE COLUMN
PL/SQL procedure successfully completed.
Oracle在读取数据时,发现COLUMN 16列为非空,但是在存储的数据中找不到该列,于是就会从ECOL$中读取该列的默认值。
这个功能不但提高了添加非空字段的速度,而且节省了大量的磁盘空间。相比之下,在数据字典中查找DEFAULT VALUE的代价小的几乎可以忽略。
Oracle使用这种方法强制存储过程在下次执行之前进行重编译,避免表结构改变后,存储过程没有更新,从而得到错误的结果。
这种方法没有问题,但是有的时候修改往往和关联的存储过程没有关系,但是Oracle并不对这种情况进行判断,这样会造成很多不需要重新编译的存储过程也被置于INVALID状态:
上面这个简单的例子就说明了这种情况,对于存储过程P_TEST来说,只是访问了T的ID列,T表新增NAME列,并不会影响存储过程P_TEST。但是在10g及以前版本,Oracle的判断依赖性达不到字段级。
而从11g开始,Oracle细化了依赖性的判断,下面看看在11g中同样的例子:
Oracle11g新增了INVISIBLE INDEX功能,可以新增一个不可见的索引,或者将目前的索引变为不可见。除非指定了参数,否则优化器不会使用这种状态的索引。
如果希望优化器考虑不可见索引,可以在系统级或会话级设置初始化参数:OPTIMIZER_USE_INVISIBLE_INDEXES设置为TRUE:
可以通过USER_INDEXES的新增字段VISIBILITY字段来查看索引的可见性:
SQL> SET AUTOT OFF
SQL> CREATE INDEX IND_T_NAME ON T (OBJECT_NAME);
索引已创建。
SQL> SELECT INDEX_NAME, VISIBILITY FROM USER_INDEXES WHERE TABLE_NAME = 'T';
INDEX_NAME VISIBILIT
------------------------------ ---------
IND_T_NAME VISIBLE
IND_T_OWNER INVISIBLE
利用不可见索引可以在不影响其他会话的情况下查看索引建立后的效果。在删除索引之前可以将索引先至于不可见状态,这样一旦发现索引不应该被删除,索引的恢复将会十分迅速。
在11g以前,Oracle提供的只读功能只到了数据库级和表空间级:
如果数据库级设置了只读打开,数据库中任何对象都无法进行修改。
设置表空间只读,那么表空间中所有的对象都无法被修改。也就是说,如果在11g之前,想要实现对某张表的只读,只有建立一个表空间,将表放到该表空间中,再将表空间至于只读状态。
不但操作麻烦,而且如果需要添加新的只读表,而不打算新建立一个表空间的话,需要先将只读表空间置为可写,然后将新的表放进去,再将其置为只读,而在操作期间,表空间中原有的对象可能会被修改。
从11g开始,Oracle终于提供了表级的只读设置了,显现方法也很简单,一个ALTER TABLE READ ONLY语句就搞定了:
将表置于只读状态,并非只是禁止DML语句,包含FOR UPDATE的查询语句,已经会引起表中数据发生变化的DDL语句都会被禁止。而不影响表中数据的DDL则可以正常执行:
在11g中,Oracle会利用现有的物化视图日志来完成在线重定义的功能。同时,物化视图日志也可以作为表的从属信息同步到目标上。不过在同步完成后,需要物化视图执行完全刷新。
最后简单给出11g中包含物化视图日志的在线重定义操作步骤:
11g在线重定义对物化视图日志进行了支持,同时COPY_TABLE_DEPENDENTS过程也添加了对物化视图日志拷贝的功能。
SQL> CREATE TABLE T AS SELECT ROWNUM ID, A.* FROM DBA_OBJECTS A;
表已创建。
SQL> CREATE INDEX IND_T_NAME ON T(OBJECT_NAME);
索引已创建。
SQL> DELETE T WHERE ID = 1;
已删除 1 行。
会话1中建立表和索引后,删除一条记录,且不提交。
SQL> SET SQLP 'SQL2> '
SQL2> ALTER INDEX IND_T_NAME REBUILD;
ALTER INDEX IND_T_NAME REBUILD
*第 1 行出现错误:
ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源
SQL2> ALTER INDEX IND_T_NAME REBUILD ONLINE;
在会话2上 尝试重建索引。由于无法马上获取锁,直接REBUILD INDEX会报错,而REBUILD INDEX ONLINE则会处于等待状态,直到获取表的锁。
这时在会话1上查询锁信息和会话2的等待情况:
再次查询,发现会话2仍然在等待锁信息,不过等待时间已经重新计算了。观察V$LOCK视图的信息可以发现会话2仍然是被锁住,但是状态已经和第一次不相同了。根据文档的描述,ONLINE REBUILD在开始和完成的时候都会去获取锁,现在应该是等待完成时的锁。
SQL> COMMIT;
提交完成。
SQL> DELETE T WHERE ID = 3;
已删除 1 行。
在会话1再次执行COMMIT加DELETE操作,发现会话2的REBUILD操作已经完成。
索引已更改。
SQL2> SELECT * FROM V$VERSION;
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE 10.2.0.1.0 Production
TNS for 32-bit Windows: Version 10.2.0.1.0 - Production
NLSRTL Version 10.2.0.1.0 - Production
再来看11g中的情况:
SQL> CONN YANGTK/yangtk@ORA11G已连接。
SQL> CREATE TABLE T AS SELECT ROWNUM ID, A.* FROM DBA_OBJECTS A;
表已创建。
SQL> CREATE INDEX IND_T_NAME ON T(OBJECT_NAME);
索引已创建。
SQL> DELETE T WHERE ID = 1;
已删除 1 行。
模仿上面的例子,在会话1中保持锁。会话2执行ONLINE REBUILD INDEX处于等待锁的状态。
最后在会话1再次提交:
SQL> COMMIT;
提交完成。
SQL> DELETE T WHERE ID = 3;
已删除 1 行。
这是会话2的REBUILD操作完成:
索引已更改。
SQL2> SELECT * FROM V$VERSION;
BANNER
-------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production PL/SQL Release 11.1.0.6.0 - Production
CORE 11.1.0.6.0 Production
TNS for Linux: Version 11.1.0.6.0 - Production
NLSRTL Version 11.1.0.6.0 - Production
看上去,10g和11g的ONLINE REBUILD操作并没有什么不同,在创建索引开始和结束的时候都要获取到表的锁。不过二者获取的锁信息不同,也就是说二者对于后续DML的影响并不相同。
在11g中,ONLINE REBUILD操作获取的锁不会阻塞后续DML操作,而11g以前的版本,在ONLINE REBUILD索引过程中,获取到锁的一个短暂时刻会阻止DML操作。
下面演示一下二者的区别,首先在11g中,一个会话运行ONLINE REBUILD,另一个会话同时运行对表的DML操作,第三个会话查询V$LOCK视图,检查ONLINE REBUILD操作是否会阻塞DML操作。
会话1:
SQL> SELECT SID FROM V$MYSTAT WHERE ROWNUM = 1;
SID
----------
131
SQL> CREATE INDEX IND_T_ID ON T(ID);
索引已创建。
SQL> ALTER INDEX IND_T_NAME REBUILD ONLINE;
索引已更改。
会话2:
SQL2> BEGIN
2 FOR I IN 1..100000 LOOP
3 UPDATE T SET ID = ID WHERE ID = 1;
4 COMMIT;
5 END LOOP;
6 END;
7 /
PL/SQL 过程已成功完成。
会话3:
SQL> CONN YANGTK/yangtk@ORA11G已连接。
SQL> SET SQLP 'SQL3> '
SQL3> SET SERVEROUT ON
SQL3> DECLARE
2 V_NUM NUMBER;
3 V_TOTAL NUMBER DEFAULT 0;
4 BEGIN
5 FOR I IN 1..1000 LOOP
6 SELECT SUM(BLOCK) INTO V_NUM FROM V$LOCK WHERE SID = 154;
7 V_TOTAL := V_TOTAL + NVL(V_NUM, 0);
8 END LOOP;
9 DBMS_OUTPUT.PUT_LINE(V_TOTAL);
10 END;
11 /
0
PL/SQL 过程已成功完成。
确保会话1的REBUILD ONLINE与会话2、会话3的存储过程几乎同时运行。可以看到在整个REBUILD索引过程中,没有阻塞DML操作。
下面看看同样的操作在10g下执行的结果。
会话1:
SQL> CONN YANGTK/YANGTK@YTK102已连接。
SQL> SELECT SID FROM V$MYSTAT WHERE ROWNUM = 1;
SID
----------
144
SQL> CREATE INDEX IND_T_ID ON T(ID);
索引已创建。
SQL> ALTER INDEX IND_T_NAME REBUILD ONLINE;
索引已更改。
会话2:
SQL2> CONN YANGTK/YANGTK@YTK102已连接。
SQL2> BEGIN
2 FOR I IN 1..100000 LOOP
3 UPDATE T SET ID = ID WHERE ID = 1;
4 COMMIT;
5 END LOOP;
6 END;
7 /
PL/SQL 过程已成功完成。
会话3:
SQL3> CONN YANGTK/YANGTK@YTK102已连接。
SQL3> SET SERVEROUT ON
SQL3> DECLARE
2 V_NUM NUMBER;
3 V_TOTAL NUMBER DEFAULT 0;
4 BEGIN
5 FOR I IN 1..1000 LOOP
6 SELECT SUM(BLOCK) INTO V_NUM FROM V$LOCK WHERE SID = 144;
7 V_TOTAL := V_TOTAL + NVL(V_NUM, 0);
8 END LOOP;
9 DBMS_OUTPUT.PUT_LINE(V_TOTAL);
10 END;
11 /
6
PL/SQL 过程已成功完成。
SQL3> SELECT * FROM V$VERSION;
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE 10.2.0.1.0 Production
TNS for 32-bit Windows: Version 10.2.0.1.0 - Production
NLSRTL Version 10.2.0.1.0 - Production
从上面的结果不难看出,11g以前版本在ONLINE REBUILD的时候会对DML操作短暂的阻塞,而11g彻底消除了ONLINE REBUILD对DML的影响。