2007张学友“好久不见”中国巡回杭州演唱会将于今年4月30日在黄龙体育中心举行。一代天皇巨星,估计这是最后一次来杭州了,所以,作为我们这个时代的人,怎么也是会去看看他的。
这一次是2002年的音乐之旅世界巡演后,真正意义上的个人演唱会。2002年的5月1日,黄龙体育中心6万人的体育场内座无虚席,在那里举行的“张学友2002音乐之旅世界巡回演唱会”让杭州歌迷疯狂了整整一夜。这次也一样,很多人从附近专程过来,就为了看看我们的心中永远的“歌神”。
为了这个演唱会,我在一个月以前就定了票,现在好多人想再定票的,要么要加很多的钱,要么只有非常非常差的位置了,可见学友在歌迷中的魅力。我一个朋友,甚至也是从上海,今天专程到杭州,来听这场演唱会。
不同于现在的年轻人,他们只喜欢周杰伦,那个始终我都不知道他在唱什么歌词的人,居然,也能得到年轻人的青睐。不要否认,这就是代沟。
首先,我们在创建一个测试表A,指定pctfree为0,这样可以保存更多的记录
- Piner@10gR2 8K>create table a(a varchar2(1)) pctfree 0 TABLESPACE users;
-
- Table created.
然后,我们查看这个表最大可以插入的记录数的规定值与实际值
- Piner@10gR2 8K>select object_id from dba_objects where object_name='A';
-
- OBJECT_ID
- ----------
- 11488
-
- Piner@10gR2 8K>SELECT SPARE1 FROM sys.TAB$ WHERE OBJ#=11488;
-
- SPARE1
- ----------
- 736
-
- Piner@10gR2 8K>select max(sys_op_rpb(rowid)) from a;
-
- MAX(SYS_OP_RPB(ROWID))
- ----------------------
以上查询可以返回每个块实际插入记录的最大数目,从0开始记数。现在实际可以插入的记录返回为null,是因为这个表还没有任何记录,我们需要插入一些记录。
- Piner@10gR2 8K>begin
- 2 for i in 1..6000 loop
- 3 insert into a values('1');
- 4 end loop;
- 5 commit;
- 6 end;
- 7 /
-
- PL/SQL procedure successfully completed.
-
- Piner@10gR2 8K>select max(sys_op_rpb(rowid)) from a;
-
- MAX(SYS_OP_RPB(ROWID))
- ----------------------
- 732
以上查询返回732,因为从0开始,所以,每个块实际可以保存的最大记录数为732+1=733,我们统计一下看看:
- Piner@10gR2 8K>select f,b,count(*) from (
- 2 select dbms_rowid.rowid_relative_fno(rowid) f,
- 3 dbms_rowid.rowid_block_number(rowid) b
- 4 from a) group by f,b;
-
- F B COUNT(*)
- ---------- ---------- ----------
- 4 23 733
- 4 24 733
- 4 46 733
- 4 20 733
- 4 44 733
- 4 21 733
- 4 22 733
- 4 45 733
- 4 47 136
-
- 9 rows selected.
可以看到,结果完全符合,而且733*8+136=6000,这里一共使用了9个数据块。
那么,我们再在这个上面创建一个索引,虽然这个索引没有任何实际的使用价值,但是,可以方便我们看看索引块中可以保存多少个条目,同样,我们也规定pctfree为0。
- Piner@10gR2 8K>create index ind_b on a(a) pctfree 0;
-
- Index created.
然后,我们dump这个索引的结构
- Piner@10gR2 8K>select object_id from user_objects where object_name='IND_B';
-
- OBJECT_ID
- ----------
- 11490
-
- Piner@10gR2 8K>ALTER SESSION SET EVENTS 'immediate trace name TREEDUMP level 11490';
-
- Session altered.
查看dump结果
----- begin tree dump
branch: 0x40707a 4223098 (0: nrow: 10, level: 1)
leaf: 0x40707b 4223099 (-1: nrow: 615 rrow: 615)
leaf: 0x40707c 4223100 (0: nrow: 615 rrow: 615)
leaf: 0x40707d 4223101 (1: nrow: 615 rrow: 615)
leaf: 0x40707e 4223102 (2: nrow: 615 rrow: 615)
leaf: 0x40707f 4223103 (3: nrow: 615 rrow: 615)
leaf: 0x407080 4223104 (4: nrow: 615 rrow: 615)
leaf: 0x407081 4223105 (5: nrow: 615 rrow: 615)
leaf: 0x407082 4223106 (6: nrow: 615 rrow: 615)
leaf: 0x407083 4223107 (7: nrow: 615 rrow: 615)
leaf: 0x407084 4223108 (8: nrow: 465 rrow: 465)
----- end tree dump
我们可以看到,每个页块实际保存了615个条目,实际占用10个页块与1个branch块。其中,nrow表示节点中曾经有过的index entry数,rrow表示节点中当前的index entry数,从这两个值可以分析出index的空间使用效率。同样,我们可以看到615*9+465=6000。
前面我介绍了IBM的ds 8000系列与HDS的usp系列,这里我介绍最后一个高端系列,EMC的dmx3系列。

点这里看大图
DMX3是在早先SYMMTRIX 2000/3000系列上发展过来的,基本的体系结构没有改变,但是cache算法却做了一个比较大的改动,在以前的高端系统中,EMC是不采用写cache镜相技术的,而别的厂商基本都采用写cache镜相、读cache不镜相、读写cache分离这样的技术。那么,他们分别采用什么样的方式来保证写cache数据的正确性呢?
第一,因为cache肯定都是电池或者ups保护的,可以保证不掉电,或者是掉点以后系统还能维持一定时间,如果在一定时间内还没有供电,再把数据写到硬盘上,防止丢失。
第二,因为写cahce镜相保护数据也很简单,就是防止cache损坏,如果坏掉一个cache,还有另外一个,只要马上把坏的cache标记起来不用,镜相到新的地方即可。
第三,早先的SYMMTRIX系列采用了一种类似raid5的电极校验法来对cache进行校验,保证数据的可靠性,提高cache的利用率。但是,在新的dmx3中,又采用了一种完全不一样的镜相方法,读写全局cache全镜相,也就是说,如果100Gcache,有效cache是50G。
那么,“全局读写cache全镜相”与“读写cache分离,写cache镜相技术”谁好谁差,我不做评论,不过有些关键点大家还是要知道
1、全局cache中,读写是混在一起的,类似oracle的buffer,读可以直接在一个cache中命中。
2、读写分离中,如果一个要读的数据在写cache中存在,需要先从写cache拷贝到读cache,可能存在多份。
3、读cache一般远远大于写cache。
除了cache算法差别,我们从体系结构图中,还可以发现,emc一样以cache为核心,有前段卡有后端卡之分,如一个前端卡上有8个光纤接口,可以连接主机,一个后端卡上同样有8个后端口,用来连接磁盘。磁盘与hds一样采用光纤环路方式。
不过,emc前端与后端卡连接cache的方式与hds有很大差别,hds是通过内部交换方式连接,而EMC是直接连接,每个卡与每个cache板之间都有数据通道,所以,emc的连接方式又叫直连矩阵。
另外,emc的raid方式与hds也很不一样,hds的raid方式很死板,如raid10就支持2D+2D,其实所谓的4D+4D不过是把2个2D+2D简单的连接在一起。而emc中,raid方式比较奇特,如做10,他们先是把磁盘划成很多道(叫split,如8split,10split,16split等等),每一split可以镜相到一个磁盘。如一个磁盘有16个split,则3.8G/split,那么这个磁盘最多可以镜相到其它16块磁盘上,同样,其它的盘也可以交错镜相到这里,形成一个比较大的磁盘pool。之后,emc在每个split上做strip,形成metalun,这才是主机最后使用的LUN,对应到一个pv。
中国网络工程师侠客行大会将于5月19号-20号在杭州举行,19号晚上的酒吧聚会作为工程师大会的一个组成部分也将热力登场,我们诚挚的邀请各位技术高手来参加,也将邀请一些外部知名工程师一齐参与,在轻松愉快的氛围下自由沟通和交流。
被邀请的这些外部工程师可以被邀请参与19号20号白天的专题论坛。
场地:确定在卡萨布兰卡乡村酒吧。位于六公园湖畔,离大会的会场–人民大会堂较近,风格是ralax的乡村氛围。大致能容纳70-80人。组委会将制作海报、展架等进行现场环境和氛围布置。酒吧入场凭证也将附在大会门票里面一起邮寄给目标来宾。
谁有兴趣的,在技术方面有一定知名度的,报名给我
不知道有多少人清楚的知道,在Oracle中,如果一个复合索引,假定索引(a,b,c)三个字段,删除了(包括unused)其中一个字段,Oracle会怎么处理这个索引。同样,如果是约束,Oracle又怎么处理?
用oracle为例子,我又拿mysql做了一个对比,看看mysql是怎么处理这个问题的。我这里不讨论谁好谁差,只是希望大家知道其中的差别与细节而已。
我们先看Oracle的例子,我们创建一个表,然后在上面创建一个约束,创建一个索引:
- SQL 10G>create table test(a int,b int,c int);
- Table created.
-
- SQL 10G>alter table test add constraint pk_test primary key (a,b);
- Table altered.
-
- SQL 10G>create index ind_test on test(b,c);
- Index created.
然后,我们检查刚才创建的约束与索引
- SQL 10G>select t.constraint_name,c.constraint_type,t.column_name,t.position,c.status,c.validated
- 2 from user_cons_columns t,user_constraints c
- 3 where c.constraint_name=t.constraint_name
- 4 and c.constraint_type != 'C'
- 5 and t.table_name = 'TEST'
- 6 order by constraint_name,position;
-
- CONSTRAINT_NAME C COLUMN_NAME POSITION STATUS VALIDATED
- ---------------- - ------------ ---------- -------- -------------
- PK_TEST P A 1 ENABLED VALIDATED
- PK_TEST P B 2 ENABLED VALIDATED
-
- SQL 10G>select t.index_name,t.column_name,t.column_position,i.status
- 2 from user_ind_columns t,user_indexes i
- 3 where t.index_name=i.index_name
- 4 and t.table_name = 'TEST'
- 5* order by index_name,column_position
-
- INDEX_NAME COLUMN_NAME COLUMN_POSITION STATUS
- -------------- ------------ --------------- --------
- IND_TEST B 1 VALID
- IND_TEST C 2 VALID
现在,我们先删除索引上的字段,其实并没有物理删除,只是设置为unused:
- SQL 10G>ALTER TABLE test SET UNUSED (c);
- Table altered.
-
- SQL 10G>select t.index_name,t.column_name,t.column_position,i.status
- 2 from user_ind_columns t,user_indexes i
- 3 where t.index_name=i.index_name
- 4 and t.table_name = 'TEST'
- 5 order by index_name,column_position;
-
- no rows selected
发现了什么,索引也删除了。那我们再删除约束上的字段呢?
- SQL 10G>ALTER TABLE test SET UNUSED (b);
- ALTER TABLE test SET UNUSED (b)
- *
- ERROR at line 1:
- ORA-12991: column is referenced in a multi-column constraint
-
- SQL 10G>ALTER TABLE test SET UNUSED (b) CASCADE CONSTRAINTS;
-
- Table altered.
-
- SQL 10G>select t.constraint_name,c.constraint_type,t.column_name,t.position,c.status,c.validated
- 2 from user_cons_columns t,user_constraints c
- 3 where c.constraint_name=t.constraint_name
- 4 and c.constraint_type != 'C'
- 5 and t.table_name = 'TEST'
- 6 order by constraint_name,position;
-
- no rows selected
我们可以看到,正常的删除会报一个错误,如果我们指定了cascade,将会把对应的约束也删除。
我们看完了Oracle的处理过程,再看看mysql是这么处理删除索引上字段这个事情的
- mysql> create table test(a int,b int,c int);
- Query OK, 0 rows affected (0.72 sec)
-
- mysql> alter table test add primary key(a,b);
- Query OK, 0 rows affected (0.27 sec)
- Records: 0 Duplicates: 0 Warnings: 0
-
- mysql> create index ind_test on test(b,c);
- Query OK, 0 rows affected (0.32 sec)
- Records: 0 Duplicates: 0 Warnings: 0
我们执行同样的操作,先删除复合索引中的一个字段,然后删除约束中的一个字段。
- mysql> alter table test drop c;
- Query OK, 0 rows affected (0.58 sec)
- Records: 0 Duplicates: 0 Warnings: 0
-
- mysql> show index from test;
- +-------+------------+----------+--------------+-------------+-----------+
- | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation |
- +-------+------------+----------+--------------+-------------+-----------+
- | test | 0 | PRIMARY | 1 | a | A |
- | test | 0 | PRIMARY | 2 | b | A |
- | test | 1 | ind_test | 1 | b | A |
- +-------+------------+----------+--------------+-------------+-----------+
- 3 rows in set (0.06 sec)
-
- mysql> alter table test drop b;
- Query OK, 0 rows affected (0.28 sec)
- Records: 0 Duplicates: 0 Warnings: 0
-
- mysql> show index from test;
- +-------+------------+----------+--------------+-------------+-----------+
- | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation |
- +-------+------------+----------+--------------+-------------+-----------+
- | test | 0 | PRIMARY | 1 | a | A |
- +-------+------------+----------+--------------+-------------+-----------+
- 1 row in set (0.03 sec)
可以看到,mysql的处理方式是有差别的,mysql仅仅是把字段从索引中拿掉,而不是删除该索引。
本文的意思,就是想提醒大家,平常在做columns删除的时候,包括unused,一定要小心,是否有复合索引包含了该字段,否则,一不小心把索引删除了,可能将引发大的错误。
一直很喜欢看中土时代的战争,没有枪炮的冷战争比现在的战争更扣人心弦,更让人觉得战争的悲烈与人性的善恶。这次的斯巴达之战,又让我想起《魔戒》,基本是同样的战争,《魔戒》却没有如此真实出色到了可怕的地步!
历史背景:
公元前480年,波斯王薛西斯率50万铁骑攻向希腊。数月之战,希腊已沦陷过半,骁勇善战的斯巴达国王列奥尼达,被众城邦选出担任守军统帅。英明神武的他力排众议,亲率300斯巴达勇士死守希腊中部的门户–温泉关。此一战力量悬殊,然而数万波斯精锐强兵,却不能攻下列奥尼达与300壮士用性命筑起的防守。直至最后一位斯巴达壮士血溅当场,波斯才最终胜利,然而300壮士的勇猛刚强,却为后世永远铭记。
那是公元前的世纪,那是一场天地变色、日月无光的战争,那是300对抗万人的战役,而若非中途有叛徒出卖,或者天时地利再多一点点,也许一切都会改变。但即便以悲剧告终,斯巴达勇士的“荣誉,责任,光荣,作战和胜利”的信条依然感天动地。希腊旧国的大气磅礴,风格独特的《罪恶之城》漫画家弗兰克·米勒的作品改编,冷兵器时代的温泉关300壮士保卫战传奇,誓将英雄主义和悲壮色彩涂画到极致!
国王,勇敢的抗争者

皇后,男人身后的支撑者

离别

千万飞箭遮挡住了太阳的光辉

敌人的死亡战士

维美的女巫

被包围

死亡

死亡


点这里查看大图
CHA(channel adapter,9970/9980也叫chip),usp的前端卡,上面可以有多个(如8个)前端口,是连接主机与存储阵列cache的通道,一般默认是2Gb通道,现在可以配置到4Gb通道。
DKA(disk adapter,9970/9980叫acp),usp的后端卡,上面一般可以有多个(如8个)后端口,连接阵列的磁盘控制器(DKU)与cache,现在还是2Gb的后端光纤环路通道。在USP的后端,磁盘放在DKU的HDU中,每个HDU最多可以放16个硬盘,而USP的一个环路最多可以接3个HDU,则一个环路最多可以是48块硬盘。
注意,所谓前端卡与后端卡,只是叫法上的分别以及功能上的差别,物理位置可以是在一起的。另外,前端卡与后端卡总是成对出现。
USP最核心的就是cache了,有控制cache与数据cache之分,控制cache也可以叫sheard cache,保存着阵列的配置信息以及数据cache的地址信息,在前端口过来寻找数据的时候,会先经过这里,如果能找到,则去数据cache,如果找不到,则可能需要磁盘读取。
数据cache也叫标准cache,如果采用4G的内存标准,最大可以到128G,而如果采用8G的内存标准,可以到256G,数据cache是整个阵列的核心所在。usp的内存cache最小单元叫segment,固定为64K,最小使用单元叫slot,在有些版本=4个segment=256K,有些版本则=1个segment=64K,因为这个slot中在磁盘上的数据必须是连续的,所以,太大的slot可能对离散度高的oltp应用不是太适合。
前端卡与后端卡连接cache,是要经过一个交换机的(Crossbar Switch),交换机最小可以是2个(冗余需要),最大可以是4个,因为所有的数据都要经过前端卡/后端卡,再经过交换机到cache,所以switch也是一个核心部件,而且数据流量会很大。
USP跟IBM的8000系列(见我的前一篇介绍)的构架已经完全不一样了,如果说IBM8000系列还是在对称体系结构上发展,依靠其强大的主机优势来保证存储的高可用性,那么USP已经发展到多模块化设计了,以cache为核心,并且cpu集成在前端与后端模块中,操作系统则集成在微码之中。
cache的命中率是一个阵列的响应速度以及处理能力的一个重要体现,因为cache命中的话,返回速度是非常快的,而不命中的话,就需要去磁盘寻找数据,可能会比较慢。而cache的命中率,与应用访问规则,数据分布规则都有很大关系。
不要太迷信厂家给出的IOPS值,他们一般都是在cache命中的基础上算出来的。
见Bug No. 4171597 or 4230721,影响版本10.1.0.3.0以后,最晕的是 Fixed in Product Version: No Data。可见,这个问题,拖了这么长时间了,oracle居然没有一个解决办法。。。
还害得我找问题在哪里,以为10g有什么改变了,结果文档就是这么写的,一点没有变化,再查metalink,ft。
- /*Create a test table */
- SQL>CREATE TABLE T ( THEVAL NUMBER(12))
- /*Create a before insert trigger on the table which uses ora_sql_txt function. */
-
- SQL>CREATE OR REPLACE TRIGGER T_TRIGGER
- BEFORE INSERT ON T
- DECLARE
- sql_text ora_name_list_t;
- stmt VARCHAR2(32300);
- n NUMBER;
- BEGIN
- stmt := NULL;
- dbms_output.put_line( 'The statement that caused me to fire is:' );
- n := ora_sql_txt(sql_text);
- FOR i IN 1..n LOOP
- BEGIN
- stmt := stmt || sql_text(i);
- END;
- END LOOP;
- dbms_output.put_line( stmt );
- END;
- /
Insert values in the table
insert into t values(123);
This fails with :
insert into t values(123)
*
ERROR at line 1:
ORA-06502: PL/SQL: numeric or value error
ORA-06512: at "SCOTT.T_TRIGGER", line 9
ORA-04088: error during execution of trigger 'SCOTT.T_TRIGGER'
As variable n contains NULL
a) 9.2.0.6
--> ora_sql_txt() returns 1
sql_text(1) returns "insert into t values(123)"
b) 10.1.0.3 & 10.2
--> ora_sql_text() returns null
sql_text collection is uninitialised
接下来的时间,我给大家介绍几款高端存储的体系结构,包括IBM的DS8000系列,HDS的USP系列(有些厂商也叫9990系列)以及EMC的DMX3系列。我不评价其好坏,只是对内部结构做一些简单的介绍。
先看DS8000的一个体系结构图:

我们可以看到,DS8000沿用了一个比较典型的对称处理结构,利用其高性能与高可靠的P570作为阵列的双控制器,两台570互相做冗余,形成一个双active的HA结构。在处理器上也是采用其先进的Power5系列的CPU。另外,因为p系列的570支持逻辑分区(LPAR),所以,8000系列的存储也支持逻辑分区。
在结构图的最上方,就是阵列负责对外的光纤卡通道或者是主机适配器,一般高端阵列中都叫前端卡(前端口),支持2Gb带宽(bit),好象到目前为止,8000好象还不支持4Gb的对外接口(如果支持,应当也是迟早的事情,因为其它厂商都已经支持了)。
在结构图的最下方,就是磁盘适配器或者是RIAD适配器接口,一般高端阵列这里叫后端卡(后端口),IBM与其它存储这里有点不一样的是,它没有采用典型的环路设计,而是采用了交换设计,也就是说,后端卡通过内部交换机其实是可以访问到每一块磁盘的。
因为IBM采用了570作为8000系列的核心控制器,所以阵列的cpu与内存也都是在570内部控制的,而且阵列的可靠性是需要570来保证的,IBM希望凭借其570的稳定性与强悍的处理能力带给存储高的可用性。
最近被这个网站耗费了太多时间去了,所以新书的书写进度有所减慢,不过,不管怎么样,到今天为止,也完成了13章的草稿了,基本上每周也在一章到两章。
按照我的性格,这个草稿离成型,还远着呢,因为既然决定要写,就一定要写好,所以后期的审稿,定型的时间肯定也不短。不过,网站到今天为止,算是告一段落,等书本完成以后再去改版吧。
另外,这个网站最终也是为我的新书服务的,如提供代码支持,提供在线支持等等,所以大家也要多支持。现在完成的13章,就是我的第一大篇与第二大篇:系统设计与系统管理,从篇幅来看,这两大篇占了总篇幅的一半。
