老和尚在他的blog中描述了了这个问题,我这里做一个详细的测试来说明这个问题。
在oracle 11g以前,如果需要在一个表中执行类似如下的命令,而且这个表本身已经有很多数据,那么,这个操作将可能花费很长的时间,并且可能阻塞应用:
- SQL>alter table table_name add field_name number default 0 not null;
因为这个操作需要修改以前所有的行,并把他们都修改为默认值,如以上的0。但是,这个情况在oracle 11g中有了巨大的改变,oracle 11g中,如果对一个表增加一个列,并带有默认值,Oracle并没有真实的去修改以前的列,只不过通过查询的时候,采用类似NVL(null,新值)的方法转换一下,让以前的列看起来象有值一样。
这个过程在select 的时候就转换好了,所以,对于使用者看来,他们是有值的,这个细小的改动将大大减少这个语句的执行时间,使得这样的语句在oracle 11g中不会引起任何阻塞,更不会影响性能了。
下面看一个简单的对比说明:
首先是Oracle 10g的测试
- Piner@10gR2>create table test(a int);
- Table created.
-
- Piner@10gR2>begin
- 2 for i in 1..1000 loop
- 3 insert into test values('1');
- 4 end loop;
- 5 commit;
- 6 end;
- 7 /
- PL/SQL procedure successfully completed.
-
- Piner@10gR2>set serveroutput on
- Piner@10gR2>exec show_space('TEST');
- Total Blocks............................8
- Total Bytes.............................65536
- Unused Blocks...........................0
- Unused Bytes............................0
- Last Used Ext FileId....................4
- Last Used Ext BlockId...................135641
- Last Used Block.........................8
- PL/SQL procedure successfully completed.
-
-
- Piner@10gR2>alter table test add flag char(2000) default 1 not null;
- Table altered.
-
- Piner@10gR2>exec show_space('TEST');
- Total Blocks............................384
- Total Bytes.............................3145728
- Unused Blocks...........................0
- Unused Bytes............................0
- Last Used Ext FileId....................4
- Last Used Ext BlockId...................134409
- Last Used Block.........................128
- PL/SQL procedure successfully completed.
-
- Piner@10gR2>select count(*) from test where flag is null;
-
- COUNT(*)
- ----------
- 0
可以看到的是,在增加默认值的列以后,该表的使用空间发生了巨大的变化,也证明数据库修改了以前的所有的块中的数据,让他们都生效。
以下是Oracle 11g的操作
- Piner@11gR1>create table test(a int);
- Table created.
-
- Piner@11gR1>begin
- 2 for i in 1..1000 loop
- 3 insert into test values('1');
- 4 end loop;
- 5 commit;
- 6 end;
- 7 /
- PL/SQL procedure successfully completed.
-
- Piner@11gR1>set serveroutput on
- Piner@11gR1>exec show_space('TEST');
- Total Blocks............................8
- Total Bytes.............................65536
- Unused Blocks...........................0
- Unused Bytes............................0
- Last Used Ext FileId....................4
- Last Used Ext BlockId...................83745
- Last Used Block.........................8
- PL/SQL procedure successfully completed.
-
- Piner@11gR1>alter table test add flag char(2000) default 1 not null;
- Table altered.
-
- Piner@11gR1>exec show_space('TEST');
- Total Blocks............................8
- Total Bytes.............................65536
- Unused Blocks...........................0
- Unused Bytes............................0
- Last Used Ext FileId....................4
- Last Used Ext BlockId...................83745
- Last Used Block.........................8
- PL/SQL procedure successfully completed.
-
- Piner@11gR1>select count(*) from test where flag is null;
-
- COUNT(*)
- ----------
- 0
这里可以看到,同样的记录,做了同样的操作,增加新列,并设置默认值以后,表的使用空间没有发生任何变化,也同样证明了Oracle没有修改以前的块中的数据。
这里的例子只有1000条记录,主要是为了体现了空间的变化。如果是几是万条,或者几百万条以上的表,同样的操作,oracle 11g是马上就结束了,不会有任何影响,而oracle 10g可能需要非常长的时间,这个时间表是被锁住的,不能执行任何DML操作,对于写频繁的应用来说,将会导致应用的阻塞。
最近是比较忙一些,天天晚上都工作到11点,在赶进度修正新书的内容。上一次说到了新书草稿完成以后,一直放了很长时间,其实是在等oracle 11g,想增加一些oracle 11g的新的高可用的特性,所以,从上海oow回来以后,才开始修正新书内容。但是因为草稿真的是很草稿,所以修正的工作量很大,而且大量的东西要增加,也需要更多的测试。
新的章节已经有所变化,很多章节都合并了,现在只有16章,已经修正到第10章了。现在的页数有400页,估计到时候全部弄完,不会小于450页。
按照我的想法,估计要修正2次,才勉强可以拿出来看,到时候,我会找几个朋友帮我一起审查一下。我也希望早点完成这个工作,给大家带来一些收获。
另外,众所周知,我们伟大的墙,又做了一件伟大的事情,把我们的feedburner给阻尼了。当他阻尼flickr的时候,我不相信,认为这个阻尼会解除的,所以我的相册一直没有换。但是,现在,我相信了,我们将会迎来更多的阻尼。这就是慢水煮青蛙,先看看大家的反应,然后再来一下。
相信了怎么办呢?我不得不苟且的更换我的相册了,不得不苟且的更换我的RSS聚合,等周末我看能不能找个时间弄弄,我还不知道怎么换到feedsky呢,唉。跟一位朋友说的那样,有能力的,出去吧,没有人会认为你不爱国的。
等新书出来,再好好的把我的blog给整整吧,现在的订阅量与访问量还实在太低啊。
前几天,大哥发了一个短信给我,说老爸在集市上又被骗了,有人买他的东西,给了他一个假的50元的人民币。他今年已经是第二次被50元的假币骗了。
老爸今年70岁了,妈妈前两年过世以后,他就一个人生活。从来没有少过他的钱用,也跟他说了无数次了,让他别干活了,但是,他就是闲不住,总是做些竹器,如篓子,扫帚等的,拿到集市上去卖。
而且,他比较让我气愤的是,我们给他的钱,居然全存到银行里面去了,而且还存成定期。我说,你存起来干嘛,赶紧用掉啊,他却说,他用不了那么多,存着有利息,我晕。
不管怎么样,我要打个电话回去安慰他,于是开玩笑说,“听说你又被骗了”。
老爸说,“是啊,人老了,认不出来钱的真假了”。
我说,“摸呢,能摸出来的啊。”
老爸说,“手上都是茧,哪能摸的出来呢。”
是啊,老爸劳累了一辈子,手上都是厚厚的茧,我不应该把这个都忘记了。
我说,“不就50元钱啊,你别放在心上,骗了就骗了,不要生气啊,钱不够我给你啊。”
老爸说,“不会的,你放心好了,我这里有钱。”
我知道老爸这么说,心里还是有点舍不得的,后来我了解到,这个买他东西的是个中年农村妇女,我想想,也只有这样的人,连老人都忍心去骗,唉。
老爸也是,每次提到给他钱吧,都说自己有钱,硬是给他的钱,又不舍得花,还非要存起来,让人哭笑不得。不过,他总是喜欢做些小东西,这个我还是不反对的,人老了,多动动,做一些轻松一些的活,其实是有好处的。
我只好提醒他了,以后就算是卖东西,也别收50、100的钱了,钱是小事情,别让别人得利,坏了自己的心情。
前几天看了biti的blog,看到了他惨痛的买房经历,主要体现在以下三点:
1、从美国出差一趟回来,房价涨了N多,多少个月又白做了
2、看好的房子,正准备出手,谁知道一夜之间,被卖掉了
3、接下来想看房子,却是一天一个价,涨的飞快
今天跟另外一个朋友聊起这房子的事情,她说整个周末都在陪别人看房子,现在不是在买房子,是在抢房子,买主都是带着现金过去的,一旦看中,马上出手。
我晕,这房子难道不值钱了,还是这世界钱太多了,好象都不是啊。
先说说这位朋友,已经是三套房子的主了,最近一套房子也是跟买白菜似的,一周不到就搞定了,居然比我买房子还快。另外,她去陪看房子的几位,都是已经有房子的主了,准备买第2套或者是第N套房子。其中的有一位,今天中午勇敢的,果断的成交了,估计从看房到买房也就几天吧。
再看看我们小区的战士,先是把房子卖了个好价钱,宁愿拖家带口的租了个房子住,也不管小孩是否能承受的了。然后用卖房子的钱,买了另外一套新楼盘,便宜是便宜多了,但是,这一租估计就是好几年了。
我当初买房的时候,虽然也很快,但是不至于象现在这么抢啊。不过,有一点建议,我还是觉得比较中肯的,看中了的,就赶紧买了吧,别指望这房子降什么价了。
房子真的不值钱吗?杭州的平均房价已经1万以上了吧,还一天一个价,不是跌,是在猛涨。
那就是这个世界有钱人太多了,但是看看好多买房的人,都背着一身的债啊,在替银行打工呢。
想来想去,想不明白,最终的结论是,这世界疯了。
没有什么特别的地方,记录一下,方便查找。
第一个用户Piner:
创建一个测试表
- SQL> create table test as select * from dba_objects;
创建一个测试索引
- SQL> create index ind_test on test(object_id);
如果是Oracle 10g以上,索引创建完成,就自带统计信息了。
创建一个表,存放export出来的统计信息
- SQL> exec dbms_stats.create_stat_table(ownname => 'Piner', stattab => 'STAT_AT', tblspace => 'SYSTEM');
刚创建完成,记录数为0
- SQL> select count(*) from STAT_AT;
-
- COUNT(*)
- ----------
- 0
收集表的统计信息
- SQL>exec dbms_stats.gather_table_stats(ownname=>'PINER',tabname=>'TEST',estimate_percent => 100,method_opt=> 'FOR ALL INDEXED COLUMNS');
如果是oracle 10g以下,如9i,以上的分析是没有分析到索引的,可以在上面的分析中增加cacade=>true,或者是单独分析索引。
- SQL>exec dbms_stats.gather_index_stats(ownname=>'PINER',indname=>'IND_TEST',estimate_percent=>100);
我们导出统计信息
- SQL>exec dbms_stats.export_table_stats(ownname=>'PINER', tabname=>'TEST', stattab=> 'STAT_AT');
-
- SQL> select count(*) from STAT_AT;
-
- COUNT(*)
- ----------
- 78
切换到另外一个用户test
- SQL> create table test as select * from dba_objects;
-
- Table created.
-
- SQL> create index ind_test on test(object_id);
-
- Index created.
-
- SQL> create table STAT as select * from piner.STAT_AT;
-
- Table created.
如果直接执行当前的用户与表名,可以执行成功,但是,没有统计数据导入,需要把原来的导出用户更新成导入用户。
- SQL>exec dbms_stats.import_table_stats(ownname=>'TEST', tabname=>'TEST', stattab => 'STAT');
-
- SQL>update stat set c5='TEST' where c5='PINER'
-
- SQL>commit;
-
- SQL>exec dbms_stats.import_table_stats(ownname=>'TEST', tabname=>'TEST', stattab => 'STAT');
然后再导入成功,导入信息包括表统计,索引统计信息,列的分析信息
kamus在他的blog 10gRAC培训 - 2 and last。说到了这个cpu time,具体可以看正文以及下面我与他的留言,他的意思其实就是:
CPU Time相对于其它等待事件,应当先忽略CPU Time,处理其它等待事件。而且cpu time高一般代表是好事情,因为系统并没有把时间耗费在Wait上。Elapsed Time = CPU Time + Wait Time,所以甚至我们可以说CPU Time越高越好。
不是说他的不对,只是比较觉得这句话太容易误导别人,所以,给一些补充建议,我同意如果有很明显的其它事件,而cpu time不明显的时候,可以优先处理其它事件。但是,还有以下2个地方需要注意:
1、cpu time高一般代表系统的逻辑读大,或者是高计算型的东西,如case函数,decode函数,自定义的计算类型函数等等,把cpu给耗光了,但是这个时候可能根本没有其它等待事件。这样是否证明这个系统很好呢?很可能是,非常不好,因为他很有可能没有必要有这么多的逻辑多,或者是,没有必要有这么多的cpu密集型计算就可以完成的任务,现在消耗这么CPU Time就是不正常的,很容易导致系统负载异常。
2、至于wait time,比如OLTP环境上最典型的db file sequential read引起的等待,是因为发生了物理io而引起的,而很多时候,物理读是随逻辑读增加而增加的,而逻辑读可能会导致cpu time高,也就是说,cpu time与wait time一般是同时增加的。特别是比较高访问量,大数据量,大SGA的系统,逻辑读与物理读都比较高,除了cpu time与db file sequential read,可能不会有很明显的其它等待事件,而优化的结果可能是cpu time与wait time同时减少。
既然说到了db file sequential read,也解释一下这个事件,与cpu time一样,很多时候说明系统是比较正常的,一个优化非常良好的系统,很可能第一个等待事件就是它。因为没有一个系统,不会发生任何物理IO,既然有物理IO,当然db file sequential read就是最好的了,一般表示读数据正常。同样的理由,db file sequential read大了,也可能预兆系统有不该有的物理读发生了。
如一个看起来很正常的sql语句,单个语句效率看起来并不差,执行时间已经小于0.01秒。假定原来逻辑读每个语句平均是100个,物理读平均10个,执行次数每小时50万次,优化完成以后,逻辑读降低为平均50个,物理读平均5个,那么,每小时降低的逻辑读总量就是50*50w=2500w个,降低的物理读为5*50w=250w个。那么再假定每个cpu每秒处理100000个逻辑读就达到了极限,每个物理读需要等待8ms,那么,就可以减少约2500w/100000*3600=7%的CPU使用率(单CPU),可以减少250w*8ms=20000秒的总io wait time。
同理,如果能采用同样的方法,减少执行次数从每小时50w到每小时25w,则收到的效果是一样的。
评价这个cpu time与db file sequential read是否正常,需要很多调优的经验,不过,需要记住的是,任何情况下,不要绝对的说好还是不好。最好的方式就是从statspack或者是awr上看看,排在cpu time,或者是物理读top的语句,他们是否应当有这么大的消耗,他们是否应当执行这么多的次数。
想不到啊,这篇文章都有续集了,起码当初写第一篇的时候是没有料到的。因为琐事太多,正事没有。。。没有办法,那就续吧。
经过了半年的招聘,终于又有一个dba今天加入了taobao,在此之前,我们只招到了一个毕业生。现在我们的队伍终于扩大到了6人,在这里先欢迎他加入taobao数据库团队。下个星期还有一个面试,我希望能早点结束掉今年的招聘计划。
自从参加了OOW回来以后,突然发现E文的交流与会议多了起来,郁闷,还郁闷,听力太差,害苦我了。汪海也去参加E文培训去了,我现在在忙于新书的修正与审稿,没有时间补习这些东西了。决定了,从下个星期起,每天早上早起来半小时,补习E文,争取走向国际化。
漂亮的jane MM与nina MM来我家作客了,jane MM从上海回到杭州乡下,来看我们了。从三年前合租同住一个屋檐下,到现在jane MM还是单身一人,而nina MM却找了个好老公,不仅仅成功的把自己嫁了出去,还怀上了一个小宝宝。10月份,怀着宝宝的nina MM的婚礼要正式在他老公家举行了,我与老婆决定作为女方代表去参加他们的婚礼,只是可惜我女儿太小,还不能帮他们牵婚纱。希望我的车到时候开到他们家的时候,不是最差的车就行了。
jane MM还是怀恋杭州的乡下生活了,想回杭州,把终身大事交给我,让我在我们单位帮她找个合适的男朋友,把她自己推销出去。这个任务好难啊,举目望去,不是太小,就是不适合。单身好男人呢,已经被自己单位的那些单身女人一个一个的数过了,基本没有了。
好象中秋不远了,提前祝各位有情人终成眷属。
好几天没有睡好了,明天还有好多杂事,好多会议。今天争取早点睡,现在23点,准备洗澡睡觉。
最近不知道为什么,没有写blog的冲动了,累了,晚上睡不着,早上醒的早,老了。也可能是压力太大了吧,也可能自己给自己定的要求太高了,有时候想想,何苦呢,休息一下多好啊。
侄子后天要去上学了,听说是什么自考本科,现在的孩子,只考了两三百分,啥都不说了,一代不如一代啊。本来也想就这个事情写一个blog的,留到以后吧。寄了点钱给他,反复交代他,以后好好读书啊,别对不起这学费。他老爸,一个老实巴交的农民,供他读书不容易啊,也不知道他读出来了能做啥。
是我和我哥把他们下一代都害了,好几个堂侄,也是看到我和我哥还不错,不惜成本买书读,也不管负债累累,也不管成绩怎么样,也不管这大学怎么样,但是,结果是知道的,已经毕业的几个在外面打工,老婆都找不到。还在读书的几个,唉,不说了。
老婆总是把每个月的工资全部花完,还总是不够,结果是知道的,存款越来越少。有的时候都想不明白这些钱花在哪里了,不过我看了一下电费单,总算明白了一点道理,一个月光电就用了700多度。想想小的时候,一个清油灯就要供一家人来用了,这差距。
抢钱最近在流行,车开出去到处在收费,单位下面的停车费都涨到500元/月了,人民币贬值也不至于贬的这么厉害吧,不开了。早上老婆送我,下班了自己走回去,计算了一下,从单位走到家,20分钟,近吧。
听说湖南的桥刚修好就垮了,湘西,离我家不远,唉,要贪少贪点行末,把这社会又河蟹了一把。
女儿还算听话,唯一高兴的事情了。
12点了,睡吧。
我们先看一个具体的例子
会话1,创建一个简单表,并在上面创建一个存储过程:
- 11gR1 Piner>create table test (a int);
-
- Table created.
-
- 11gR1 Piner>create or replace procedure sp_test
- 2 as
- 3 num number;
- 4 begin
- 5 select count(*) into num from test;
- 6 end;
- 7 /
-
- 11gR1 Piner>select object_name,status from user_objects where object_name='SP_TEST';
-
- OBJECT_NAME STATUS
- ------------------------------ -------
- SP_TEST VALID
同样的会话,我们插入一条记录,查看锁状态:
- 11gR1 Piner>insert into test values(1);
-
- 1 row created.
-
- 11gR1 Piner>SELECT USERENV('SID') FROM DUAL;
-
- USERENV('SID')
- --------------
- 118
-
- 11gR1 Piner>select sid,id1,id2,type,lmode,request from v$lock
- 2 where sid = 118;
-
- SID ID1 ID2 TY LMODE REQUEST
- ---------- ---------- ---------- -- ---------- ----------
- 118 99 0 AE 4 0
- 118 71091 0 TM 3 0
- 118 524292 679 TX 6 0
可以看到,这里对比以前的版本,多了一个类型为AE的锁。
然后,我们在另外一个会话做另外的操作
会话2:增加列
- SQL> alter table test add b int;
-
- Table altered.
-
- SQL> alter table test drop column b;
- alter table test drop column b
- *
- ERROR at line 1:
- ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
-
- 11gR1 Piner>select object_name,status from user_objects where object_name='SP_TEST';
-
- OBJECT_NAME STATUS
- ------------------------------ -------
- SP_TEST VALID
可以看到,列增加成功,并没有任何阻塞,但是,drop列是阻塞的。是因为add新列并不影响正在执行的dml,但是drop老列却是可能影响到正在执行的DML的。
也可以看到,增加列的过程完成以后,存储过程并不失效。这个是很好的一个特性,只要不让存储过程错误,就不会使得存储过程INVALID,减少了DDL引发的library cache pin可能导致的系统异常。
我们另外看看oracle 11g的一个ddl timeout参数,在会话2上修改此参数,并重新执行刚才的drop列的命令。
- SQL> show parameter ddl_lock_timeout;
-
- NAME_COL_PLUS_SHOW_PARAM TYPE VALUE_COL_PLUS_SHOW_PARAM
- ------------------------------ ----------- --------------------------
- ddl_lock_timeout integer 0
-
- SQL> alter system set ddl_lock_timeout = 10;
-
- System altered.
-
- SQL> set timing on
- SQL> alter table test drop column b;
- alter table test drop column b
- *
- ERROR at line 1:
- ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
-
- Elapsed: 00:00:10.00
这里的ddl_lock_timeout表示ddl会等待设置的值(单位为秒),这个是很有用的,比如在一个更新很频繁的表上面,适当的等待可以让ddl执行成功,以前dcba专门写了一个循环来模拟这个等待,现在,终于有这个参数了。
可以看到,正好等待10s左右
现在,会话1,我们提交该事务
- 11gR1 Piner>commit;
-
- Commit complete.
会话2,drop终于可以成功了
- SQL> alter table test drop column b;
-
- Table altered.
我们再查看存储过程的状态
- 11gR1 Piner>select object_name,status from user_objects where object_name='SP_TEST';
-
- OBJECT_NAME STATUS
- ------------------------------ -------
- SP_TEST VALID
整个简单测试到此完成,大家看到了几处跟以前10g不一样的地方?可以看到的是,oracle在一些细节的地方,处理的越来越好了。
接到通知,因为文一路修路,需要停水36小时,从今天晚上8点起到后天早上8点,共2夜一天。今天早早就回家吃饭洗澡了了,还接了几大桶水做备用。
大热天的,怎么说停就停呢,停个几小时也不打紧,居然停36小时,我ft,明天晚上怎么洗澡啊?不过想想,幸好是停水,不是停电啊,停电我就要去找宾馆了。
河蟹社会就是好啊。