Oracle两个使用细节知识点

本文介绍了Oracle数据库中两个关键优化点:慎用forupdate大规模数据操作,以及数据列默认值设置的影响。对于forupdate,指出其在锁定数据时带来的性能损耗及redo日志消耗,建议合理使用避免数据表过大时的性能瓶颈。关于默认值设置,通过对比不同操作顺序的性能表现,强调了生产环境中均衡化负载的重要性,避免一次性操作导致的风险。


大量的Oracle知识点都是需要在实际中不断发现实验获得的。本篇中介绍两个实际中发现的问题,权当记录。


1、慎用for update大规模数据


Select for update是我们有时候会使用的一种语句类型。它的特点优势是能够将筛选出的数据进行独占锁定。通常情况下的Oracle select操作,是不会对数据表进行锁定,同时也不会被其他操作所阻塞。


在实际应用中,我们的确是经常使用for update进行数据锁定。常用的场景是,确定符合条件的数据是否存在,如果存在就立即锁定该条记录独占。之后对独占的数据进行修改。在存在多会话并发访问的时候,for update可以起到资源/信号量抢夺的作用。

 

但是,for update的使用是有相应的代价的。在之前的系列文章中,我们探讨过for update要消耗更多的时间,主要在于需要在每个数据行进行加锁操作。在Oracle[url=]11g[/url]中,CBO的Query Optimizer采用了一些优化[url=]技术[/url]进行性能提升。但是,总的来说,for update是要消耗更多的资源的。


除了上面提到的方面,在redo size上for update也是有独特之处。通常我们继续DDL和更改类型操作的时候,才会生成redo log片段。普通的select是不会生成redo log记录的。如果使用for update,我们是可以发现select会生成redo size。

 


SQL> select * from v$version;


BANNER

--------------------------------------------------------------------------------

Oracle Database11gEnterpriseEdition Release11.2.0.1.0 - Production

PL/SQL Release11.2.0.1.0 - Production

CORE    11.2.0.1.0      Production


TNS for Linux: Version11.2.0.1.0 - Production

NLSRTL Version11.2.0.1.0 – Production

 

使用v$mystat可以看到当前会话累计的各种指标统计量。

 


SQL> select a.statistic#, name, [url=]value[/url] from v$mystat a left join v$statname b on a.STATISTIC#=b.STATISTIC# where b.NAME = 'redo size';


STATISTIC# NAME                     VALUE

---------- -------------------- ----------

      169 redo size                    0

 

当前,redo size生成的消耗量是0。

 

SQL> select bytes, blocks from dba_segments where segment_name='T' and wner='SYS';


    BYTES    BLOCKS

---------- ----------

  9437184      1152


SQL> select count(*) from t;


COUNT(*)

----------

    72349

 

此时,我们使用一个for update语句。

 

SQL> select * from t where wner='SCOTT' for update;


(结果集合省略……)

 

此时,redo size的消耗可以查询到。

 

SQL> select a.statistic#, name, value from v$mystat a left join v$statname b on a.STATISTIC#=b.STATISTIC# where b.NAME = 'redo size';


STATISTIC# NAME                     VALUE

---------- -------------------- ----------

      169 redo size                    0


SQL> commit;


SQL> select a.statistic#, name, value from v$mystat a left join v$statname b on a.STATISTIC#=b.STATISTIC# where b.NAME = 'redo size';


STATISTIC# NAME                     VALUE

---------- -------------------- ----------

      169 redo size                 2628

 

一个一般的for update之后,进行commit,没有发生本质上的数据变化。但是之后,我们发现了redo size指标增加到了2628。一般的select操作,就不会产生redo size消耗。

 

SQL> select * from t where wner='SCOTT';

(结果集合省略……


SQL> select a.statistic#, name, value from v$mystat a left join v$statname b on a.STATISTIC#=b.STATISTIC# where b.NAME = 'redo size';


STATISTIC# NAME                     VALUE

---------- -------------------- ----------

      169 redo size                 2628


SQL> commit;

SQL> select a.statistic#, name, value from v$mystat a left join v$statname b on a.STATISTIC#=b.STATISTIC# where b.NAME = 'redo size';


STATISTIC# NAME                     VALUE

---------- -------------------- ----------

      169 redo size                 2628

 

可见,一般的select的操作是不会产生redo记录的。这个问题我们也可以使用autotrace工具来查看到。


SQL> set autotrace traceonly;

SQL> select * from t for update;

已选择72349行。


执行计划

----------------------------------------------------------

Plan hash value: 250223868

----------------------------------------------------------------------------

| Id | Operation          | Name | Rows | Bytes | Cost (%CPU)| Time    |

----------------------------------------------------------------------------

|  0 | SELECT STATEMENT   |     | 72349 | 6853K|  282  (1)| 00:00:04 |

|  1 | FOR UPDATE        |     |      |      |           |         |

|  2 |  BUFFER SORT      |     |      |      |           |         |

|  3 |   [url=]TABLE[/url] ACCESS FULL| T   | 72349 | 6853K|  282  (1)| 00:00:04 |

----------------------------------------------------------------------------

统计信息

----------------------------------------------------------

        46 recursive calls

     73649 db block gets

      1066 consistent gets

      1026 physical reads

  15086508 redo size

   5649675 bytes sent via SQL*Net to client

     53429 bytes received via SQL*Net from client

      4825 SQL*Net roundtrips to/from client

         1 sorts (memory)

         0 sorts (disk)

     72349 rows processed


SQL> select * from t;

已选择72349行。


执行计划

----------------------------------------------------------

Plan hash value: 1601196873

--------------------------------------------------------------------------

| Id | Operation        | Name | Rows | Bytes | Cost (%CPU)| Time    |

--------------------------------------------------------------------------

|  0 | SELECT STATEMENT |     | 72349 | 6853K|  282  (1)| 00:00:04 |

|  1 | TABLE ACCESS FULL| T   | 72349 | 6853K|  282  (1)| 00:00:04 |

--------------------------------------------------------------------------

统计信息

----------------------------------------------------------

         1 recursive calls

         0 db block gets

      5788 consistent gets

         0 physical reads

         0 redo size

   3286309 bytes sent via SQL*Net to client

     53429 bytes received via SQL*Net from client

      4825 SQL*Net roundtrips to/from client

         0 sorts (memory)

         0 sorts (disk)

     72349 rows processed

 

从统计量信息看,使用for update在处理相同数据行数据的时候,要产生相当数据量的redo size和嵌套[url=]SQL[/url]。


分析原因,从Oracle角度看,实现数据行的锁定依靠Select语句是不能做到的。所以,在for update操作的时候,Oracle会启动一个事务过程,将筛选出的数据行作出修改动作。


在这个过程中,Oracle会在条件记录对应的数据块上的事务槽作出标记,表示对应的数据行已经进行修改。这样才可能以独占的方式获取数据行。在Oracle中,只要对数据块进行了修改,无论这个修改是否最终被commit/rollback,都会生成出redo log信息,写入到online redo log中。

 

数据表越大,涉及的数据越多,for update的损耗和生成redo就越大。所以,如果数据表很大,同时select出的数据集合也很大,频繁的select for update就引起Oracle产生出大量的redo blocks。在归档模式下,过于频繁的对大数据进行for update操作,会带来额外的归档日志存储要求。如果没有预先的规划,容易引起[url=]数据库[/url]hange住。

 

2、数据列默认值:先加后加,大不相同


数据列默认值,也是我们经常使用的一种数据库对象技术。通过数据列默认值的设置,当插入数据的时候,如果不对数据列进行指定,那么默认值就会加入其中。


如果对一个已经存在数据的数据表加列,同时确定默认值和之后设置默认值有什么差别呢?

 

SQL> create table t (id number, name varchar2(10));

Table created


SQL> select * from t;

       ID NAME

---------- ----------


SQL> insert into t select object_id, null from dba_objects where rownum<10;

9 rows inserted


SQL> commit;


Commit complete


SQL> select * from t;


       ID NAME

---------- ----------

       20

       46

       28

       15


9 rows selected

 

如果我们首先创建数据列和定义默认值在一起,会如何呢?

 

SQL> set timing on;

SQL> alter table t add vname varchar2(10) [url=]default[/url] 'X';

Table altered


Executed in0.39 seconds

SQL> select * from t;


       ID NAME      VNAME

---------- ---------- ----------

       20           X

       46           X

       28           X

       15           X

       29           X

        3           X

       25           X

       41           X

       54           X


9 rows selected


Executed in 0.093 seconds

 

如果我们在新添加列的时候,指定default value。效果就是将数据列加入其中,并且使用默认值填满已经为null的记录项目。Default value的含义在于补全,将不完全的数据加以不全。


不过,也要注意这个操作的时候消耗为0.39s,时间消耗略大。下面,如果我们只是指定数据列,不同时进行默认值指定的情况呢?


--新添加列

SQL> alter table t add nname varchar2(10);

Table altered


Executed in 0.062 seconds


--后设置默认值;

SQL> alter table t modify nname default 'M';

Table altered


Executed in 0.062 seconds

 

SQL> select * from t;


       ID NAME      VNAME     NNAME

---------- ---------- ---------- ----------

       20           X        

       46           X        

       28           X        

       15           X        

       29           X        

        3           X        

       25           X        

       41           X        

       54           X        


9 rows selected


Executed in 0.094 seconds

 

第一个区别,如果后设置默认值,数据列中原来为空的取值,是不会如同时设置一样补齐数据的。

 

SQL> insert into t (id) values (100);

1 row inserted


Executed in 0 seconds

SQL> commit;

Commit complete


Executed in 0.015 seconds


SQL> select * from t;


       ID NAME      VNAME     NNAME

---------- ---------- ---------- ----------

       41           X        

       54           X        

      100           X         M


10 rows selected


Executed in 0.094 seconds

 

从实验结果上看,对新插入的数据,后设置default value是其效果的。同时,从时间上看,一次性的设置新列和默认值消耗的时间巨大。这对于生产环境是一个潜在的风险。


我们对高消耗的操作通常的处理是均衡化负载,尽可能将一个操作划分为若干个可干预、可控制的操作。所以,对一个大表添加有默认值数据列,可以先加一个无默认值数据列,之后修改默认值,最后使用dml进行数据更新。

 

3、结论


本篇阐述介绍的两个知识点,都是一些非常零碎的经验积累。只有将这些知识通过实验进行验证,归纳为自己的所得,不断的总结提高,才能达到我们质变的目的。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值