2008年12月31日星期三

2008年个人总结

其实2008这一年对我来说还是挺重要的,今年的突破主要在以下几个方面:

1 ORACLE数据库上大有提高,对ORACLE数据库整体的认识在今年上了一个台阶,特别是对数据库体系结构比较清楚了,记得年前的时候,别人让我解释9i和10g架构上的差异,我都不知道如何回答。经过一年的刻苦修炼,我现在差不多能回答这个问题了。来年希望在数据库管理方面深入一下,并且在数据仓库,数据挖掘,OLAP系统方面能有所提高。

2 JAVA方面本年度提高不大,最大的进步是对RMI,CORBA,EJB的原理基本了解一点了, 还有对于基本的设计思想和设计模式算是有点感悟。希望来年把spring好好弄弄,熟悉熟悉功能,看看源码,希望提高比较大。

3 web展示层,应该是我今年提高最大方面之一,另一个是linux操作系统。从以前写根本并不会写页面到现在对 页面展示层还算比较了解,自我觉得提高很大。明年的目标就是结合spring再有点提高(嘿嘿,毕竟我的目标不是做前台)。

4 linux操作系统,给我带来的提高也很大。接触linux是因为ORACLE现在的很多架构转到linux上来了,得跟上技术的潮流。从刚开始接触,到现在已经有将近1年时间了,自己的感觉是对linux操作系统的一些基本的东西有所了解了,从开始的一点也不懂,到现在也能跟人砍一砍这个东西,进步还是蛮大的。希望来年在shell编程和服务器配置方面更进一步。

5 开源数据库和c语言,选择postgresql作为学习和研究的对象,主要是它号称基本都是c语言写的代码,而我自己c语言编程水平的提高很大一部分也来自于postgresql。当然今年只是学习了PG的SQL语言解析器部分的代码,但是仅仅这部分代码就已经让我进步很大了。 今年学会的C语言方面的知识有gcc,gdb,make,autoconfig,lex,yacc(bison),还有linux上的一大堆c语言库函数和API,回头看看进步神速,颇感欣慰。希望来年继续postgresql的学习和研究。

大概算算,今年学了很多东西,也浪费了很多时间,来年可真得抓紧时间,好好工作,努力学习了。

2008年12月28日星期日

ORACLE动态采样初探

一直以来,我都以为ORACLE的动态采样只有在没有统计信息的时候才有用,今天看了TOM老人家得一篇大作,受益匪浅啊。

dynamic sampling从ORACLE9iR2开始就可以使用了,CBO在执行hard parse的时候使用动态采样来收集相关表的统计信息同时来纠正自己的猜测数据。这个过程只在hard parse中产生,并且被用来生成更加准确的统计信息以供CBO使用,因此被称作动态采样。

优化器使用很多输入参数来产生合适的执行计划,比如它使用表上的约束,系统统计信息,查询涉及的相关对象的统计信息。优化器使用相应的统计信息来估算基数,而基数正是成本计算的最重要的变量。也可以说基数估算的正确与否,直接影响到执行计划的选择。这就是引入动态采样的直接动机--帮助优化器估算正确的基数,从而得到正确的执行计划。

动态采样提供了11个级别可以供使用(从0到10),后面我会详细解释每一个级别。ORACLE 9i R2的默认动态采样级别是1,而10g 11g的默认级别是2。

1 使用动态采样的方法
数据库级别可以调整optimizer_dynamic_sampling参数,会话级别可以使用alter session
在查询里可以使用dynamic_sampling提示

2 几个例子
针对没有统计信息的表:
SQL> create table t
2 as
3 select owner, object_type
4 from all_objects
5 /
Table created.

SQL> select count(*) from t;

COUNT(*)
------------------------
68076

下面的查询禁用了动态采样:
SQL> set autotrace traceonly explain
SQL> select /*+ dynamic_sampling(t 0) */ * from t;

Execution Plan
------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16010 | 437K| 55 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| T | 16010 | 437K| 55 (0)| 00:00:01 |
--------------------------------------------------------------------------
下面的查询启用了动态采样:
SQL> select * from t;

Execution Plan
------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 77871 | 2129K| 56 (2)| 00:00:01 |
| 1 | TABLE ACCESS FULL| T | 77871 | 2129K| 56 (2)| 00:00:01 |
--------------------------------------------------------------------------

Note
------------------------------------------
- dynamic sampling used for this statement

请注意,启用动态采样后的执行计划的基数(77871)更加接近实际的行数(68076),因此这个执行计划更可靠一点。

SQL>set autot off

当然也可能估算出差别很大的基数:

SQL> delete from t;
68076 rows deleted.

SQL> commit;
Commit complete.

SQL> set autotrace traceonly explain
禁用动态采样:
SQL> select /*+ dynamic_sampling(t 0) */ * from t;

Execution Plan
------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16010 | 437K| 55 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| T | 16010 | 437K| 55 (0)| 00:00:01 |
--------------------------------------------------------------------------
启用动态采样:
SQL> select * from t;

Execution Plan
-----------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 28 | 55 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| T | 1 | 28 | 5 (0)| 00:00:01 |
--------------------------------------------------------------------------

Note
---------------------------------------
- dynamic sampling used for this statement
实际上,表里现在没有数据,但是由于使用了delete而不是truncate,数据库并没有重置HWM,因此数据库猜错了结果,启用动态采样的基数要远远强于没有采样的。

以上两个例子,都是在没有对表进行统计的时候得到的,那么在有统计信息的情况下,动态采样还能有作用么?请考虑以下的例子,假设EMP表里有两个字段,birth_day date 和星座(varchar2),假设其中有1440000行数据(当然数据库表设计没有遵循第二范式)。在这种情况下,查询出生在1月份,并且星座是天平座的人,CBO会估算出多少行呢?答案是1W,但是实际上一行都不会返回,在这种数据库表设计有严重问题的时候,动态采样再一次向我们展示了其魅力所在。

SQL> create table t
2 as select decode( mod(rownum,2), 0, 'N', 'Y' ) flag1,
3 decode( mod(rownum,2), 0, 'Y', 'N' ) flag2, a.*
4 from all_objects a
5 /
Table created.

SQL > create index t_idx on t(flag1,flag2);
Index created.

SQL > begin
2 dbms_stats.gather_table_stats
3 ( user, 'T',
4 method_opt=>'for all indexed columns size 254' );
5 end;
6 /
PL/SQL procedure successfully completed.
这里做了统计,有直方图了,以下是一些统计信息:

SQL> select num_rows, num_rows/2,
num_rows/2/2 from user_tables
where table_name = 'T';

NUM_ROWS NUM_ROWS/2 NUM_ROWS/2/2
-------- ---------- ------------
68076 34038 17019

SQL> set autotrace traceonly explain
SQL> select * from t where flag1='N';

Execution Plan
------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 33479 | 3432K| 292 (1)| 00:00:04 |
|* 1 | TABLE ACCESS FULL| T | 33479 | 3432K| 292 (1)| 00:00:04 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("FLAG1"='N')

SQL> select * from t where flag2='N';

Execution Plan
----------------------------
Plan hash value: 1601196873

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 34597 | 3547K| 292 (1)| 00:00:04 |
|* 1 | TABLE ACCESS FULL| T | 34597 | 3547K| 292 (1)| 00:00:04 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("FLAG2"='N')

前两个执行计划都是准确的,会返回34597行数据,大概是表里数据的一半。接着执行以下查询:

SQL> select * from t where flag1 = 'N' and flag2 = 'N';

Execution Plan
----------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 17014 | 1744K| 292 (1)| 00:00:04 |
|* 1 | TABLE ACCESS FULL| T | 17014 | 1744K| 292 (1)| 00:00:04 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
----------------------------------------------------

1 - filter("FLAG1" = 'N' AND "FLAG2" = 'N')

这时会返回表里四分之一左右的数据,但是实际上应该是一行数据都没有,并且由于错误的基数估算导致错误的执行计划。再看看下面的这个查询,强制CBO进行动态采样。

SQL> select /*+ dynamic_sampling(t 3) */ * from t where flag1 = 'N' and flag2 = 'N';

Execution Plan
-----------------------------
Plan hash value: 470836197

------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 630 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T | 6 | 630 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | T_IDX | 6 | | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
----------------------------------------------------

2 - access("FLAG1"='N' AND "FLAG2"='N')

CBO估计会返回6行数据,并且进行了索引范围扫描,成本也很低,总体来说这个执行计划还令人满意。至于CBO估算出来的基数为什么是6,而不是0,这个解释起来比较复杂,在此就不详述了。

3 CBO动态采样级别:
level 0:不使用动态采样
level 1:在以下情况进行采样所有查询涉及的表:(1)查询语句里至少有一个未anylze过的表;(2)unanalyzed table连接到其他表或者出现在子查询中或者在不可合并的视图中;(3)这个表上没索引;(4)表数据块比进行动态采样的数据块多。
level 2:应用动态采样到所有unanalyzed table,表数据块至少是动态采样块的2倍。
level 3:满足level2的所有条件,并且外加所有表的标准选择率可以使用动态采样的谓词来计算
level 4:满足level3的所有标准,并且表上有参照到多个列的单个谓词。
level 5,6,7,8 and 9:满足前一个级别的所有标准,使用使用2,4,8,32,128倍的默认动态采样率。
level 10:满足level9的所有标注,并且对表里的所有块进行动态采样。

当然,前面的关于11个level的论述我可能翻译的不是很准确,有兴趣的话可以参考oracle database performance tuning.

4 何时使用动态采样以及如何使用:
(1) OLAP系统中可以大量使用
(2) 尽量避免在OLTP系统中使用
(3) 如果想在OLTP系统中使用的话,建议通过sql profiles来使用,遗憾的是从10g以后才能使用这个功能。原理上来说,SQL profiles有点像收集统计数据的查询和存储信息的字典,因此降低动态抽样的时间和hard parse的时间。

欢迎有问题一起讨论。tom关于这个问题有更加详细的论述,如果有兴趣的话,可以参考。

2008年12月25日星期四

北林为什么强于哈佛大学?

从别人那里转过来的。

1.北林是我党领导下的社会主义国家公立大学,哈佛大学是没落的资本主义国家的私立大学.两种国家的性质决定了北林地位必然高于哈佛大学,这是谁也无法抹杀的.

2.学校占地规模决定了北林强于哈佛大学,北林两个校区共占地面积约为12289亩,而哈佛大学总共占地才500亩,小的可怜.

3.学生人数上哈佛大学再次告负.目前,北林学生人数达2万多人,而哈佛大学才1万多,相比北林少了1万人.为什么会少了1万人?!不正是说明世界人民心目中喜欢北林的人多余喜欢哈佛大学的人吗?

4. 学生的素质.北林的学生不但全部流利掌握中文,并且大多数通过了大学英语四六级考试.而哈佛的学生除了会说英语,懂中文的人实在少之又少.同样,作为学校教学和科研的主力军,北林有很多老师既有中国教育背景,又有国外留学背景.而哈佛大学,全校竟找不出几个有完整中国教育背景的老师来.

5.两个学校不同的校训预示了两个学校未来的命运.北林的校训是"知山知水,树木树人" ,读起来琅琅上口,一种朝气蓬勃的社会主义优越感,而哈佛大学的校训是"以柏拉图为友,以亚历士多德为友,更要以真理为友",听起来让人有同性恋的感觉,而且还有一种暮气沉沉的感觉.

6. 另外结合本人的个人经历,我更深深体会到北林作为一所世界级名校在所有学子心目中的崇高地位.我本人作为一名美貌与智慧并存,温柔而不失刚毅的优秀有为青年,除了天资聪慧,秉赋过人外,更是每日五更勤奋苦读,经过不懈努力,终于有幸被北林录取.而哈佛大学,在本人及周边所有认识的人的高考经历中,没有一个志愿填报过这所学校.

综上所述,我可以很自豪的说:"我选择了北林,无怨无悔,因为它就是世界上最好的大学."


我们承认哈佛大学在世界范围内的名气大于北林,但这是由于美帝国主义的媒体掌握着话语权,有意压制北林的结果,我们相信通过全校校友在网络上的宣传,我们一定可以让全世界人民认识并喜欢.

2008年12月22日星期一

char型数据和绑定变量

众所周之,在ORACLE数据库中使用绑定变量是比较好的作法。今天一个同事在使用绑定变量的时候碰到一点问题,花了一点时间来解决,这个问题应该很容易碰见,对一般程序员来说挺有挑战性的。

在ORACLE数据库中做以下操作:
create table char_test(tid char(2),nid number(2));
insert into char_test select to_char(rownum),rownum from all_objects where rownum<=10;
commit;

然后分别运行以下语句:
select * from char_test where tid='1';(可以查出来值)
select * from char_test where tid='10';(也可以查出来值)
select * from char_test where tid=:ptid;

在这里将绑定变量ptid设为'1','10',可以看到前者没有查出来值,而后者有值。
原因主要是数据库在做针对字面量的查询时,做了转换,将'1',转换成了占char2,因此前者的出来正确结果,而使用绑定变量的时候,数据库不会做这个转换,因此就查不出来值。当然在都是查询tid='10'的时候,就没有这个问题。

我们也可以查看char(2)和varchar2(2)的区别:
select dump('1') from dual;
select tid from char_test where nid=1;

解决方法是在使用绑定变量并且数据类型是char的时候,将查询语句改写一下:
select * from char_test where tid=rpad(:ptid,2);

2008年12月21日星期日

apex--ORACLE的云计算开发工具

前两天看见ORACLE的APEX(application express可以在OTN直接使用了,中文应该叫快捷应用吧),介绍APEX的文章在OTN上有的是,有兴趣的话可以去看看。值得一提的是可能ORACLE会把APEX作为自己云计算的开发工具来使用,看来ORACLE也想在云计算市场上分一杯羹。话说回来,其实网格计算就可以看做是一种低空云。可以通过http://apex.oracle.com来使用使用该工具,注册后ORACLE会免费提供10M的空间供你存储数据和应用程序。

APEX是一个及其傻瓜的工具,比ACCESS更傻瓜,不过功能很强大,可以作出很多应用来,具体效果看图就知道了。
以下是我抓的图片(用APEX做的小例子):







不知各位老大如果用JAVA或者.NET要用多长时间才能把这么大的一个网站做完,如果是我的话,至少需要2周,当然实在不包括CSS的情况下。可是使用APEX只需要2天就可以做完了,而且界面美观,没什么bug,性能应该也还可以,中小型应用应该够了,毕竟这个东西支撑起了asktom。

CBO基础笔记

cursor_sharing 用于控制字面量替换
db_file_multiblock_read_count DFMRC用于在全表扫描和索引全扫描时计算成本
optimizer_index_caching 索引缓存率
optimizer_index_cost_adj 用于调整索引访问的成本
PGA_AGGREGATE_TARGET 用于控制hash和排序区的大小
optimizer_mode all_rows,first_rows_n,first_rows
star_transformation_enabled 是否允许星型转换
v$sql_cs_statistics 保存绑定变量的执行信息
低索引集群因子说明相近的锁银行集中少量列上

v$sql_plan,v$sql_plan_statistics和v$sql_plan_statistics_all可知plan
select plan_table_output from table(dbms_xplan.display());
select * from table(dbms_xplan.display('plan_table',null,'All'));
alter session set db_file_multiblock_read_count=16;

闪回查询前导列如何使用
object_id,object_value与对象表相关
全表扫描的成本计算公式:
1+高水位线下的块的数目/调整后的参数
单表选择率算法:
(numrows-numnull)/num_rows
alter session set "_optimizer_cost_model"=io;

begin
dbms_stats.gather_table_stats(user,'t1',estimate_percent=>null,method_opt=>'for all collumns size 120');
end;/

sql trace:
1 alter session set tracefile_identifier='fangyuan';
2 alter session set sql_trace=true;
do sth
3 alter session set sql_trace=false;
4 show parameter use_dump_dest
5 tkprof
or select spid from v$session s, v$precess p
where s.paddr=p.addr
and s.username=user
and module='SQL*PLUS';

10053 event:
alter session set event '10053 trace name context forever';
alter session set event '10053 trace name context off';

查询计划中的view_pushed predicate表示进行了谓词推进
优化器默认使用嵌套循环来处理anti_join,但是如果使用merge_aj,hash_aj,nl_aj的话,优化器能进行相应的转换。
优化器默认使用嵌套循环来处理semi_join,但是如果使用merge_sj,hash_sj,nl_sj的话,优化器能进行相应的转换。
但是在11g并且有统计信息的情况下,优化器不一定会使用NL来默认处理上述两种情况

内联视图
select p.pname ,c1_sum1,c2_sum2 from p,
(select id,sum(*) c1_sum1 from s1 group by id) s1,
(select id,sum(*) c2_sum2 from s2 group by id) s2
where p.id=s1.id and p.id=s2.id

标量子查询
select p.pname,
(select sum(p1) c1_sum1 from s1 where s1.id=p.id) c1_sum1,
(select sum(p2) c2_sum2 from s2 where s2.id=p.id) c2_sum2
from p

将子查询转换为内联视图是比较好的做法
提高CBO对于not in 的选择率精度,必须保证连接操作两端都不为NULL,负责可能出现错误结果

star提示可以用于进行星型连接
alter session set star_transformation_enabled=...
B树索引访问成本:
blevel+ceiling(leaf_blocks*effectvie index sel)+ceiling(clustering_factor+effectvie index sel)

连接操作选择率公式:
sel=(
(num_rows(t1)-num_nulls(t1,c1))/num_rows(t1)
*(num_rows(t2)-num_nulls(t2,c2))/num_rows(t2)
/greater(num_distinct(t1,c1),num_distinct(t2,c2))
)
基数公式:
card=
sel*filtered card(t1)*filtered card(t2)
当过滤谓词仅仅出现在一侧时,需要使用另一侧的distinct来替代最大值。
如果是多列连接,需要将多个连接条件的选择率相乘,如果在10g以后,可能会使用两个相同的条件相乘,并且选择较大的选择率
如果是范围连接,优化器使用类似与绑定变量的选择率处理
如果是不等值连接,选择率为1-等值连接的选择率

优化器在处理不等值连接且条件间为or时可能会有错误,如果加入no_expand提示就可以得到正确的结果,或者讲等值连接的条件分别写成查询最后union all.

在有重复史书的时候,计算成本总为1000k或者1,在有直方图的情况下,某些查询的成本会好一些,但是在数据不重叠时候,直方图会带来新的问题。
在建立直方图的时候,只有size远远大于列中不同的值的数量的时候,才可能使用频率直方图,否则都会使用高度均衡直方图。

如果在连接条件上有一个过滤条件,CBO可能会做闭包传递,导致过滤条件不可用以及忽略有过滤条件的连接条件,如果连接条件两边都有过滤条件,则有可能是成本和基数的差异都很大。

在进行多表连接时,需要将选择率相乘,而计算公式中的参数直接来自于语句中的谓词,在执行复杂查询的时候,连接条件非常重要

在估算code-业务表这种类型的连接查询时,如果讲很多的代码并入一张CODE表中,并且添加type列来进行区分,那么优化器将有可能进行错误的成本计算,即使建立了直方图也有这个问题。解决方法是对code表按type进行分区。

绑定变量的选择率为:density or 1/num_distinct
optimizer_index_caching设为75在OLTP系统中比较合理

嵌套循环的成本:
从第一个表取数据的成本+从第一个表得到结果的基数*对第二个表访问一次的成本
其实说白了就是嵌套循环的原理

hash_join的成本:
(探查遍数+1)*ceiling(大表数据块大小/调整IO后的单表值)+ceiling(小表表数据块大小/调整IO后的单表值)+hash_join前的成本和
其实就是hash_join的原理

10104事件用于查看hash_join的细节

dbms_lock.request(1,dbms_lock.xmode,release_on_commit=>true);
用于加排他锁,这时在其他会话中执行童谣的代码时就会等待,可以用这个方法来模拟并发。

begin
execute immediate 'purge recylebin';
end;

alter session set work_size_policy=mamual;
later session set hash_area_size=;

event 10032用于报告排序时系统内部相关活动的统计信息
event 10046 level 8用于记录等待状态
event 10033列出并发的IO细节
alter session flush shared_pool;
alter session flush buffer_cache;

一般来说,增加sort_area_size可以改善排序,但也可以增加对CPU的占用
在使用union,minus,intersect操作时,不要使用distinct关键字,否则计算card,cost有错误

10053事件的追踪报告通常包括:
绑定变量
参数设定
查询块
基础统计信息
完备性检查
一般执行计划:
在这里会衡量各个访问路径和顺序,但是在表的数量较多(4个表就有24种可能,4个表通常ORACLE只会估算13-16个路径)的情况下,ORACLE不会遍历所有的路径,这也是SQL需要优化的原因。

2008年12月18日星期四

ORACLE REA --很牛!


这两天刚看见ORACLE RICH ENTERPRISE APPLICATIONS,这个东西可以在OTN上直接看到,感觉做的很强,里面有一些哥哥姐姐们的视频,可以用来学习ORACLE在JAVA方面的一些技术,主要是ADF。刚看完他们的视频,感觉jdeveloper这个工具的功能越来越强了,毕竟是ORACLE精心打造的。

可以点击这里去学习一下。

ORACLE concepts笔记

commit comment参考sql ref
reco进程在两段式提交里的作用 参考concepts 114
自治plsql块 查pl sql ref
表大于255列的情况可以参考 concepts 121
数据行p122

表压缩部分:
表压缩只用于只读环境
表压缩与重定义的包相关 dbms_redefinition
null值在b树索引不会被计入索引,在集群列上的索引或着位图索引则会计入索引

什么情况下使用临时表需要再查查
查询视图也可以使用绑定变量
to_char,to_date,to_number三个函数的NLS参数需要注意,在视图定义的时候也可以指定。

外部表和sql loader作为数据加载的工具很好,需要看看。
escape处理转义字符
likec是针对国家字符集的,比如NCHAR
any和some是要其中任何一个,但是二者也有差别,需要再查查
all是所有的

视图也可以增删改,但是应该如何写DDL
物化视图可以用于分布式环境下的数据复制,也可以用于移动环境下的数据同步,也就是说在ETL操作中很好用
查到的资料说多维物化视图可以提供对多维数据的访问能力,但是OC上说MV伤的约束也可以做,具体怎么做?
物化视图的日志用于记录基表的变更信息
dimension用于数据仓库表示层次关系,结合物化视图学习

可见和不可见索引
基于函数的索引的deterministic关键字
将索引和表置于不同的表空间,OC里的解释似乎有问题
索引组指标上的位图索引和普通表的区别在于,由于索引组指标没有真正的物理ROWID,因此用一个映射表来存储ROWID和键值,之后对映射表做索引
关于OC p152的UROWID的问题
关于应用域索引的问题可以查阅oracle db cartridge developer's guide
hash cluster适用于大量返回值的等值查询

查看系统对象依赖:DBA_DEPENDENCIES,DEPTREE.....
远端程序调用的依赖控制,时间戳检查,特征检查(仅用于RPC)
设置会话级别的RPC:
alter session set remote_dependencies_mode={signature|timestamp}
DB级别:alter system
建议:
1 server端PLSQL用户使用timestamp
2 如果在分布式环境下避免不必要的重编译,应该使用signature mode
3 客户端应该为signature mode,因为,其一不需要重新编译过程,就建立新的应用程序,其二,不影响客户端就可以进行server端升级
4 使用signature mode是,在package中间或者末尾添加新函数或者新过程是会重编译
一些对象被重编译后,共享池内的sql会被标记为无效,下次进行重解析

数据字典保护:07_dictionary_accessibility=false,只允许sys用户访问
v$result_cache...用于查看结果缓存
使用result cache提示来缓存结果集
设置result_cache_mode来控制缓存的结果集用于所有查询还是仅用于带提示的查询
v$bgprocess视图获取后台进程的信息
job$视图查询job信息
private_sga可以限制共享服务器模式下会话私有数据的大小
数据库初始化参数shared_servers和max_shared_servers用于控制共享服务器模式下的连接数

使用ORACLE内部的连接池(池化模式)
以sysdba登陆:
exec dbms_connection_pool.start_pool('sys_default_connection_pool');
tns中的连接字符串
...(server=pooled)
purity可以指定重用池化session/new session

listener这块还有问题,需要再看看书

alter session用于改变用户会话参数
alter system用于改变实例参数
alter database才是改变数据库参数

contention争用的问题怎么解决?

2008年12月16日星期二

in vs exists

in和exists的问题我们都谈论了好多年了,今天又有课长提到这个问题,并且宣称in没有exists高效。一直以来大家都这么想,也就是说in比较慢。因此大家都用exists来替代in,其实这是一种不一定正确的做法。

在很多情况下,exists是没有办法替代in的,比如select * from emp where deptno in(10,20);我就看不出来这种条件下exists怎么能替代in。再者,如果exists真的能完全替代in的话,那么in为什么不从最新的sql 2003标准里除名呢。要知道制定这个标准的是数据库领域内的专家。

当然了,在大部分情况下,是可以使用exists来替代in的,同理也可以使用not exists来替代not in。但是即使在能替代的情况下,也不能保证二者究竟谁更高效一些。他们的效率问题,tom老人家早有论述,原话如下:

Well, the two are processed very very differently.

Select * from T1 where x in ( select y from T2 )

is typically processed as:

select *
from t1, ( select distinct y from t2 ) t2
where t1.x = t2.y;

The subquery is evaluated, distinct'ed, indexed (or hashed or sorted) and then joined to
the original table -- typically.


As opposed to

select * from t1 where exists ( select null from t2 where y = x )

That is processed more like:


for x in ( select * from t1 )
loop
if ( exists ( select null from t2 where y = x.x )
then
OUTPUT THE RECORD
end if
end loop

It always results in a full scan of T1 whereas the first query can make use of an index
on T1(x).


So, when is where exists appropriate and in appropriate?

Lets say the result of the subquery
( select y from T2 )

is "huge" and takes a long time. But the table T1 is relatively small and executing (
select null from t2 where y = x.x ) is very very fast (nice index on t2(y)). Then the
exists will be faster as the time to full scan T1 and do the index probe into T2 could be
less then the time to simply full scan T2 to build the subquery we need to distinct on.


Lets say the result of the subquery is small -- then IN is typicaly more appropriate.


If both the subquery and the outer table are huge -- either might work as well as the
other -- depends on the indexes and other factors.

英语不错的同学可以看看,翻译过来的意思是:in实际上是拿到内层查询的所有值,然后在去外层查找对应的值,得到查询结果;而exists实际上是拿到内层的每一个值都去外层查找一次。因此in更适合处理小结果集合的内层查询,而exists更适合处理大结果的内层查询。

以下是他给的例子:
rem create table big as select * from all_objects;
rem insert /*+ append */ into big select * from big;
rem commit;
rem insert /*+ append */ into big select * from big;
rem commit;
rem insert /*+ append */ into big select * from big;
rem create index big_idx on big(object_id);
rem
rem
rem create table small as select * from all_objects where rownum < 100;
rem create index small_idx on small(object_id);
rem
rem analyze table big compute statistics
rem for table
rem for all indexes
rem for all indexed columns
rem /
rem analyze table small compute statistics
rem for table
rem for all indexes
rem for all indexed columns
rem /

so, small has 99 rows, big has 133,000+

select count(subobject_name)
from big
where object_id in ( select object_id from small )

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.01 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.02 0.02 0 993 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.03 0.03 0 993 0 1

Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
1 SORT (AGGREGATE)
792 MERGE JOIN
100 SORT (JOIN)
100 VIEW OF 'VW_NSO_1'
99 SORT (UNIQUE)
792 INDEX GOAL: ANALYZED (FULL SCAN) OF 'SMALL_IDX'
(NON-UNIQUE)
891 SORT (JOIN)
0 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'BIG'


versus:

select count(subobject_name)
from big
where exists ( select null from small where small.object_id = big.object_id )

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 4.12 4.12 0 135356 15 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 4.12 4.12 0 135356 15 1

Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
1 SORT (AGGREGATE)
792 FILTER
135297 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'BIG'
133504 INDEX GOAL: ANALYZED (RANGE SCAN) OF 'SMALL_IDX'
(NON-UNIQUE)

That shows if the outer query is "big" and the inner query is "small", in is generally more
efficient then NOT EXISTS.

Now:

select count(subobject_name)
from small
where object_id in ( select object_id from big )

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.01 0 0 0 0
Execute 2 0.00 0.00 0 0 0 0
Fetch 2 0.51 0.82 50 298 22 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 5 0.52 0.83 50 298 22 1



Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT GOAL: CHOOSE
1 SORT (AGGREGATE)
99 MERGE JOIN
16913 SORT (JOIN)
16912 VIEW OF 'VW_NSO_1'
16912 SORT (UNIQUE)
135296 INDEX GOAL: ANALYZED (FAST FULL SCAN) OF 'BIG_IDX'
(NON-UNIQUE)
99 SORT (JOIN)
99 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'SMALL'


versus:
select count(subobject_name)
from small
where exists ( select null from big where small.object_id = big.object_id )

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.01 0.01 0 204 12 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.01 0.01 0 204 12 1

EGATE)
99 FILTER
100 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'SMALL'
99 INDEX GOAL: ANALYZED (RANGE SCAN) OF 'BIG_IDX' (NON-UNIQUE)

shows that is the outer query is "small" and the inner query is "big" -- a WHERE EXISTS can be
quite efficient.

不过这个例子我在ORACLE 11g上运行的时候并没有得到相同的结果,主要是因为我开启了查询重写,使得ORACLE可以自己重写查询,根据实际情况将exists和in进行互换(你不能指望每次数据库的查询重写都正确,特别是在数据库设计的不怎么好的情况下,查询重写总会出错)。禁用查询重写后得到了类似的结论,在9i和10g上我没有试过,有兴趣的话,可以测试一下。

欢迎有问题一起讨论。

2008年12月15日星期一

触发器做数据验证的性能问题

还是继续上次的话题,用触发器做数据验证,上次已经讨论了用触发器做数据验证有可能会出错,是功能方面的问题。这次想讨论一下性能问题。

首先建立两个测试表:
create table t1 (c1 number,constraint myt1_ck1 check( c1 between 0 and 100000)) ;
create talbe t2 (c1 number);
create or replace
TRIGGER MYT2_TRG
AFTER INSERT OR UPDATE ON T2
FOR EACH ROW
BEGIN
IF :NEW.C1<0 OR :NEW.C1>100000 THEN
RAISE_APPLICATION_ERROR(-20001,'Value is too large or too small.');
END IF;
END;

然后set timing on
运行以下语句:
insert into t1 select rownum from all_objects where rownum<=10000;
insert into t2 select rownum from all_objects where rownum<=10000;
第一个语句耗时0.51M
第二个语句耗时0.59M
insert into t1 select rownum from all_objects where rownum<=100000;
insert into t2 select rownum from all_objects where rownum<=100000;
第一个语句耗时35.25M
第二个语句耗时32.56M

这是在没有错误发生的情况下测试,如果有错误发生,性能相差更大,有兴趣可以写个小例子测试一下。

接下来使用tom老人家的runstats工具来进行比较测量。

fy@ORCL> exec runstats_pkg.rs_start();

PL/SQL procedure successfully completed.

fy@ORCL> insert into myt1 select rownum from all_objects where rownum<=100000;

71844 rows created.

fy@ORCL> exec runstats_pkg.rs_middle();

PL/SQL procedure successfully completed.

fy@ORCL> insert into myt2 select rownum from all_objects where rownum<=100000;

71844 rows created.

fy@ORCL> exec runstats_pkg.rs_stop(1000);
Run1 ran in 10488 hsecs
Run2 ran in 12552 hsecs
run 1 ran in 83.56% of the time

Name Run1 Run2 Diff
LATCH.object queue header oper 502 1,616 1,114
LATCH.simulator lru latch 9,022 12,771 3,749
LATCH.simulator hash latch 9,053 12,905 3,852
STAT...no work - consistent re 81,400 72,775 -8,625
STAT...consistent gets from ca 225,445 216,746 -8,699
STAT...consistent gets 225,445 216,746 -8,699
STAT...buffer is not pinned co 283,146 274,428 -8,718
STAT...buffer is pinned count 74,052 82,792 8,740
STAT...consistent gets from ca 82,070 73,301 -8,769
STAT...session uga memory -15,772 0 15,772
STAT...physical read total byt 32,768 49,152 16,384
STAT...physical read bytes 32,768 49,152 16,384
STAT...session logical reads 226,812 290,606 63,794
STAT...HSC Heap Segment Block 405 71,858 71,453
STAT...redo entries 900 72,356 71,456
STAT...db block gets from cach 1,367 73,860 72,493
STAT...db block gets 1,367 73,860 72,493
STAT...calls to kcmgrs 37,858 119,922 82,064
STAT...db block changes 1,384 144,841 143,457
STAT...session pga memory -196,608 0 196,608
LATCH.cache buffers chains 237,315 583,505 346,190
STAT...undo change vector size 172,280 4,600,964 4,428,684
STAT...redo size 1,120,676 17,033,476 15,912,800

Run1 latches total versus runs -- difference and pct
Run1 Run2 Diff Pct
838,049 1,190,743 352,694 70.38%

PL/SQL procedure successfully completed.

上面 Diff的差值是负数的情况,估计是由于数据缓存造成的,在真实环境下,应该不会有这种情况发生。主要可以看到不论是运行时间还是latch数量,第一个方法都小于第二个方法。因此,用什么方法做数据验证,大家应该都没问题了吧。

2008年12月14日星期日

美国教授:中国的研究生基本靠自学成材

转帖的文章,原文请看这里,十分惊讶老美居然这么了解中国。

中国和美国学术环境不同的地方很多,最大的不同是,研究人员对自己研究工作的投入多少不同。当然,在中国有很多负责任并工作努力的研究人员,但也有不少人打着学术的旗号捞好处﹑争权夺利。同时还不忘时时炫耀一下自己的知识分子地位;为了爬上领导的位子而勾心斗角;尽力安排媒体对自己的采访以提高知名度;出去演讲以赚取高额的报酬;加入各种公司的董事会以谋得某种好听的头衔和利益。如此这般,这些教授哪里还有什么时间做研究呢?他们如何及时完成自己的科研成果呢?那就只有依靠自己学生们的努力。

  这些教授因为能够争取到研究资金,而且也拥有行政职位,因此能够招到最好的学生。学生们一方面有追求成功的动力,一方面不敢也不愿得罪自己的指导教师。为了自己的将来,学生们就不惜余力地满足教授的各种要求,加深教授对自己的好印象。教授也正好乐得坐享其成。

  如果在这个过程中,教授能够提供一些指导,情况还不算糟糕。但一般来讲,很多学生在面对巨大的研究压力的时候,所得到的教授的帮助是相当不足的,基本上都要靠自己。当然,通过自学,他们学到了一些新的知识,却会不可避免地走一些弯路,也达不到本应达到的效果。

在中国,许多请学生帮忙的教授,虽然没提供过什么指导或指导不够,在文章发表的时候还是会把自己的名字放在学生的文章上面,好像是自己尽心指导了一样。还有一些为数不多的教授,直接就把文章拿来,占为已用,学生的名字根本不在文章上出现。这些教授,虽然文章没写一个字,却得到好评。在美国,这种情况叫做 “plagiarism”(剽窃),是非常严重的错误,也是学校可以把教授开除的原因之一。这样的事情不应该发生,而且它也是中国经济发展的巨大障碍。

  另一个中国和美国比较明显的差别,和学院权力的分配有关。老实说,在美国,确实也常常会碰到一个人或“一小团人”想控制整个学院的例子,不过美国的学院制度会约束这样的情况。比如在美国,刚获得博士学位的毕业生是不允许留校的,他们至少要有三到五年在其他学校工作的经历,以证明他们有独立研究和教书的能力,才可以回到母校去。

  但在中国,与握有实权的教授关系好的毕业生,可以直接留校教书,而教授就可以由此形成自己的“团派”。学院的一切也尽在教授的掌握之中,学术自由从而受到了限制。这么严重的情况在美国是不存在的。

  由此所衍生出来的同一大学各学院的领导关系也错综复杂,学校的研究工作也受到极大的影响。例如,某个有权力控制学院“王国”的领导,与其他某个学院的领导交恶,那么他的权力范围内的学院,会对这个学院敬而远之。其结果导致学院之间基本没有什么合作活动,研究工作就会受到不利的影响。这个情况有点像一个幼稚的游戏,阻碍着中国学术的发展。

  与此相反,在美国,大学非常重视学院之间的合作,这能够产生更有意义的研究结果。因为各个学院分属不同的领域,这样跨学科的研究合作既有利于整个学校的发展,也有利于各学院之间的互相学习,而这样的学习在中国是不常见的。

  最后,我想指出的一个中国和美国之间的不同,则和学术概念有关。美国的学者基本上都热爱学术,可以说是满腔热忱。这种态度是一种动力,激励他们去努力。大部分美国的学者也对知识保持好奇的态度,努力把自己的视野范围扩大起来。他们大部分也都会负起学术方面的责任,而且尊重他们领域内其他的人。虽然教授之间的学术概念经常不一样,但这种不同一般不会产生什么关系不愉快的结果,学术的质量比数量更加受到重视。

  但是,相当多的中国教授进行学术研究的动力,好像和“钱”或“权”有关系,教授之间也常常存在很多个人仇恨或嫉妒。在中国,被考虑的主要问题经常是:这一年你写了几篇文章?这些文章是哪里发表的?你是哪一个学校毕业的?你的指导老师是谁?你和哪一些领导关系比较好?还有你能够申请到多少钱的课题?你曾经在国外待过吗?是不是名校?等等。

  与此相反,在美国,如果你在一年甚至五年之内只发表一篇、但品质堪称一流的文章,人人都会很看重你,因为从文章的内容,可以看出作者所倾注的心血和努力。然而在中国,这样做几乎是不可能的。因为这里所有的成绩和统计数字有关,质量并不重要,重要的是你写了没有,发表了没有,在什么级别的杂志发表的。没有人真正关心文章是否真有价值,也没有人真正关心学生究竟在学校里学到了什么。

我的GIS之路

从接触GIS软件开始,不知不觉就已经4个年头了。上次邓老师让我介绍一下自己的学习经验,一时没想好,最近大概整理了一下,想写出来,给非地理新系统或者计算机类专业的同学参考一下。

虽然说起来我接触GIS已经4年了,但是前两年几乎等于没有接触。主要是因为自己没有买电脑,就算想学,也没机会学。其实大三的时候,邓老师上GIS那课的时候,我还是好好学了一下。那时候,看了一本很好的书,书的名字我记不住了。好像是陈健飞翻译的一本老美的书。由于老师的严格要求和自己的努力,我对GIS概念上的那套东西有了基本的了解。大四的时候,我神使鬼差的找到一份GIS二次开发的工作,这才算是正真的开始学习和了解GIS。但是那个时候主要还是以学习编程语言为主,当时用了慧图公司的top map SDK,现在想想,那个软件真的还挺适合上手的。虽然很多功能不能做,但是学习的门槛很低而且学习曲线也不陡峭。

做了三个月基于TOPMAP的二次开发后,又开始基于ARCOBJECTS做一个GIS的框架,作为以后公司CS模式GIS的框架。很遗憾由于后来别的项目的原因,我没有完成这个开发任务,但是ESRI清晰的设计还是给我留下很深刻的印象。在开发的过程中,我时不时的需要做一些图,来给客户看,因此学会了一些基本的操作。但是除了那些基本的操作之外,我建立起来了,地图,图层,实体,属性这些基本的概念。当然我建立的不是死的概念,而是很具体的概念和操作的对应,从此我才算是正真的踏上GIS之路。

后来一年的工作里,我其实一直都没怎么接触GIS,除了当时协助邓老师,指导一位本科毕业生。由于我当时的工作比较忙,除了搭好框架外,实际上没有给他太大的帮助。虽然我没有接触GIS,但是我在Database方面有了长足进步。而且十分幸运的是,我学习的是ORACLE数据库,并且在数据库编程方面下了点功夫。当然,当时对于XML Db这块的学习,更是锦上添花。因为其实ORACLE数据库中的XML Db和SPATIAL Db的构成原理有类似之处。因此这也是我对想真正学习GIS的同学的一点建议,那就是花3个月到半年时间来学习一门数据库,当然我更建议学习ORACLE或者Postgresql,主要原因是他们的功能性很强,学习数据库编程的时候更加有回旋余地。而且这两个数据库上面都有已经成型的,非常强大的空间数据库扩展。需要注意的是,不要去学数据库原理这样的课程,主要是因为这样的课程比较空泛,理论太多而实际用的到的内容太少。

八月份后我就开始准备复习和出国的事情了。这时我看了两本书,对我的帮助很大。一本书是《为我们的世界建模》,英文名字叫做《modeling our world》,是ESRI公司的一个人写的。另外一本书是《空间数据库》,英文名字叫做《Spatial database a tour》,是明尼苏达大学的一个老师写的。前者主要是针对geodatabase的一些理论说明,后者则非常全面和深入的讨论了空间数据库的很多方面的内容,但是想读懂这本书需要很强的数学,数据库以及JAVA方面的功底,强烈推荐想学的同学看看。当时还旁听了一个老师的GIS课程,当然他讲的挺垃圾的,估计全班90人没几个人听懂了。

大概是从11月份以后吧,我使用GIS的时候就如有神助了。其实最重要的是,我已经对GIS的术语,概念和基本模型很了解了。因此,面对问题的时候,我能很快的使用GIS的数据模型来模拟,而碰见不太懂的操作的时候只需google一下就好了。这一段时间,我还看了一些map server的源代码,当然主要是想糊弄明大的那个老师,不过他鹰眼犀利,一下就看出我是个草包。因此,我建议想学的话,可以先把基本的概念弄明白,然后学习如何将这些概念模型应用到自己的领域中,动手实践,发现问题,反过来再看书学习......到帮助邓老师代05级学生环境规划实习的时候,我已经对GIS领域内的内容了如指掌(哈哈,吹牛了)。

放假回家后,闲来无事就继续研究研究GIS。这时候,我才正真接触了PGSQL和POSTGIS。关于这两个东西介绍,网上有很多,只是都不怎么好,如果想学习的话,建议大家看看POSTGIS的手册,和数据库里面的源代码。注意,需要看的数据库里的PLSQL源码,而不是C语言写的那些代码。对于想学空间数据库的朋友来说,POSTGIS是一个不可多得的财富。有时候宝藏就在咱们手笔,只是百分之九十九的人不知道怎么念芝麻开门。还有一个很好的工具是QGIS,它是一个很好的POSTGIS的前端软件。我自己觉得POSTGIS和QGIS,再加上MAP server,你就可以搞定GIS内80%的问题,而且全部都免费。在家里最大的进步就在于对于一些GIS软件的基本实现有所了解。如果在达到我上面说的了如指掌的境界后,还想继续学习GIS软件的基本实现的话,那我建议好好看看POSTGIS(空间数据存储),QGIS(空间数据显示),MAP SERVER(空间数据的WEB服务器),当然后这三个对于C语言要求挺高的,熟悉C语言是前提要求,而POSTGIS除了对C语言要求挺高之外,还需要精通PLSQL编程,因此在学习这些东西之前,要有足够的心理准备和知识储备。如果是熟悉JAVA语言的话,可以看看UDIG(空间数据显示),GEO SERVER(空间数据的WEB服务器)。另外,MYSQL的空间数据库扩展MYSPATIAL也很有特色,走了一条跟现在的ORACLE和PGSQL都不一样的路(跟ORACLE8I的空间数据库差不多)。

说了这么多,还是想总结一下,多看书+多思考+多实践=GIS Expert。

2008年12月13日星期六

PostgreSql源码学习(7)

PG源码之parser(4)

前文已经大概描述了PGSQL,在解析SQL语句时所做的工作,接下来需要对在这个过程中的一些主要文件进行分析。

主要分析文件:
    // 基础节点定义
    src/include/nodes/nodes.h
    // SQL语句被解析后得到的节点结构体定义
//SQL语句经过parser解析后都先对应该该文件的一个struct
    src/include/nodes/parsenodes.h
    // List定义
    src/include/nodes/pg_list.h
    // List实现
    src/backend/nodes/list.c   
    // utility Stmt运行文件
    src/backend/tcop/utility.c
    // SQL analyze and rewrite
    src/backend/parser/analyze.c

PostgreSQL用一种非常简单的形式实现了类似C++的继承,请看nodes.h :
typedef struct Node
{
  NodeTag  type;
} Node;
然后请看:parsenodes.h
假设有一个create table的sql:
create table test (name varchar(100, pass varchar(100));
将会被解析到如下structure:
typedef struct CreateStmt
{
  NodeTag  type;
  RangeVar   *relation;  /* 关系名*/
  List    *tableElts;  /* 列定义*/
  List    *inhRelations; /*表继承关系(PG里的继承) */
  List    *constraints; /* 约束条件 */
  List    *options;  /*其他的可选条件*/
  OnCommitAction oncommit; /* 提交时做的事情 */
  char    *tablespacename; /* 表空间*/
} CreateStmt;

以下是select语句对应的结构体:
typedef struct SelectStmt
{
NodeTag type;

/*
* These fields are used only in "leaf" SelectStmts.
*/
List *distinctClause; /* 处理distinct关键字 */
IntoClause *intoClause; /*处理select into /create table tbn as select ... */
List *targetList; /* 列*/
List *fromClause; /* from 子句*/
Node *whereClause; /* WHERE条件子句 */
List *groupClause; /* GROUP BY子句*/
Node *havingClause; /* HAVING 子句 */

/*
* In a "leaf" node representing a VALUES list, the above fields are all
* null, and instead this field is set. Note that the elements of the
* sublists are just expressions, without ResTarget decoration. Also note
* that a list element can be DEFAULT (represented as a SetToDefault
* node), regardless of the context of the VALUES list. It's up to parse
* analysis to reject that where not valid.(这段话的意思是valuesLists只在叶查询节点里出 * 现,就是最内层的子查询)
*/
List *valuesLists; /*表达式值列表*/

/*
* These fields are used in both "leaf" SelectStmts and upper-level
* SelectStmts.
*/
List *sortClause; /* 排序*/
Node *limitOffset; /* 从第几个开始limit子句的第一个参数 */
Node *limitCount; /* 到哪里结束limit子句的第二个参数*/
List *lockingClause; /* FOR UPDATE (list of LockingClause's) */

/*
* These fields are used only in upper-level SelectStmts.
*/
SetOperation op; /*set操作的类型*/
bool all; /*因该是处理any关键字的*/
struct SelectStmt *larg; /* left child */
struct SelectStmt *rarg; /* right child */
/* Eventually add fields for CORRESPONDING spec here */
} SelectStmt;

这上面是两个对应于create语句和select语句的结构体,对应于其他语句的结构体,也都可以在这个文件里找到,这两个结构体里面各个成员变量的含义我已经做了注释。这两个struct的第一个元素都是type(NodeTag)。PG的很多struct的第一个元素都是NodeTag,这样在函数中传递指针变量时,可以很简单的把参数设置成:Node*。说简单点,其实有点像是所有的struct都继承了Node这个struct。

在nodes.h中有每个Node的值的定义,比如:上面说的CreateStmt的type的值就是:T_CreateStmt。其他的每个node的定义也可以在这个文件里找到。

接下来需要看看的是链表,链表在pg_list.h中有定义:
typedef struct List
{
  NodeTag  type;   /* T_List, T_IntList, or T_OidList */
  int   length;
  ListCell   *head;
  ListCell   *tail;
} List;
可以看到,List的定义是基于基类Node来进行的。
常用的List操作函数有:
//取List第一个元素
ListCell *y = list_head(List *l);
//得到List的元素个数
list_length(List *l);
// 遍历List
foreach(cell, l)
{
 …
}
其他的很多函数具体可以参考pg_list.h和list.c

Database is the core of universe

记得刚接触ORACLE的时候,有人给我讲了个故事。他说他在第一次看到ORACLE7的时候,当时叫做ORACLE universal server,翻译成汉语就叫做ORACLE通用服务器,他的第一反应是ORACLE宇宙服务器。然后他说,等你以后明白为什么叫做宇宙服务器了,你就大概了解ORACLE数据库库了。

现在我接触这个东西都两年多了,也算是对这个东西有所了解了,逐渐体会到所谓宇宙服务器的含义。CS模式兴起的时候,ORACLE给数据库也加一个客户端。网络走红的时候,就干脆把数据库服务器当作web服务器来用,网页就干脆是数据库里的一堆存储过程。一直到现在的apex(application express),都是走的这个思路,著名的Ask tom就是用这个东西做的。搜索热门的时候,ORACLE7就集成了全文检索和多媒体检索功能,说白了就是在数据库里建立几张表来做索引,进行检索。到了搜索引擎时代,ORACLE AS 里就集成了搜索引擎的功能,而这个功能说白了也就是在数据库里建了几张表,进行全文检索。更不要提JAVA,在JAVA大紫大红的年代,ORACLE就把JAVA直接装入数据库,让JAVA程序员可以用JAVA来写存储过程。当然了,还有PRO C,PRO COBOL...一系列装载在数据库里的编程语言。

于是,ORACLE 8i 集成了空间数据库(ORACLE Spatial)来存储空间信息。ORACLE 9i把XML数据作为原生的本地数据库。10g又进一步增强了很多应用方面的功能。到了11g我们已经能存储真三维数据和在数据库里做web service了,更不要说那些OLAP,BI,DM方面的功能,感觉什么功能都具备了,什么东西都可以放在数据库里(这个感觉很像星际之门亚特兰迪斯里远古人的那个超大超强的database)。

理所当然的,我第一次听见OEL (ORACLE enterprise LINUX)的时候,我的感觉就是这是个装在数据库里的操作系统,第一次听见BTRFS的时候,我认为这是个装在数据库里的文件系统,而ORACLE AS,那种从数据库里强行扒出来的感觉更是让人印象深刻。

因此,在ORACLE公司看来,Database is the core of universe.

闲来无事,就把上面的文章翻译了一下,好长时间没写英文了,感觉有点生疏。

When using ORACLE database firstly , my manger told me a story. His colleague called oracle universal server to oracle universe server by mistake, when playing ORACLE database version 7. And he told me that whether understanding why the databasewas called universe server is the key to study oracle database.

Now,i have used this serveral years. To some extent,i am a pre expert in this field. I begin to understand what's the universe server mean. When c/s patten began, Oracle would add a client to the database server. When net era was coming, the database server had been used as a web server.Indeed, the web page was sereral procedures in DB. Now as the son of OWA, application express continue this thinking.The most famous website of dba in oracle field--asktom was completed by this tool.When searching technology was in fashion,Oracle intigrate text searching and multimedia searching technology in database.Of course,the logo of ORACLE database should be add "JAVA inside" label,which means java could be used to write procedure in DB.

Further,ORACLE combine spatial database to ORACLE DB. At the same time, they extend DB to store XML data as a native XML database.And now , we could store the real 3D data and build web service on database,besides powerful OLAP,BI and DM functions.My feeling is everything could be stored in Database, that like the super database of the ancients in stargate atlantis.

Naturally,I thinked the OEL as a operation system in database and the BTRFS as a file system in database when finding them firstly. The feeling of ORACLE AS was generated from ORACLE database was impressive.

So, for ORACLE company, database is the core of universe.

2008年12月12日星期五

触发器是用来做数据验证的么

前几天听同事说,他们做了一个触发器来进行数据验证。我知道触发器有很多用途,但是用触发器来做数据验证,多半就有问题。

举例说明如下:
在emp表里希望每个部门的人数都不能少于三人,在EMP表建立如下触发器:
create or replace
TRIGGER
emp_trg AFTER INSERT OR DELETE OR UPDATE ON EMP
BEGIN
for irec in (
select deptno, count(*) emps
from emp
GROUP by deptno
HAVING count(*) < 3
)
loop
RAISE_Application_error(-20000,'dept wrong number');
end LOOP;
END;

在第一个会话里执行如下语句(员工号为7499,7521,7654,7900的员工都是部门号为30的员工,这个部门共有6个人):
delete from emp where empno in (7499,7521,7654);
3 rows deleted.
这里触发器看到的部门号为30的员工有三个。
在第二个会话里执行如下语句:
delete from emp where empno=7900;
1 row deleted.
这里触发器看到的部门号为30的员工有5个。
commit;
Commit complete.
这是再回到第一个会话执行commit;
Commit complete.

然而这时部门号为三十的部门只有两个人,因此触发器验证失败。
这是功能上的问题,性能上,一个触发器对增删改操作的性能影响,至少和一个索引相当。因此在性能上来说,也得不偿失。如果是做数据验证的话,用check来做多半好一些。

2008年12月10日星期三

ORACLE数据库的成本是什么

在应用ORACLE数据库的过程中,我们经常需要查看执行计划。执行计划里最重要的两个指标就是基数和成本。很多人都对成本的含义不清楚,因此有必要弄清楚其意义到底是什么。

根据ORACLE database performance tuning guide,
cost=(
#SRds*sreadtim+
#MRds*mreadtim+
#CPUCycles/cpuspeed
)
/sreadtim

where
#SRds number of single block reads(单块读取数量)
#MRds number of multi block reads(多块读取数量)
#CPUCycles number of cpu cycles(CPU转数)

sreadtim single block read time(单块读取时间)
mreadtim multi block read time(多块读取时间)
cpuspeed cpu cycles per seconde(CPU每秒钟转数)

实际上,成本就是,操作花费在单块读上的总时间,加上花费在多快读上的总时间,再加上CPU处理总时间,除以一个单块读需要花费的总时间。

有人说COST仅仅是个相对的数值,只是在比较不同操作的成本时才有用,当然是不正确的,上式表明COST是尤其自己的意义的(跟时间相关)。

其实ORACLE选取这样一个奇怪的单位来衡量成本的大小,一点也不让人吃惊。当然也有人说,ORACLE使用这个公式只是为了保持跟以前版本的延续。但是很显然这个说法是错误的。为什么ORACLE在第一次出现CBO时会选择这个古怪的单位来表示成本呢,这个上述说法显然无法解释这个问题。我以为,使用这个单位的原因主要是它可以消除由于硬件带来的差异。比如一台IBM最高端的服务器和随便一台笔记本电脑的运行速度明显不一样。这就会造成运行于这两台机器上的ORACLE数据库在执行同样的操作的时候,消耗的时间不同。而通过以上的计算,选择那个古怪的单位作为成本的单位,就可以消除硬件带来的差异(当然软件环境一致的情况下才能在不通的机器上得到同样的成本)。

有问题欢迎与我讨论。

有关附件存储方式的探讨

在oracle中国家语言支持的索引问题一文中,我已经提到了优化器在处理长字符串时的一些问题。

在高丽棒子做的数据库设计中(其实我们中国人很多时候也会这么做),要么是把附件作为BLOB直接存储,要么是把附件地址保存在一个VARCHAR2的字符串中。

第一种做法非常不可取,原因在于这种做法会极大的增加数据库服务器的负担,并且在程序里处理BLOB的痛苦不是常人可以忍受的。

第二种做饭看似不错,但是考虑到通常服务器是unix/linux操作系统,因此路径会被保存为很长的路径,比如:/home/fanng/documents/attfiles/username/...(其实这还不算长),这种情况下优化器在处理长字符串时会选择错误的执行计划。因此对数据库服务器性能的影响也会很大。

个人以为,首先来说,数据库服务器本身是不适合同时存储附件的,因此第一种做法几乎可以立即排除。因为数据库服务器的资源有限,特别是I/O开销太大,最好还是只跑数据库。而应用服务器上主要是跑应用业务,因此CPU开销较大,而I/O开销相对较小,因此将附件置于应用服务器应该更好一些。可以基于文件系统来控制,并且在数据库里保存少量的信息来定位,这样就可以避免出现上文所说的两种情况。

欢迎有问题与我探讨。

再探ORACLE数据库字符集乱码问题

一 服务器字符集

如前文所述,ORACLE数据库有数据库字符集和国家语言字符集两种。数据库字符集用于存储char,varchar2,clob,long等数据类型,用来标示表名,列名,sql和PLSQL存储单元等。而国家字符集通常只用来存储nvarchar2,nchar,vclob这些数据类型。建库时就已经决定了数据库字符集和国家语言字符集。对于简体中文操作系统,如果安装数据库的时候没有特别指定,默认字符集就是ZHS16GBK。



ORACLE数据库中字符集的命名遵循以下规则:
Language\bit size\encoding
ZHS 16 GBK
当然有一些字符集命名不在此例:
AL16UTF16,其中AL代表ALL,指的是all language。

二 客户端字符集

NLS_LANG参数由以下组成:
NLS_LANG=language_Territory.client_characterset
language指定ORACLE所用的提示语言,日期中月份和日的显示。
Territory指定货币和数字格式,地区和计算星期及日期的习惯。
client_characterset控制客户端应用程序的字符集。
如果是在windows平台,可以使用chcp查看系统页代码,比如中文winxp操作系统就会显示“活动的代码页:936”。
在sqlplus里可以使用alter session set nls_lang=...来修改这个参数,在sql developer中,需要在tools->preference->database->NLS_PARAMETERS来修改。

三 乱码产生

乱码的产生是由于下面三个字符集引起的:
数据库字符集
客户端字符集
客户端NLS_LANG参数的设置

(1)当客户端字符集和数据库字符集不同,同时NLS_LANG不同于服务器字符集。
在这种情况下存在两种可能性:
a 客户端输入的字符在NLS_LANG中没有对应的字符,这时会以?来代替。
b 客户端输入的字符在NLS_LANG中对了其他字符,传递给数据库后,字符被存储下来,但是元数据丢失,因此数据库中存储的字符不再代表客户输入的字符。(注意:这个过程不可逆,因为通常情况下我们不知道客户端字符集)

(2)NLS_LANG和服务器字符集相同时
在这种情况下,数据库不用对客户端传递过来的数据进行编码转换,直接存入数据库。如果客户端传过来的字符集在数据库中有正确的对应关系,则存储正确,否则就被替换为?,于是就产生了乱码。
比如如果数据库字符集是WE8MSWIN1252时,即使NLS_LANG设置相同,数据库也不能正确的存储汉字。


四 常用的转换方法
通常使用dblink,并组合utl_raw.cast_to_raw以及utl_raw.cast_to_varchar2来转化,具体步骤请参考ORACLE公司的文档。

2008年12月9日星期二

日期数据应该以什么数据类型存储

日期类型应该存储成什么数据一直是很多人容易弄错的问题。总的来说,在各种数据库中日期类型都应该存储成date类型。对于mysql和postgresql来说,存储成日期类型是没有问题的;对于ORACLE数据库来说,日期类型有其特殊的问题。因为ORACLE数据库的date数据类型的精度是到秒,因此如果只是需要精确到天的话,处理起来要麻烦一点。
mysql和pgsql可以使用如下语句进行查询:

select * from emp where birth_day=a date;

但是ORACLE里就不能这么写查询,必须写成:

select * from emp where birth_day between to_date(datestr||' 00:00:00','yyyy/mm/dd hh24:mi:ss') and to_date(datestr||'23:59:59','yyyy/mm/dd hh24:mi:ss') ;

注意不要用这种写法:

select * from emp where birth_day>=to_date(datestr||' 00:00:00','yyyy/mm/dd hh24:mi:ss') and birth_day<= to_date(datestr||'23:59:59','yyyy/mm/dd hh24:mi:ss') ;

这种写法有其本身的问题。

在这种情况下很多人会直接这样写查询(因为between写起来麻烦一点):

select * from where datestr=to_char(birth_day,'yyyy/mm/dd');

熟悉数据库的人都知道这么写会给数据库带来多大的影响,即使加fbi。

因此很多人为了省事,就直接在数据库中把日期型数据存储成为字符型或者数字型(高丽棒子就常这么做),比如:

create table t1 (tid number,birth_day date);
create table t2 (tid number,birth_day char(8));
create table t3 (tid number,birth_day number(8));

insert into t1 select rownum,sysdate-rownum from all_objects where rownum<=365*3;
insert into t2 select rownum,to_char(sysdate-rownum,'yyyymmdd') from all_objects where rownum<=365*3;
insert into t3 select rownum,to_number(to_char(sysdate-rownum,'yyyymmdd')) from all_objects where rownum<=365*3;

commit;

create index t1_bd_idx on t1(birth_day);
create index t2_bd_idx on t2(birth_day);
create index t3_bd_idx on t3(birth_day);

set autot traceonly explain

执行以下查询:

select * from t1 where birth_day between to_date('20070101','yyyymmdd') and to_date('20071231','yyyymmdd');
因为有三分之一的数据会命中,所以ORACLE肯定会使用全表扫描,执行计划中基数是365,成本是3。

select * from t2 where birth_day between '20070101' and '20071231';
这个查询其实应该和上一个查询命中同样多的数据,有同样的执行计划。但是很遗憾,ORACLE执行了索引范围扫描,然后通过ROWID再拿到数据。整个查询中基数是43,成本是3,(这只是优化器估算出来的成本,实际的成本应该是26)。

select * from t3 where birth_day between 20070101 and 20071231;
这个查询跟上一个查询有同样的执行计划。

很显然后两个查询的执行计划是有问题的。这个问题就是由于错误的数据类型导致ORACLE在估算数据的范围的时候出了差错,比如第三个查询数据库的估算方法是:
(20071231-20070101)--条件里范围差
/(20081210-20051210) --实际数据的范围差
*1095(表里的行数)
最后的出来的基数是42。字符类型的计算也与此类似。
当然如果在表上建立了直方图的话,情况会好一些,成本估算所得的基数与实际基数近似。

由此可见将日期类型存储成字符或者数值类型会有很大问题,虽然程序员在处理问题时方便了一点,但是数据库的性能确会遭到影响(程序员都喜欢这么设计数据库表)。
另外由于pgsql和mysql的优化查询算法是不完全与ORACLE类似,因此这种设计表的方法在这两种数据库上运行的如何有待继续研究。

有问题欢迎与我联系。

JAVA语言学校的危险性

转自CSDN,原文请看这里

下面的文章是More Joel on Software一书的第8篇。

我觉得翻译难度很大,整整两个工作日,每天8小时以上,才译出了5000字。除了Joel大量使用俚语,另一个原因是原文涉及“编程原理”,好多东西我根本不懂。希望懂的朋友帮我看看,译文有没有错误,包括我写的注解。

====================
JAVA语言学校的危险性
作者:Joel Spolsky
译者:阮一峰
原文: http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html
发表日期 2005年12月29日,星期四


如今的孩子变懒了。
多吃一点苦,又会怎么样呢?

我一定是变老了,才会这样喋喋不休地抱怨和感叹“如今的孩子”。为什么他们不再愿意、或者说不再能够做艰苦的工作呢。
当我还是孩子的时候,学习编程需要用到穿孔卡片(punched cards)。那时可没有任何类似“退格”键(Backspace key)这样的现代化功能,如果你出错了,就没有办法更正,只好扔掉出错的卡片,从头再来。

回想1991年,我开始面试程序员的时候。我一般会出一些编程题,允许用任何编程语言解题。在99%的情况下,面试者选择C语言。

如今,面试者一般会选择Java语言。

说到这里,不要误会我的意思。Java语言本身作为一种开发工具,并没有什么错。、

等一等,我要做个更正。我只是在本篇特定的文章中,不会提到Java语言作为一种开发工具,有什么不好的地方。事实上,它有许许多多不好的地方,不过这些只有另找时间来谈了。

我在这篇文章中,真正想要说的是,总的来看,Java不是一种非常难的编程语言,无法用来区分优秀程序员和普通程序员。它可能很适合用来完成工作,但是这个不是今天的主题。我甚至想说,Java语言不够难,其实是它的特色,不能算缺点。但是不管怎样,它就是有这个问题。

如果我听上去像是妄下论断,那么我想说一点我自己的微不足道的经历。大学计算机系的课程里,传统上有两个知识点,许多人从来都没有真正搞懂过的,那就是指针(pointers)和递归(recursion)。

你进大学后,一开始总要上一门“数据结构”课(data structure), 然后会有线性链表(linkedlist)、哈希表(hashtable),以及其他诸如此类的课程。这些课会大量使用“指针”。它们经常起到一种优胜劣汰的作用。因为这些课程非常难,那些学不会的人,就表明他们的能力不足以达到计算机科学学士学位的要求,只能选择放弃这个专业。这是一件好事,因为如果你连指针很觉得很难,那么等学到后面,要你证明不动点定理(fixed point theory)的时候,你该怎么办呢?

有些孩子读高中的时候,就能用BASIC语言在AppleII型个人电脑上,写出漂亮的乒乓球游戏。等他们进了大学,都会去选修计算机科学101课程,那门课讲的就是数据结构。当他们接触到指针那些玩意以后,就一下子完全傻眼了,后面的事情你都可以想像,他们就去改学政治学,因为看上去法学院是一个更好的出路[1]。关于计算机系的淘汰率,我见过各式各样的数字,通常在40%到70%之间。校方一般会觉得,学生拿不到学位很可惜,我则视其为必要的筛选,淘汰那些没有兴趣编程或者没有能力编程的人。

对于许多计算机系的青年学生来说,另一门有难度的课程是有关函数式编程(functionalprogramming)的课程,其中就包括递归程序设计(recursiveprogramming)。MIT将这些课程的标准提得很高,还专门设立了一门必修课(课程代号6.001[2]),它的教材(Structureand Interpretation of Computer Programs,作者为Harold Abelson和Gerald JaySussmanAbelson,MIT出版社1996年版)被几十所、甚至几百所著名高校的计算系机采用,充当事实上的计算机科学导论课程。(你能在网上找到这本教材的旧版本,应该读一下。)

这些课程难得惊人。在第一堂课,你就要学完Scheme语言[3]的几乎所有内容,你还会遇到一个不动点函数(fixed- pointfunction),它的自变量本身就是另一个函数。我读的这门导论课,是宾夕法尼亚大学的CSE121课程,真是读得苦不堪言。我注意到很多学生,也许是大部分的学生,都无法完成这门课。课程的内容实在太难了。我给教授写了一封长长的声泪俱下的Email,控诉这门课不是给人学的。宾夕法尼亚大学里一定有人听到了我的呼声(或者听到了其他抱怨者的呼声),因为如今这门课讲授的计算机语言是Java。

我现在觉得,他们还不如没有听见呢。

这就是争议所在。许多年来,像当年的我一样懒惰的计算机系本科生不停地抱怨,再加上计算机业界也在抱怨毕业生不够用,这一切终于造成了重大恶果。过去十年中,大量本来堪称完美的好学校,都百分之百转向了Java语言的怀抱。这真是好得没话说了,那些用“grep”命令[4]过滤简历的企业招聘主管,大概会很喜欢这样。最妙不可言的是,Java语言中没有什么太难的地方,不会真的淘汰什么人,你搞不懂指针或者递归也没关系。所以,计算系的淘汰率就降低了,学生人数上升了,经费预算变大了,可谓皆大欢喜。

学习Java语言的孩子是幸运的,因为当他们用到以指针为基础的哈希表时,他们永远也不会遇到古怪的“段错误”[5] (segfault)。他们永远不会因为无法将数据塞进有限的内存空间,而急得发疯。他们也永远不用苦苦思索,为什么在一个纯函数的程序中,一个变量的值一会保持不变,一会又变个不停!多么自相矛盾啊!

他们不需要怎么动脑筋,就可以在专业上得到4.0的绩点。

我是不是有点太苛刻了?就像电视里的“四个约克郡男人”[6](Four Yorkshiremen)那样,成了老古板?就在这里吹嘘我是多么刻苦,完成了所有那些高难度的课程?

我再告诉你一件事。1900年的时候,拉丁语和希腊语都是大学里的必修课,原因不是因为它们有什么特别的作用,而是因为它们有点被看成是受过高等教育的人士的标志。在某种程度上,我的观点同拉丁语支持者的观点没有不同(下面的四点理由都是如此):“(拉丁语)训练你的思维,锻炼你的记忆。分析拉丁语的句法结构,是思考能力的最佳练习,是真正对智力的挑战,能够很好地培养逻辑能力。”以上出自Scott Barker之口(http://www.promotelatin.org/whylatin.htm)。但是,今天我找不到一所大学,还把拉丁语作为必修课。指针和递归不正像计算机科学中的拉丁语和希腊语吗?

说到这里,我坦率地承认,当今的软件代码中90%都不需要使用指针。事实上,如果在正式产品中使用指针,这将是十分危险的。好的,这一点没有异议。与此同时,函数式编程在实际开发中用到的也不多。这一点我也同意。

但是,对于某些最激动人心的编程任务来说,指针仍然是非常重要的。比如说,如果不用指针,你根本没办法开发Linux的内核。如果你不是真正地理解了指针,你连一行Linux的代码也看不懂,说实话,任何操作系统的代码你都看不懂。

如果你不懂函数式编程,你就无法创造出MapReduce[7],正是这种算法使得Google的可扩展性(scalable)达到如此巨大的规模。单词“Map”(映射)和“Reduce”(化简)分别来自Lisp语言和函数式编程。回想起来,在类似6.001这样的编程课程中,都有提到纯粹的函数式编程没有副作用,因此可以直接用于并行计算(parallelizable)。任何人只要还记得这些内容,那么MapRuduce对他来说就是显而易见的。发明MapReduce的公司是Google,而不是微软,这个简单的事实说出了原因,为什么微软至今还在追赶,还在试图提供最基本的搜索服务,而Google已经转向了下一个阶段,开发世界上最大的并行式超级计算机——Skynet[8]的H次方的H次方的H次方的H次方的H次方的H次方。我觉得,微软并没有完全明白,在这一波竞争中它落后多远。

除了上面那些直接就能想到的重要性,指针和递归的真正价值,在于那种你在学习它们的过程中,所得到的思维深度,以及你因为害怕在这些课程中被淘汰,所产生的心理抗压能力,它们都是在建造大型系统的过程中必不可少的。指针和递归要求一定水平的推理能力、抽象思考能力,以及最重要的,在若干个不同的抽象层次上,同时审视同一个问题的能力。因此,是否真正理解指针和递归,与是否是一个优秀程序员直接相关。

如果计算机系的课程都与Java语言有关,那么对于那些在智力上无法应付复杂概念的学生,就没有东西可以真的淘汰他们。作为一个雇主,我发现那些100%Java教学的计算机系,已经培养出了相当一大批毕业生,这些学生只能勉强完成难度日益降低的课程作业,只会用Java语言编写简单的记账程序,如果你让他们编写一个更难的东西,他们就束手无策了。他们的智力不足以成为程序员。这些学生永远也通不过MIT的6.001课程,或者耶鲁大学的 CS323课程。坦率地说,为什么在一个雇主的心目中,MIT或者耶鲁大学计算机系的学位的份量,要重于杜克大学,这就是原因之一。因为杜克大学最近已经全部转为用Java语言教学。宾夕法尼亚大学的情况也很类似,当初CSE121课程中的Scheme语言和ML语言,几乎将我和我的同学折磨至死,如今已经全部被Java语言替代。我的意思不是说,我不想雇佣来自杜克大学或者宾夕法尼亚大学的聪明学生,我真的愿意雇佣他们,只是对于我来说,确定他们是否真的聪明,如今变得难多了。以前,我能够分辨出谁是聪明学生,因为他们可以在一分钟内看懂一个递归算法,或者可以迅速在计算机上实现一个线性链表操作函数,所用的时间同黑板上写一遍差不多。但是对于Java语言学校的毕业生,看着他们面对上述问题苦苦思索、做不出来的样子,我分辨不出这到底是因为学校里没教,还是因为他们不具备编写优秀软件作品的素质。PaulGraham将这一类程序员称为“Blub程序员”[9](www.paulgraham.com/avg.html)。

Java语言学校无法淘汰那些永远也成不了优秀程序员的学生,这已经是很糟糕的事情了。但是,学校可以无可厚非地辩解,这不是校方的错。整个软件行业,或者说至少是其中那些使用grep命令过滤简历的招聘经理,确实是在一直叫嚷,要求学校使用Java语言教学。

但是,即使如此,Java语言学校的教学也还是失败的,因为学校没有成功训练好学生的头脑,没有使他们变得足够熟练、敏捷、灵活,能够做出高质量的软件设计(我不是指面向对象式的“设计”,那种编程只不过是要求你花上无数个小时,重写你的代码,使它们能够满足面向对象编程的等级制继承式结构,或者说要求你思考到底对象之间是“has-a”从属关系,还是“is-a”继承关系,这种“伪问题”将你搞得烦躁不安)。你需要的是那种能够在多个抽象层次上,同时思考问题的训练。这种思考能力正是设计出优秀软件架构所必需的。

你也许想知道,在教学中,面向对象编程(object-orientedprogramming,缩写OOP)是否是指针和递归的优质替代品,是不是也能起到淘汰作用。简单的回答是:“不”。我在这里不讨论OOP的优点,我只指出OOP不够难,无法淘汰平庸的程序员。大多数时候,OOP教学的主要内容就是记住一堆专有名词,比如“封装”(encapsulation)和“继承”(inheritance)”,然后再做一堆多选题小测验,考你是不是明白“多态”(polymorphism)和“重载”(overloading)的区别。这同历史课上,要求你记住重要的日期和人名,难度差不多。OOP不构成对智力的太大挑战,吓不跑一年级新生。据说,如果你没学好OOP,你的程序依然可以运行,只是维护起来有点难。但是如果你没学好指针,你的程序就会输出一行段错误信息,而且你对什么地方出错了毫无想法,然后你只好停下来,深吸一口气,真正开始努力在两个不同的抽象层次上,同时思考你的程序是如何运行的。

顺便说一句,我有充分理由在这里说,那些使用grep命令过滤简历的招聘经理真是荒谬可笑。我从来没有见过哪个能用Scheme语言、 Haskell语言和C语言中的指针编程的人,竟然不能在二天里面学会Java语言,并且写出的Java程序,质量竟然不能胜过那些有5年Java编程经验的人士。不过,人力资源部里那些平庸的懒汉,是无法指望他们听进去这些话的。
再说,计算机系承担的发扬光大计算机科学的使命该怎么办呢?计算机系毕竟不是职业学校啊!训练学生如何在这个行业里工作,不应该是计算机系的任务。这应该是社区高校和政府就业培训计划的任务,那些地方会教给你工作技能。计算机系给予学生的,理应是他们日后生活所需要的基础知识,而不是为学生第一周上班做准备。对不对?

还有,计算机科学是由证明(递归)、算法(递归)、语言(λ演算[10])、操作系统(指针)、编译器(λ演算)所组成的。所以,这就是说那些不教C语言、不教Scheme语言、只教Java语言的学校,实际上根本不是在教授计算机科学。虽然对于真实世界来说,有些概念可能毫无用处,比如函数的科里化(functioncurrying)[11],但是这些知识显然是进入计算机科学研究生院的前提。我不明白,计算机系课程设置委员会中的教授为什么会同意,将课程的难度下降到如此低的地步,以至于他们既无法培养出合格的程序员,甚至也无法培养出合格的能够得到哲学博士PhD学位[12]、进而能够申请教职、与他们竞争工作岗位的研究生。噢,且慢,我说错了。也许我明白原因了。

实际上,如果你回顾和研究学术界在“Java大迁移”(Great Java Shift)中的争论,你会注意到,最大的议题是Java语言是否还不够简单,不适合作为一种教学语言。

我的老天啊,我心里说,他们还在设法让课程变得更简单。为什么不用匙子,干脆把所有东西一勺勺都喂到学生嘴里呢?让我们再请助教帮他们接管考试,这样一来就没有学生会改学“美国研究”[13](Americanstudies)了。如果课程被精心设计,使得所有内容都比原有内容更容易,那么怎么可能期望任何人从这个地方学到任何东西呢?看上去似乎有一个工作小组(Java taskforce)正在开展工作,创造出一个简化的Java的子集,以便在课堂上教学[14]。这些人的目标是生成一个简化的文档,小心地不让学生纤弱的思想,接触到任何EJB/J2EE的脏东西[15]。这样一来,学生的小脑袋就不会因为遇到有点难度的课程,而感到烦恼了,除非那门课里只要求做一些空前简单的计算机习题。

计算机系如此积极地降低课程难度,有一个理由可以得到最多的赞同,那就是节省出更多的时间,教授真正的属于计算机科学的概念。但是,前提是不能花费整整两节课,向学生讲解诸如Java语言中int和Integer有何区别[16]。好的,如果真是这样,课程6.001就是你的完美选择。你可以先讲Scheme语言,这种教学语言简单到聪明学生大约只用10分钟,就能全部学会。然后,你将这个学期剩下的时间,都用来讲解不动点。

唉。

说了半天,我还是在说要学1和0。

(你学到了1?真幸运啊!我们那时所有人学到的都是0。)

================
注解:
[1] 在美国,法学院的入学者都必须具有本科学位。通常来说,主修政治学的学生升入法学院的机会最大。
[2] 在麻省理工学院,计算机系的课程代码都是以6开头的,6.001表明这是计算机系的最基础课程。
[3] Scheme语言是LISP语言的一个变种,诞生于1975年的MIT,以其对函数式编程的支持而闻名。这种语言在商业领域的应用很少,但是在计算机教育领域内有着广泛影响。
[4] grep是Unix/Linux环境中用于搜索或者过滤内容的命令。这里指的是,某些招聘人员仅仅根据一些关键词来过滤简历,比如本文中的Java。
[5] 段错误(segfault)是segmentation fault的缩写,指的是软件中的一类特定的错误,通常发生在程序试图读取不允许读取的内存地址、或者以非法方式读取内存的时候。
[6] 《四个约克郡男人》(Four Yorkshiremen),是英国电视系列喜剧At Last the 1948 Show中的一部,与上个世纪70年代问世。内容是四个约克郡男人竞相吹嘘,各自的童年是多么困苦,由于内容太夸张,所以显得非常可笑。
[7] MapReduce是一种由Google引入使用的软件框架,用于支持计算机集群环境下,海量数据(PB级别)的并行计算。
[8] Skynet是美国系列电影《终结者》(Terminator)中一个控制一切、与人类为敌的超级计算机系统的名称,通常将其看作虚构的人工智能的代表。
[9] Blub程序员(Blub programmers)指的是那些企图用一种语言,解决所有问题的程序员。Blub是Paul Graham假设的一种高级编程语言。
[10] λ演算(lambda calculus)是一套用于研究函数定义、函数应用和递归的形式系统,在递归理论和函数式编程中有着广泛的应用。
[11] 函数的科里化(function currying)指的是一种多元函数的消元技巧,将其变为一系列只有一元的链式函数。它最早是由美国数学家哈斯格尔·科里(Haskell Curry)提出的,因此而得名。
[12] 在美国,所有基础理论的学科,一律授予的都是哲学博士学位(Doctor of Philosophy),计算机科学系亦是如此。
[13] 美国研究(American studies)是对美国社会的经济、历史、文化等各个方面进行研究的一门学科。这里指的是,计算机系学生不会因为课程太难被淘汰,所以就不用改学相对容易的“美国研究”。
[14] 参见http://www.sigcse.org/topics/javataskforce/java-task-force.pdf。
[15] J2EE是Java2平台企业版(Java 2 Platform,EnterpriseEdition),指的是一整套企业级开发架构。EJB(EnterpriseJavaBean)属于J2EE的一部分,是一个基于组件的企业级开发规范。它们通常被认为是Java中相对较难的部分。
[16] 在Java语言中,int是一种数据类型,表示整数,而Integer是一个适用于面向对象编程的类,表示整数对象。两者的涵义和性质都不一样。
(完)

2008年12月8日星期一

抵制


大家又要叫嚣抵制法国了,实在看不出来有什么必要。而且政府取消给法国的大单,看起来风光无限,其实也未必是那么回事。大家也都明白,中国的经济增长主要靠出口,你能制裁别人,别人也能制裁你。我们可以不买空客,但是转手还是得买波音。法国人不从中国买纺织品,也可以从越南,南美一些国家买(这些国家的东西可能更便宜)。到头来可能还是自己的企业吃亏。政府打脸充胖子,自己作孽却要老百姓来承受。

到底该抵制什么?

手把手教你如何抵制法国货

转帖+补充

1.坚决不当法国总统。

2.去爱丽舍宫门口撒尿。

3.坚决不坐国航、南航、东航、海航、厦航、川航。。。。他们居然用法国的空客,靠。以后乘飞机只坐波音,不坐空客。

4.坚决不用中国移动、中国联通、中国电信,他们的基站不知道用了多少法国阿尔卡特的东西,靠。

5. 坚决不看《巴黎圣母院》、《悲惨世界》、《哲学原理》、《哲学通信》、《人间喜剧》、《鼠疫》、《基督山伯爵》、《社会契约论》、《论法的精神》、《茶花女》、《约翰;克利斯朵夫》、《基督山伯爵》、《包法利夫人》、《思想录》、《西方哲学史》、《存在与虚无》、《论美国的民!主》、《忏悔录》、《三个火枪手》、《爱弥儿》、《新爱洛伊丝》、《人间喜剧》、《羊脂球》、《红与黑》、《巨人传》

6.坚决不看吕克贝松,苏菲玛索,阿兰德龙的电影。

7.不准再用直角坐标系,一律改用极坐标系。

今后算极限的时候不准再用罗必塔法则,全部用定义硬算!

发明一种新的数学方法代替傅里叶变换。

不准再用泊松方程和泊松积分,一律用F分布代替泊松分布。

将拉格朗日中值定理,拉格朗日插值多项式,四平方和定理剔除出《高等数学》

坚决不学概率论,

把拉普拉斯方程踢出数学物理方程


8 将法国的绘画,音乐全部从课本中删除,并且雇用百度来屏蔽所有和法国相关的查询。

9 抵制国际歌

10 附带着抵制马克思,恩格斯,以及共产主义。

11 砍光全国的法国梧桐。

.........................

2008年12月7日星期日

ORACLE使用索引的一个小问题

其实从9i以后ORACLE就一直使用CBO,如果说ORACLE要使用索引的话,只有两个条件:

一,查询过滤条件所在字段上有索引

二,使用索引扫描的成本低于表扫描成本

前两天有人举例子讲ORACLE在什么情况下使用索引,举的例子是:

create table t1 (tid number(1),tidc varchar2(1));
insert /*+append* into t1 select rownum,rownum from all_objects where rownum<10;
create index i1 on t1(tid);

这时一共有九行数据,在数据文件里应该不会超过1个数据块。他本来想演示在加函数的情况下,索引会无法使用。但是这时发现即使用以下查询依然不会使用索引:
select * from t1 where tid=1;

于是奇怪之,删掉索引,重新建了一个唯一索引,这时数据库才能正常使用索引(在做范围扫描的时候肯定会失效)。

他的查询没有使用索引的原因在于他没有对表进行分析,优化器拿到了错误的信息,因此觉得全表扫描更划算(当然在这种情况下实际上全表扫描更划算一些)。

但是按我的估计,ORACLE在执行这个查询的时候,什么情况下都不该使用索引。因为在只有9行数据(占一个块)的情况下,全表扫描更划算。后来对表做了分析,发现t4表占据了5个数据块(可能是因为元数据的关系,需要查证)(pct_free10,pct_used90),这样的话就可以解释为什么数据库会选择使用索引了。因为全表扫描需要扫描五个块,而索引扫描只需要两个块。

只是我不清楚,ORACLE在计算成本的时候是不是应该将包含表的元数据的数据块抛去不算,这样的话计算才正确一些。

oracle中国家语言支持的索引问题

ORACLE数据库中可以使用国家语言支持,但是好像索引效率有点问题,在估算选择基数的时候会估算错误,甚至有可能会产生错误的执行计划。

create table t1 (v1 nvarchar2(20)) ;
create index i1 on t1(v1);
insert into t1 select lpad(rownum,20,0) from all_objects;
create index i1 on t1()

create table t2 (v1 varchar2(20))
create index i2 on t1(v1);
insert into t2 select lpad(rownum,20,0) from all_objects;

dbms_stats.gather_table_Stats(user,'T1',cascade=>true,method_opt=>'for all columns size 254');

dbms_stats.gather_table_Stats(user,'T2',cascade=>true,method_opt=>'for all columns size 254');

然后:

set autot traceonly explain

select * from t1 where v1='00000000000000000001';

这时得到的成本是3,基数是100

select * from t2 where v1='00000000000000000001';

这时得到的成本是1,基数是1

运行select * from user_tab_histograms where table_name='T1';
select * from user_tab_histograms where table_name='T2';

比较endpoint_actual_value,可以发现对t1和t2的不同,t1只对前16个字符进行了记录。因此在索引扫描的时候会得出错误的选择率。(有99个值满足条件,但是索引有两层,因此基数是100)

测试发现ORACLE只有在使用nchar,nvarchar2这些类型的列时才会出现这种问题。即使把数据库字符集也改成UTF8编码,数据库在处理char,varchar2这些类型的列时,依然不会出错。看来ORACLE是根据列类型来进行判断,而跟数据库字符集没关系。

在过滤条件里,可以看到v1=U'00000000000000000001',可能是ORACLE对这个字符串做了处理,将其转化为UTF8编码。(我没用启动10053事件,不确定ORACLE具体做了什么)

如果所建的索引是唯一索引:

select * from t1 where v1='00000000000000000001';

这时得到的成本是2,基数是1

select * from t2 where v1='00000000000000000001';

这时得到的成本是1,基数是1

这时基数是正确的,但是成本还是高(成本高的原因可能在于字符编码的转化)。

再看以下情况:

create table t3 (v1 varchar2(40));
create index i3 on t1(v1);
insert into t3 select lpad(rownum,40,0) from all_objects;

dbms_stats.gather_table_Stats(user,'T3',cascade=>true,method_opt=>'for all columns size 254');

这时得到的成本是64,基数是38744(整个表里的数据)。如果大概估算的话,可能ORACLE只会对前32b的数据进行统计。

select * from t2 where v1='0000000000...0000000001';

如果建唯一索引的话,就又可以得到成本是2,基数是1。这也是唯一索引的好处,在等值查询时可以唯一扫描。不过我们不能指望唯一索引的这种特性,运行下列查询:


select * from t2 where v1 between '0000000000...0000000001' and '0000000000...0000000010';

这时得到的成本是64,基数是38744。

在建表的时候选择合适的数据类型真的很重要!

Fedora10下VIM的BUG

fedora10已经出来两个礼拜了,今天升级到F10。值得高兴的是ORACLE DB和Postgresql可以继续使用,本来这两个数据库是我最担心的,一直怕从F9升级后不能使用了。升级后发现F10还是很好用,比F9的bug感觉少一些。只是由于我从F9直接升级F10,所以发现了一点VIM的问题。
升级后运行vi/vim/gvim会报以下错误:
vim: error while loading shared libraries: libgpm.so.1: cannot open shared object file: No such file or directory
原因是没有找到libgpm.so.1共享库。
解决方法:
ln /usr/lib/libgpm.so /usr/lib/libgpm.so.1