-
Jun02
Oracle9i优化器介绍(下)
Posted in Database, 367 views
-
理解CBO访问路径
访问路径就是从数据库中检索数据的方式。通常来说,检索一个表中少量的数据行应该使用索引访问,但是检索大量数据时全表扫描可能优于索引。
全表扫描(Full Table Scans)全表扫描将读取HWM之下的所有数据块,访问表中的所有行,每一行都要经WHERE子句判断是否满足检索条件。当Oracle执行全表扫描时会按顺序读取每个块且只读一次,因此如果能够一次读取多个数据块,可以提高扫描效率,初始化参数DB_FILE_MULTIBLOCK_READ_COUNT用来设置在一次I/O中可以读取数据块的最大数量。
优化器何时会使用全表扫描
在以下情况中优化器会使用全表扫描:
1、无可用索引
如下面例子:
SELECT last_name, first_name
FROM employees
WHERE UPPER(last_name)='TOM'
last_name字段有索引,但在查询中使用了函数,因此该查询不会使用索引。如果想让这个查询走索引,则需要建立函数索引create index ind_upper_lastname on last_name (upper(last_name))。特别要注意的是隐式转换,比如colx字段是varchar2型但存放数字:where colx=123456,这时会发生隐式转换TO_NUMBER(colx),此时colx上的索引也会失效。2、大量数据
如果优化器认为查询将会访问表中绝大多数的数据块,此时就算索引是可用的也会使用全表扫描。
3、小表
如果一个表HWM之下的数据块比DB_FILE_MULTIBLOCK_READ_COUNT要少,只需要一次I/O就能扫完,则使用全表扫描要比使用索引的成本低,此时会使用全表扫描。
如果有这样小表访问频率又高,通常把它固定在内存中为好alter table table_name storage(buffer_pool keep)。4、并行
如果在表一级设置了较高的并行度,如alter table table_name parallel(degree 10),通常会使CBO错误的选择全表扫描。通常不建议在表级的设置并行。
并行查询通常可以提高全表扫描的性能,建议在语句级用HINTS来实现并行,如/*+full(table_name) parallel(table_name degree)*/。5、全表扫描hints
如果想强制优化器使用全表扫描可以用提示FULL。
I/O是针对数据块的而不是行
Oracle的I/O是针对数据块的,因此被访问的数据块所占的百分比将影响CBO是否选择全表扫描。通常一个数据块中存储着多条记录,被请求的记录要么聚集在少数几个块中,要么分散在大量的数据块中。
HWM(High Water Mark)
HWM是全表扫描范围的标记,每个全表扫描都要读到HWM位置。当表analyze之后可以在DBA_TABLES.BLOCKS查到HWM,当表被drop、truncate或者move之后,HWM将会被重置。需要注意的是,当一个表被大量删除记录之后,HWM下面的大量数据块是空的,此时若对此表进行全表扫描,Oracle仍然会读到HWM位置,会对全表扫描的性能产生极坏的影响。
Rowid扫描
Rowid就是一个记录在数据块中的位置,由于指定了记录在数据库中的精确位置,因此rowid是检索单条记录的最快方式。
如果通过rowid来访问表,Oracle首先需要获得被检索记录的rowid,Oracle可以在WHERE子句中得到rowid,但更多的是通过索引扫描来获得,然后Oracle基于rowid来定位被检索的每条记录。优化器何时使用Rowid
并不是每个索引扫描都伴随着rowid的访问,如果索引中包含了被访问的所有字段,则不再需要通过rowid来访问表。
注意:
Rowid是Oracle表示数据存储的内部方法,它可能会由于版本的改变而改变。不推荐通过在WHERE中指定rowid来访问数据,因为行迁移和行链接会导致rowid变化,exp/imp也会使rowid变化。
索引扫描
索引不仅包含被索引字段的值,还包含表中行的位置标识rowid,如果语句只检索索引字段,Oracle直接从索引中读取该值而不去访问表,如果语句通过索引检索其他字段值,则Oracle通过rowid访问表中记录。
索引扫描类型:
索引唯一扫描(Index Unique Scans)
索引范围扫描(Index Range Scans)
索引降序范围扫描(Index Range Scans Descending)
索引跳跃扫描(Index Skip Scans)
全索引扫描(Full Scans)
快速全索引扫描(Fast Full Index Scans)
索引连接(Index Joins)
位图连接(Bitmap Joins)1、索引唯一扫描
这种扫描通常发生在对一个主键字段或含有唯一约束的字段指定相等条件时,只有单行记录被访问。
2、索引范围扫描
索引范围扫描是检索数据的常用方式,返回的数据返照索引字段升序排列,字段值相同的则按照rowid升序排列。如果在语句中指定了order by字句,而且排序字段是索引字段时Oracle将忽略order by子句。
例如:
SQL> select * from t;
COLX COLY
--------------- ---------------
1 3
1 2
1 1
1 0
SQL> create index ind_t on t(coly);
SQL> set autotrace on
SQL> select * from t where coly>0;
COLX COLY
--------------- ---------------
1 1
1 2
1 3
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T'
2 1 INDEX (RANGE SCAN) OF 'IND_T' (NON-UNIQUE)没有使用order by结果集已经是按coly升序排列的。
SQL> set autotrace traceonly
SQL> select * from t where coly>0 order by coly;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T'
2 1 INDEX (RANGE SCAN) OF 'IND_T' (NON-UNIQUE)
可以看到执行计划中无SORT 步骤,说明Oracle忽略了order by子句。3、索引降序范围扫描
如果在order by中指定了索引是降序排列的,或者使用了index_desc提示,Oracle可能会使用索引降序范围扫描。
例如:
SQL> select /*+index_desc(t ind_t)*/colx,coly from t where coly<3;
COLX COLY
--------------- ---------------
1 2
1 1
1 0
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=4 Bytes=104)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=1 Card=4 Bytes=104)
2 1 INDEX (RANGE SCAN DESCENDING) OF 'IND_T' (NON-UNIQUE) (Cost=2 Card=1)4、索引跳跃式扫描
跳跃式扫描发生在复合索引中,它在逻辑上将索引分离为较小的子索引,当复合索引的某一个字段不在查询中指定时,它将被跳过,从而提高索引扫描的效率。可以使用index_ss提示强制使用跳跃扫描。举个例子:
SQL> select* from employees;SEX EMPLOYEE_ID ADDRESS
---- --------------- --------------------
F 98 ABC
F 100 ABC
F 102 ABC
F 104 ABC
M 101 ABC
M 103 ABC
M 105 ABC
SQL> create index ind_sex_empid on employees(sex,employee_id);
索引结构如下图所示:SQL>set autotrace traceonly
SQL>select/*+index_ss(employees ind_sex_empid)*/* from employees where employee_id=101;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=11)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (Cost=3 Card=1 Bytes=11)
2 1 INDEX (SKIP SCAN) OF 'IND_SEX_EMPID' (NON-UNIQUE) (Cost=2 Card=1)5、全索引扫描
如果要使用全索引扫描必须满足两个条件,一是查询涉及的字段都包含在索引中,二是至少一个索引字段具有非空属性。由于索引键的数据是有序的,因此全索引扫描可以用消除排序操作。全索引扫描只需要一次I/O。
select empno,ename from big_emp order by empno,ename;Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=11)1 0 INDEX (FULL SCAN) OF ‘BE_IX' (Cost=2 Card=1)
6、快速全索引扫描快速全索引扫描只访问索引本身,而不去访问表,因此只有查询涉及的字段都包含在索引中时才会使用快速全索引扫描。如果想使用快速全索引扫描查询所涉及的字段必须全部包含在索引中,而且索引中至少有一个字段具有非空属性。满足条件后可以使用index_ffs提示来强制使用快速全索引扫描,快速全索引扫描只适用于CBO。
快速全索引扫描并不能消除排序操作,因为索引键中的数据没有被排序。不同于全索引扫描,快速全索引扫描是通过多块读取的方式来读取整个索引的,并可以设置并行方式。7、索引连接
只有查询涉及的所有字段都包含在索引中,才会使用索引连接,此时只通过访问索引就能获得所有需要的数据,而不用访问表。索引连接只适用于CBO,且不能消除排序操作。
可以通过index_join提示来强制使用索引连接。8、位图连接
位图连接使用一个位图作为键,然后通过映射函数将比特位转换为rowid。只有Oracle9i企业版才支持位图索引和位图索引连接。
Sample Table ScansSample table scan是随机检索表中的数据,当FROM后面有SAMPLE或SAMPLE BLOCK子句时,会执行Sample table scan。
如:SELECT * FROM employees SAMPLE BLOCK (1);CBO如何选择访问路径
CBO首先检查WHERE子句中的条件以及FROM子句,确定有哪些访问路径是可用的。然后CBO使用这个访问路径产生一组可能的执行计划,再通过索引、表的统计信息评估每个计划的成本,最后优化器选择成本最低的一个。
例1:
SELECT *
FROM employees
WHERE last_name = 'JACKSON';
如果last_name具有唯一约束或者主键约束,优化器了解到只有一行数据被返回,这种情况下查询具有很强的选择性,优化很可能走唯一索引扫描。例2:
还是上面的语句,如果last_name不具有唯一约束或主键约束,优化器使用USER_TAB_COLUMNS.NUM_DISTINCT和USER_TABLES.NUM_ROWS的统计信息来评估查询的选择性,估算last_name为jackson的记录占了employees表的比例。例3:
SELECT *
FROM employees
WHERE employee_id < 7500;
评估这个查询的选择性时优化器使用WHERE子句中的边界值7500和employee_id字段的USER_TAB_COLUMNS.HIGH_VALUE、USER_TAB_COLUMNS.LOW_VALUE,优化器假定在最小值和最大值之间employee_id是平均分布的,优化器确定值小于7500的百分比,然后把这个值作为这个查询的选择性。例4:
SELECT *
FROM employees
WHERE employee_id < :e1;
优化器并不知道e1的值,绑定变量的值每次运行都可能不同,因此优化器不能使用前面的方法来评估含有绑定变量的查询的选择性,在这种情况下优化器会使用内部缺省值试探着估算一个选择性。例5:
SELECT *
FROM employees
WHERE employee_id BETWEEN :low_e AND :high_e;
优化器会将这句改写为:
employee_id >= :low_e
employee_id <= :high_e
然后优化器仍然是用内部缺省值来试探着为其评估一个选择性。例6:
SELECT *
FROM employees
WHERE employee_id BETWEEN 7500 AND 7800;
优化器会改写为:
employee_id >= 7500
employee_id <= 7800
优化器为每个条件独立的评估选择性(S1和S2),然后用下列公式计算BETWEEN的选择性:S=ABS(S1+S2-1)理解连接
CBO如何运行连接语句
为一个连接语句选择一个执行计划,优化器必须做出下列相关决策:
1、 访问路径优化器必须给连接语句中的每个表选择一个可用来检索数据的路径。
2、连接方法
Oracle必须为每对行源执行连接操作,连接的方法包括嵌套循环、排序合并、散列连接、笛卡尔积等。
3、连接顺序
如果需要连接的表多于两个,Oracle先连接其中两个表然后将其连接的结果与下一个表做连接,直到所有的表都被连接。
CBO如何选择连接方法
优化器评估每个连接方法的成本,然后选择成本最低的一个。如果一个返回多行的连接,优化器将考虑如下三个因素:
当返回大量的结果集(大于1万行),嵌套循环连接是效率很低的,优化器可能不会选择它。嵌套循环的成本主要在把外表中所有被选择的行与内表匹配的过程,CBO的连接顺序可以用ORDERED提示来改变。嵌套循环连接的成本计算公式:
cost = access cost of A + (access cost of B * number of rows from A)
如果你使用CBO,当返回大量结果集时使用散列连接效率是非常高的。散列连接的成本计算公式:
cost = (access cost of A * number of hash partitions of B) + access cost of B
如果你使用RBO,当返回大量结果集时排序合并连接的效率比较高。排序合并连接的成本主要在于把所有行源读到内存中,进行排序的过程,多块读取对排序合并连接会有所帮助。排序合并连接的成本计算公式:
cost = access cost of A + access cost of B + (sort cost of A + sort cost of B)
当数据是预先排序的,后面两个排序成本为0。CBO如何运行Anti-joins
SELECT * FROM employees
WHERE department_id NOT IN
(SELECT department_id FROM departments
WHERE location_id = 1700);
优化器缺省是用嵌套循环来处理anti-joins的,但是如果使用了MERGE_AJ、HASH_AJ、NL_AJ提示,NOT IN能够被转换为一个排序合并或hash anti-join。CBO如何运行Semi-joins
SELECT * FROM departments
WHERE EXISTS
(SELECT * FROM employees
WHERE departments.department_id = employees.department_id
AND employees.salary > 2500);
优化器缺省也是用嵌套循环来执行EXISTS的,也同样可以通过MERGE_SJ、HASH_SJ、NL_SJ提示来调整。通常建议把NOT IN用EXISTS来改写,但是当NOT IN的子查询中包含OR分支时不能转为EXISTS。嵌套循环连接
对于被连接的数据子集较小的情况,嵌套循环连接是个较好的选择。在嵌套循环中,内表被外表驱动,外表返回的每一行都要在内表中检索找到与它匹配的行,因此整个查询返回的结果集不能太大(大于1万不适合),要把返回子集较小表的作为外表(CBO默认外表是驱动表),而且在内表的连接字段上一定要有索引。当然也可以用ORDERED提示来改变CBO默认的驱动表,使用USE_NL(table_name1 table_name2)可是强制CBO执行嵌套循环连接。
散列连接
散列连接是CBO做大数据集连接时的常用方式,优化器使用两个表中较小的表(或数据源)利用连接键在内存中建立散列表,然后扫描较大的表并探测散列表,找出与散列表匹配的行。
这种方式适用于较小的表完全可以放于内存中的情况,这样总成本就是访问两个表的成本之和。但是在表很大的情况下并不能完全放入内存,这时优化器会将它分割成若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要有较大的临时段从而尽量提高I/O的性能。
也可以用USE_HASH(table_name1 table_name2)提示来强制使用散列连接。如果使用散列连接HASH_AREA_SIZE初始化参数必须足够的大,如果是9i,Oracle建议使用SQL工作区自动管理,设置WORKAREA_SIZE_POLICY为AUTO,然后调整PGA_AGGREGATE_TARGET即可。排序合并连接
通常情况下散列连接的效果都比排序合并连接要好,然而如果行源已经被排过序,在执行排序合并连接时不需要再排序了,这时排序合并连接的性能会优于散列连接。可以使用USE_MERGE(table_name1 table_name2)来强制使用排序合并连接。
以下情况Oracle可能会选择使用排序合并连接:
l 两个表做非等值连接
l OPTIMIZER_MODE被设置成RULE
l HASH_JOIN_ENABLE设置成FALSE
l 已经事先排过序,优化器认为使用排序合并连接的成本要比散列连接低。
l HASH_AREA_SIZE和SORT_AREA_SIZE设置太小,优化器认为散列连接成本过高。外连接
不论是嵌套循环外连接还是散列外连接,CBO不会根据成本去选择连接顺序,被驱动的表总是含有(+)的一方。
SQL> select /*+ordered use_nl(t1 t2)*/ t1.msisdn,t2.msisdn from t1,t2 where t1.msisdn(+)=t2.msisdn;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=100 Card=99 Bytes=2178)
1 0 NESTED LOOPS (OUTER) (Cost=100 Card=99 Bytes=2178)
2 1 INDEX (FULL SCAN) OF 'IND_T2' (NON-UNIQUE) (Cost=1 Card=99 Bytes=1089)
3 1 INDEX (RANGE SCAN) OF 'IND_T1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=11)虽然使用了ORDERED提示,试图以t1作为驱动表,可是由于是外连接,仍然是以t2作为驱动表。
换成散列连接也是一样:
SQL> select /*+ordered use_hash(t1 t2)*/ t1.msisdn,t2.msisdn from t1,t2 where t1.msisdn(+)=t2.msisdn;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=99 Bytes=2178)
1 0 HASH JOIN (OUTER) (Cost=4 Card=99 Bytes=2178)
2 1 INDEX (FULL SCAN) OF 'IND_T2' (NON-UNIQUE) (Cost=1 Card=99 Bytes=1089)
3 1 INDEX (FAST FULL SCAN) OF 'IND_T1' (NON-UNIQUE) (Cost=2 Card=999 Bytes=10989)
此时,外表为t1,内表为t2,连接保留t1表与t2不匹配的行,然后用t1构建散列表,最后由t2表去探测t1生成的散列表。Full Outer Joins
select t1.msisdn,t2.msisdn
from t1
full outer join t2
on t1.msisdn=t2.msisdn
order by t2.msisdnExecution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=15 Card=1000 Bytes=36000)
1 0 SORT (ORDER BY) (Cost=15 Card=1000 Bytes=36000)
2 1 VIEW (Cost=6 Card=1000 Bytes=36000)
3 2 UNION-ALL
4 3 NESTED LOOPS (OUTER) (Cost=2 Card=999 Bytes=21978)
5 4 INDEX (FAST FULL SCAN) OF 'IND_T1' (NON-UNIQUE) (Cost=2 Card=999 Bytes=10989)
6 4 INDEX (RANGE SCAN) OF 'IND_T2' (NON-UNIQUE)
7 3 HASH JOIN (ANTI) (Cost=4 Card=1 Bytes=22)
8 7 INDEX (FULL SCAN) OF 'IND_T2' (NON-UNIQUE) (Cost=1 Card=99 Bytes=1089)
9 7 INDEX (FAST FULL SCAN) OF 'IND_T1' (NON-UNIQUE) (Cost=2 Card=999 Bytes=10989)
从执行计划看,实际上就是做了两个外连接,一个是t1.msisdn=t2.msisdn(+)走嵌套循环,一个是t1.msisdn(+)=t2.msisdn走散列连接,然后再UNION-ALL两个行集。
结果像下面这样:T1.MSISDN T2.MSISDN
------------- -----------
10 10
20 201
20 202
30 114
30 115
30 116
...
270
280
178
207一些和优化器相关的初始化参数
1、OPTIMIZER_FEATURES_ENABLE
每个版本的Oracle优化器特性都不相同,特别是做了版本升级以后一定要修改这个参数才可以使用仅被该版本支持的优化器特性。
可以赋予它的值如:9.2.0、9.0.2、9.0.1、8.1.7、8.1.6等。2、CURSOR_SHARING
这个参数会将SQL语句中的常量用变量来替换,存在大量常量的OLTP系统可以考虑启用这个参数。但是有一点要明白,绑定变量虽然可以使大量的SQL重用,减少分析时间,但是当数据分布发生变化后,CBO为绑定变量SQL生成的执行计划可能不是最优(不会考虑具体的变量值)。通常OLTP系统适用于绑定变量,OLTP系统特点是,SQL运行频繁且时间相对较短,SQL的分析时间比重较大。如果在DSS系统中,SQL运行时间长,相比之下分析时间微不足道,好的执行计划才是最重要的,因此DSS系统绑定变量要另行考虑。3、HASH_AREA_SIZE
这是散列连接时散列表的存放区域,如果使用散列连接这个参数值不能过小,如果散列表不能完全存放于内存中,对散列连接性能的影响很大。如果是9i建议启动工作区自动管理,然后设置PGA_AGGREGATE_TARGET。4、SORT_AREA_SIZE
内存排序区的大小,如果排序时内存区不够会写如磁盘。9i同样建议启动工作区自动管理,然后设置PGA_AGGREGATE_TARGET。5、HASH_JOIN_ENABLED
如果启用这个参数,CBO在考虑连接方法的时候将会考虑散列连接。6、OPTIMIZER_INDEX_CACHING
这个参数表示被缓存的索引块所占百分比,可选值的范围是0-100。这个值会影响嵌套循环连接,如果这个值设得较高,CBO将更倾向使用嵌套循环。7、OPTIMIZER_INDEX_COST_ADJ
优化器利用这个参数(是个百分比)把索引扫描的成本转换为等价的全表扫描的成本,然后与全表扫描的成本进行比较。缺省值100,表示索引扫描成本与全表扫描成本等价。可选值范围是0-10000。8、OPTIMIZER_MAX_PERMUTATIONS
这个初始参数用来设定优化器最多考虑多少种连接顺序,优化器不断的产生可能的表的连接的排列,直到排列数达到参数optimizer_max_permutations为止。一旦优化器停止产生新的可能连接排列,它将会从中选择出成本最小的排列。9、DB_FILE_MULTIBLOCK_READ_COUNT
这个参数表示在全表扫描或索引快速全扫描时一次I/O读的连续数据块数量(block#连续,且一次I/O不能超过extent)。10、OPTIMIZER_MODE
优化器模式,也是优化器的优化目标。值为:RULE、CHOOSE、ALL_ROWS、FIRST_ROWS_n、FIRST_ROWS。11、PARTITION_VIEW_ENABLED
如果设置为TRUE, 该优化器将跳过分区视图中未被请求的分区,该参数还能更改基于成本的优化程序从基础表统计信息计算分区视图统计信息的方式。12、QUERY_REWRITE_ENABLE
如果设置为TRUE,优化器将利用可用的物化视图来重写SQL。 -
No Comments »
-
Jun02
笔记:重建用户下所有索引
Posted in Database, 434 views
-
查找所有user1下的index
select * from user_indexes;重建index
alter index myindex rebuild;用动态sql产生alter语句:
spool e:\doc\sql\index.sql;
select 'alter index '|| index_name ||' rebuild;' from user_indexes;
spool off@e:\doc\sql\index.sql;
-
No Comments »
-
May30
笔记:查询oracle被锁的对象
Posted in Database, 455 views
-
select a.object_name objectname,b.session_id,c.serial#,c.program program,c.username username,c.command,c.machine machine,c.lockwait from all_objects a,v$locked_object b,v$session c where a.object_id =b.object_id and c.sid=b.session_id
-
No Comments »
-
May23
-
今天,一个平时没多少访问量的站点数据库连接池在几分钟内爆满,导致不能正常访问
# netstat
tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:3024 TIME_WAIT
tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:3034 TIME_WAIT
tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:4342 TIME_WAIT
tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:1524 TIME_WAIT
……
netstat发现大量类似的连接信息,特征是:
1、都来自123.114.11.31
2、都处于TIME_WAIT状态
3、每个端口都不同,在1000-5000间浮动用netstat -p查看连接的pid,再用ps -aux,看到pid对应是resin的进程
怀疑遭到攻击
用iptables封掉该ip
# /sbin/iptables -A INPUT -s 123.114.17.31 -j REJECT再用netstat查看
tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:1626 LAST_ACK -tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:1115 LAST_ACK - tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:2139 LAST_ACK -tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:2118 LAST_ACK - tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:1606 LAST_ACK - tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:4934 LAST_ACK - tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:1092 LAST_ACK - tcp 1 11209 HOST.localdomai:http ::ffff:123.114.17.31:1356 LAST_ACK -
所有的TIME_WAIT变成了LAST_ACK,同时数据库连接池也恢复正常,网站恢复访问。
-
No Comments »
-
May19
StarCraft2
Posted in Games, 418 views
-
我最期待的游戏排行榜第二名:StarCraft2,今天正式发布
在WOW之外,终于又有了吸引眼球的东西
更为期待的Fallout3什么时候来临呢

-
No Comments »
-
May17
Java日期操作常用方法
Posted in Program, 510 views
-
取得指定月份的第一天与取得指定月份的最后一天
http://iamin.blogdriver.com/iamin/847990.html
/**
* 取得指定月份的第一天
*
* @param strdate String
* @return String
*/
public String getMonthBegin(String strdate)
{
java.util.Date date = parseFormatDate(strdate);
return formatDateByFormat(date,"yyyy-MM") + "-01";
}/**
* 取得指定月份的最后一天
*
* @param strdate String
* @return String
*/
public String getMonthEnd(String strdate)
{
java.util.Date date = parseFormatDate(getMonthBegin(strdate));
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.MONTH,1);
calendar.add(Calendar.DAY_OF_YEAR, -1);
return formatDate(calendar.getTime());
}/**
* 常用的格式化日期
*
* @param date Date
* @return String
*/
public String formatDate(java.util.Date date)
{
return formatDateByFormat(date,"yyyy-MM-dd");
}/**
* 以指定的格式来格式化日期
*
* @param date Date
* @param format String
* @return String
*/
public String formatDateByFormat(java.util.Date date,String format)
{
String result = "";
if(date != null)
{
try
{
SimpleDateFormat sdf = new SimpleDateFormat(format);
result = sdf.format(date);
}
catch(Exception ex)
{
LOGGER.info("date:" + date);
ex.printStackTrace();
}
}
return result;
}
---------------------------------------------------------------package com.app.util;
/**
* 日期操作
*
* @author xxx
* @version 2.0 jdk1.4.0 tomcat5.1.0 * Updated Date:2005/03/10
*/
public class DateUtil {
/**
* 格式化日期
*
* @param dateStr
* 字符型日期
* @param format
* 格式
* @return 返回日期
*/
public static java.util.Date parseDate(String dateStr, String format) {
java.util.Date date = null;
try {
java.text.DateFormat df = new java.text.SimpleDateFormat(format);
String dt=Normal.parse(dateStr).replaceAll(
"-", "/");
if((!dt.equals(""))&&(dt.length()dt+=format.substring(dt.length()).replaceAll("[YyMmDdHhSs]","0");
}
date = (java.util.Date) df.parse(dt);
} catch (Exception e) {
}
return date;
}public static java.util.Date parseDate(String dateStr) {
return parseDate(dateStr, "yyyy/MM/dd");
}public static java.util.Date parseDate(java.sql.Date date) {
return date;
}public static java.sql.Date parseSqlDate(java.util.Date date) {
if (date != null)
return new java.sql.Date(date.getTime());
else
return null;
}public static java.sql.Date parseSqlDate(String dateStr, String format) {
java.util.Date date = parseDate(dateStr, format);
return parseSqlDate(date);
}public static java.sql.Date parseSqlDate(String dateStr) {
return parseSqlDate(dateStr, "yyyy/MM/dd");
}public static java.sql.Timestamp parseTimestamp(String dateStr,
String format) {
java.util.Date date = parseDate(dateStr, format);
if (date != null) {
long t = date.getTime();
return new java.sql.Timestamp(t);
} else
return null;
}public static java.sql.Timestamp parseTimestamp(String dateStr) {
return parseTimestamp(dateStr, "yyyy/MM/dd HH:mm:ss");
}/**
* 格式化输出日期
*
* @param date
* 日期
* @param format
* 格式
* @return 返回字符型日期
*/
public static String format(java.util.Date date, String format) {
String result = "";
try {
if (date != null) {
java.text.DateFormat df = new java.text.SimpleDateFormat(format);
result = df.format(date);
}
} catch (Exception e) {
}
return result;
}public static String format(java.util.Date date) {
return format(date, "yyyy/MM/dd");
}/**
* 返回年份
*
* @param date
* 日期
* @return 返回年份
*/
public static int getYear(java.util.Date date) {
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(date);
return c.get(java.util.Calendar.YEAR);
}/**
* 返回月份
*
* @param date
* 日期
* @return 返回月份
*/
public static int getMonth(java.util.Date date) {
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(date);
return c.get(java.util.Calendar.MONTH) + 1;
}/**
* 返回日份
*
* @param date
* 日期
* @return 返回日份
*/
public static int getDay(java.util.Date date) {
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(date);
return c.get(java.util.Calendar.DAY_OF_MONTH);
}/**
* 返回小时
*
* @param date
* 日期
* @return 返回小时
*/
public static int getHour(java.util.Date date) {
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(date);
return c.get(java.util.Calendar.HOUR_OF_DAY);
}/**
* 返回分钟
*
* @param date
* 日期
* @return 返回分钟
*/
public static int getMinute(java.util.Date date) {
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(date);
return c.get(java.util.Calendar.MINUTE);
}/**
* 返回秒钟
*
* @param date
* 日期
* @return 返回秒钟
*/
public static int getSecond(java.util.Date date) {
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(date);
return c.get(java.util.Calendar.SECOND);
}/**
* 返回毫秒
*
* @param date
* 日期
* @return 返回毫秒
*/
public static long getMillis(java.util.Date date) {
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTime(date);
return c.getTimeInMillis();
}/**
* 返回字符型日期
*
* @param date
* 日期
* @return 返回字符型日期
*/
public static String getDate(java.util.Date date) {
return format(date, "yyyy/MM/dd");
}/**
* 返回字符型时间
*
* @param date
* 日期
* @return 返回字符型时间
*/
public static String getTime(java.util.Date date) {
return format(date, "HH:mm:ss");
}/**
* 返回字符型日期时间
*
* @param date
* 日期
* @return 返回字符型日期时间
*/
public static String getDateTime(java.util.Date date) {
return format(date, "yyyy/MM/dd HH:mm:ss");
}/**
* 日期相加
*
* @param date
* 日期
* @param day
* 天数
* @return 返回相加后的日期
*/
public static java.util.Date addDate(java.util.Date date, int day) {
java.util.Calendar c = java.util.Calendar.getInstance();
c.setTimeInMillis(getMillis(date) + ((long) day) * 24 * 3600 * 1000);
return c.getTime();
}/**
* 日期相减
*
* @param date
* 日期
* @param date1
* 日期
* @return 返回相减后的日期
*/
public static int diffDate(java.util.Date date, java.util.Date date1) {
return (int) ((getMillis(date) - getMillis(date1)) / (24 * 3600 * 1000));
}
} -
1 Comment »
-
May14
如何保持Linux服务器间的文件同步
Posted in Linux, 417 views
-
本文详细介绍rsync服务的安装配置以及如何利用rsync保持Linux服务器间的文件同步。
服务器之间常常要保持些文件或目录的一致,比如一些大的软件下载网站,它们通常使用多台服务器来提供下载服务。当一台服务器上的文件更新后,其它的服务器也需要更新,而且 在更新的时候应该是只对新增或是修改过的文件进行更新,否则会造成网络带宽和时间的浪费。rsync就是能有效的保持文件及目录的一致的优秀软件。
rsync,remote synchronize
顾名思意就知道它是一款实现远程同步功能的软件,它在同步文件的同时,可以保持原来文件的权限、时间、软硬链接等附加信息,而且可以通过ssh方式来传输文件,这样其保密性也非常好,另外它还是免费的软件。rysnc的官方网站:http://rsync.samba.org/,可以从上面得到最新的版本。当然,因为rsync是一款如此有用的软件,所以很多Linux的发行版本都将它收录在内了。你的Linux里并没有安装rsync,你可以按以下的安法自行安装:
一、安装过程
1.下载rsync
目前(2003年9月)最新的rsync版本是2.5.6,从rysnc的官方网站上下载一个回来:
# wget http://ftp.samba.org/ftp/rsync/rsync-2.5.6.tar.gz
2.解压
# tar -xzpvf rsync-2.5.6.tar.gz
3.编译安装
# cd rsync-2.5.6/
# ./configure --prefix=/usr/local/rsync
# make
# make install以上过程没有出现的话就安装好了,现在就有rsync命令可以用了,rsync命令放在/usr/local/rsync/bin。用rsync命令可以去运行有rsync服务的服务器上抓取资料。
如果要把当前的机器变成一台rsync服务器的话,就需要继续进行一些配置了。
二、配置rsync服务
配置一个简单的rsync服务并不复杂,你需要修改或建立一些配置文件。
1.rsyncd.conf
# vi /etc/rsyncd.motd
rsyncd.con是rsync服务的主要配置文件,它控制rsync服务的各种属性,下面给出一个rsyncd.conf文件的例子:
#先定义整体变量
secrets file = /etc/rsyncd.secrets
motd file = /etc/rsyncd.motd
read only = yes
list = yes
uid = nobody
gid = nobody
hosts allow = 192.168.100.90 #哪些电脑可以访问rsync服务
hosts deny = 192.168.100.0/24 #哪些电脑不可以访问rsync服务
max connections = 2
log file = /var/log/rsyncd.log
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsync.lock#再定义要rsync目录
[terry]
comment = Terry 's directory from 192.168.100.21
path = /home/terry
auth users = terry,rsync[test]
comment = test rsync
path = /home/test在上面的配置文件中,限定了192.168.100.0/24这个子网中,只有192.168.100.90的机器可以来访问这台rsync服务器的rsync服务。配置文件的后面部分定义了两个rsync的目录,terry目录是只有知道terry、rsync两个账号的人才能使用的,而text目录是无需账号就可以访问的。rsync在定义目录时还提供了一些其它选项,可以作更严格的控制。
2.rsyncd.secrets
# vi /etc/rsyncd.secrets
rsyncd.secrets是存储rsync服务的用户名和密码的,它是一个明文的文本文件,下面给出一个rsyncd.secrets文件的例子:
terry:12345
rsync:abcde因为rsyncd.secrets存储了rsync服务的用户名和密码,所以非常重要,因此文件的属性必须设为600,只有所有者可以读写:
# chmod 600 /etc/rsyncd.secrets
3.rsyncd.motd
# vi /etc/rsyncd.motd
rsyncd.motd记录了rsync服务的欢迎信息,你可以在其中输入任何文本信息,如:
Welcome to use the rsync services!
4.services
# vi /etc/services
services并不是rsync的配置文件,这一步也可以不做。而修改了services文件的好处就在于系统知道873端口对就的服务名为rsync。修改services的方法就是确保services中有如下两行,没有的话就自行加入:
rsync 873/tcp # rsync
rsync 873/udp # rsync5./etc/xinetd.d/rsync
# vi /etc/xinetd.d/rsync
建立一个名为/etc/xinetd.d/rsync文件,输入以下内容:
service rsync
{
disable = no
socket_type = stream
wait = no
user = root
server = /usr/local/rsync/bin/rsync
server_args = --daemon
log_on_failure += USERID
}保存后,就可以运行rsync服务了。输入以下命令:
# /etc/rc.d/init.d/xinetd reload
这样rsync服务就在这台机器上(192.168.100.21)运行起来了,接下来就是如何来使用它了。
三、rsync命令的用法
在配置完rsync服务器后,就可以从客户端发出rsync命令来实现各种同步的操作。rsync有很多功能选项,下面就对介绍一下常用的选项:
rsync的命令格式可以为:
1. rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST
2. rsync [OPTION]... [USER@]HOST:SRC DEST
3. rsync [OPTION]... SRC [SRC]... DEST
4. rsync [OPTION]... [USER@]HOST::SRC [DEST]
5. rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST
6. rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]rsync有六种不同的工作模式:
1. 拷贝本地文件;当SRC和DES路径信息都不包含有单个冒号":"分隔符时就启动这种工作模式。
2.使用一个远程shell程序(如rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当DST路径地址包含单个冒号":"分隔符时启动该模式。
3.使用一个远程shell程序(如rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当SRC地址路径包含单个冒号":"分隔符时启动该模式。
4. 从远程rsync服务器中拷贝文件到本地机。当SRC路径信息包含"::"分隔符时启动该模式。
5. 从本地机器拷贝文件到远程rsync服务器中。当DST路径信息包含"::"分隔符时启动该模式。
6. 列远程机的文件列表。这类似于rsync传输,不过只要在命令中省略掉本地机信息即可。
下面以实例来说明:
# rsync -vazu -progress terry@192.168.100.21:/terry/ /home
v详细提示
a以archive模式操作,复制目录、符号连接
z压缩u只进行更新,防止本地新文件被重写,注意两者机器的时钟的同时
-progress指显示
以上命令是保持客户机192.168.100.90上的/home/terry目录和rsync服务器上的terry目录同步。该命令执行同步之前会要求你输入terry账号的密码,这个账号是我们前面在rsyncd.secrets文件中定义的。如果想将这条命令写到一个脚本中,然后定时执行它的话,可以使用--password-file选项,具体命令如下:
# rsync -vazu -progress --password-file=/etc/rsync.secret
terry@192.168.100.21:/terry/ /home要使用--password-file选项,就得先建立一个存放密码的文件,这里指定为/etc/rsync.secret。其内容很简单,如下:
terry:12345
同样要修改文件属性如下:
# chmod 600 /etc/rsyncd.secrets
四、利用rsync保持Linux服务器间的文件同步实例
现在假设有两台Linux服务器A(192.168.100.21)和B(192.168.100.90),服务器A中的/home/terry和服务器B中的/home/terry这两个目录需要保持同步,也就是当服务器A中文件发生改变后,服务器B中的文件也要对应去改变。
我们按上面的方法,在服务器A上安装rsync,并将其配置为一台rsync服务器,并将/home/terry目录配置成rsync共享出的目录。然后在服务器B上安装rsync,因为B只做客户端,所以无需配置。然后在服务器B,建立以下脚本:
#!/bin/bash
/usr/loca/rsync/bin/rsync -vazu -progress --delete
--password-file=/etc/rsync.secret terry@192.168.100.21:/terry/ /home将这个脚本保存为AtoB.sh,并加上可执行属性:
# chmod 755 /root/AtoB.sh
然后,通过crontab设定,让这个脚本每30分钟运行一次。执行命令:
# crontab -e
输入以下一行:
0,30 * * * * /root/AtoB.sh
保存退出,这样服务器B每个小时的0分和30分时都会自动运行一次AtoB.sh,AtoB.sh是负责保持服务器B和服务器A同步的。这样就保证了服务器A的所有更新在30钟后,服务器B也一样取得了和服务器A一样的最新的资料。
五、其它应用
rsync除了同步文件及目录之外,还可以利用它来实现对远程网站的远程备份。如果再结合脚本和Crontab就能实现定时自动远程备份。其可以实现与商业化的备份和镜象产品的类似效果,但完全免费
-
No Comments »
-
May14
rsync 同步目录
Posted in Linux, 563 views
-
ssh-keygen -t rsa 回车,然后问你文件名,可以直接回车跳过,密码──如果你不想给KEY文件加密,可以回车跳过。建立完毕,把id_rsa.pub传到服务器上相应用户的.ssh目录下并命名成authorized_keys,同时确保本地~/.ssh/id_rsa文件的权限是-rw-------,应该就行了。
启动rsync服务
rsync --daemonrsync -vzru --progress --delete -e ssh yushin@192.168.0.1:/mnt/date/ /mnt/data
rsync有六种不同的工作模式:
1. 拷贝本地文件;当SRC和DES路径信息都不包含有单个冒号":"分隔符时就启动这种工作模式。
2.使用一个远程shell程序(如rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当DST
路径地址包含单个冒号":"分隔符时启动该模式。
3.使用一个远程shell程序(如rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当SRC
地址路径包含单个冒号":"分隔符时启动该模式。
4. 从远程rsync服务器中拷贝文件到本地机。当SRC路径信息包含"::"分隔符时启动该模式。
5. 从本地机器拷贝文件到远程rsync服务器中。当DST路径信息包含"::"分隔符时启动该模式。
6. 列远程机的文件列表。这类似于rsync传输,不过只要在命令中省略掉本地机信息即可。
下面以实例来说明:
# rsync -vazu -progress terry@192.168.100.21:/terry/ /home
v详细提示
a以archive模式操作,复制目录、符号连接
z压缩
u只进行更新,防止本地新文件被重写,注意两者机器的时钟的同时
-progress指显示
以上命令是保持客户机192.168.100.90上的/home/terry目录和rsync服务器上的terry目录同
步。该命令执行同步之前会要求你输入terry账号的密码,这个账号是我们前面在rsyncd.secrets
文件中定义的。如果想将这条命令写到一个脚本中,然后定时执行它的话,可以使用--password-file
选项,具体命令如下:
# rsync -vazu -progress --password-file=/etc/rsync.secret
terry@192.168.100.21:/terry/ /home
要使用--password-file选项,就得先建立一个存放密码的文件,这里指定为/etc/rsync.secret。
其内容很简单,如下:
terry:12345
同样要修改文件属性如下:
# chmod 600 /etc/rsyncd.secrets四、利用rsync保持Linux服务器间的文件同步实例
现在假设有两台Linux服务器A(192.168.100.21)和B(192.168.100.90),服务器A中的
/home/terry和服务器B中的/home/terry这两个目录需要保持同步,也就是当服务器A中文件发生
改变后,服务器B中的文件也要对应去改变。
我们按上面的方法,在服务器A上安装rsync,并将其配置为一台rsync服务器,并将/home/terry
目录配置成rsync共享出的目录。然后在服务器B上安装rsync,因为B只做客户端,所以无需配置。
然后在服务器B,建立以下脚本:
#!/bin/bash
/usr/loca/rsync/bin/rsync -vazu -progress --delete
--password-file=/etc/rsync.secret terry@192.168.100.21:/terry/ /home
将这个脚本保存为AtoB.sh,并加上可执行属性:
# chmod 755 /root/AtoB.sh
然后,通过crontab设定,让这个脚本每30分钟运行一次。执行命令:
# crontab -e
输入以下一行:
0,30 * * * * /root/AtoB.sh
保存退出,这样服务器B每个小时的0分和30分时都会自动运行一次AtoB.sh,AtoB.sh是负责
保持服务器B和服务器A同步的。这样就保证了服务器A的所有更新在30钟后,服务器B也一样取
得了和服务器A一样的最新的资料。五、其它应用
rsync除了同步文件及目录之外,还可以利用它来实现对远程网站的远程备份。如果再结合脚本和Crontab就能实现定时自动远程备份。其可以实现与商业化的备份和镜象产品的类似效果,但完全免费。
远程拷贝:/usr/bin/rsync -a 192.168.0.1::remotedic /home2/html/localdic -
No Comments »
-
Apr10
Super Mario! 超级变态关卡
Posted in View, 426 views
-
No Comments »
-
Apr10
Hibernate包作用详解
Posted in Program, 375 views
-
Hibernate一共包括了23个jar包,令人眼花缭乱。本文将详细讲解Hibernate每个jar包的作用,便于你在应用中根据自己的需要进行取舍。
下载Hibernate,例如2.0.3稳定版本,解压缩,可以看到一个hibernate2.jar和lib目录下有22个jar包:
hibernate2.jar:
Hibernate的库,没有什么可说的,必须使用的jar包cglib-asm.jar:
CGLIB库,Hibernate用它来实现PO字节码的动态生成,非常核心的库,必须使用的jar包dom4j.jar:
dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。dom4j是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在IBM developerWorks上面可以找到一篇文章,对主流的Java XML API进行的性能、功能和易用性的评测,dom4j无论在那个方面都是非常出色的。我早在将近两年之前就开始使用dom4j,直到现在。如今你可以看到越来越多的Java软件都在使用dom4j来读写XML,特别值得一提的是连Sun的JAXM也在用dom4j。这是必须使用的jar包,Hibernate用它来读写配置文件。odmg.jar:
ODMG是一个ORM的规范,Hibernate实现了ODMG规范,这是一个核心的库,必须使用的jar包。commons-collections.jar:
Apache Commons包中的一个,包含了一些Apache开发的集合类,功能比java.util.*强大。必须使用的jar包。commons-beanutils.jar:
Apache Commons包中的一个,包含了一些Bean工具类类。必须使用的jar包。commons-lang.jar:
Apache Commons包中的一个,包含了一些数据类型工具类,是java.lang.*的扩展。必须使用的jar包。commons-logging.jar:
Apache Commons包中的一个,包含了日志功能,必须使用的jar包。这个包本身包含了一个Simple Logger,但是功能很弱。在运行的时候它会先在CLASSPATH找log4j,如果有,就使用log4j,如果没有,就找JDK1.4带的java.util.logging,如果也找不到就用Simple Logger。commons-logging.jar的出现是一个历史的的遗留的遗憾,当初Apache极力游说Sun把log4j加入JDK1.4,然而JDK1.4项目小组已经接近发布JDK1.4产品的时间了,因此拒绝了Apache的要求,使用自己的java.util.logging,这个包的功能比log4j差的很远,性能也一般。后来Apache就开发出来了commons-logging.jar用来兼容两个logger。因此用commons-logging.jar写的log程序,底层的Logger是可以切换的,你可以选择log4j,java.util.logging或者它自带的Simple Logger。不过我仍然强烈建议使用log4j,因为log4j性能很高,log输出信息时间几乎等于System.out,而处理一条log平均只需要5us。你可以在Hibernate的src目录下找到Hibernate已经为你准备好了的log4j的配置文件,你只需要到Apache 网站去下载log4j就可以了。commons-logging.jar也是必须的jar包。
使用Hibernate必须的jar包就是以上的这几个,剩下的都是可选的。
ant.jar:
Ant编译工具的jar包,用来编译Hibernate源代码的。如果你不准备修改和编译Hibernate源代码,那么就没有什么用,可选的jar包optional.jar:
Ant的一个辅助包。c3p0.jar:
C3PO是一个数据库连接池,Hibernate可以配置为使用C3PO连接池。如果你准备用这个连接池,就需要这个jar包。proxool.jar:
也是一个连接池,同上。commons-pool.jar, commons-dbcp.jar:
DBCP数据库连接池,Apache的Jakarta组织开发的,Tomcat4的连接池也是DBCP。实际上Hibernate自己也实现了一个非常非常简单的数据库连接池,加上上面3个,你实际上可以在Hibernate上选择4种不同的数据库连接池,选择哪一个看个人的偏好,不过DBCP可能更通用一些。另外强调一点,如果在EJB中使用Hibernate,一定要用App Server的连接池,不要用以上4种连接池,否则容器管理事务不起作用。
connector.jar:
JCA 规范,如果你在App Server上把Hibernate配置为Connector的话,就需要这个jar。不过实际上一般App Server肯定会带上这个包,所以实际上是多余的包。jaas.jar:
JAAS是用来进行权限验证的,已经包含在JDK1.4里面了。所以实际上是多余的包。jcs.jar:
如果你准备在Hibernate中使用JCS的话,那么必须包括它,否则就不用。jdbc2_0-stdext.jar:
JDBC2.0的扩展包,一般来说数据库连接池会用上它。不过App Server都会带上,所以也是多余的。jta.jar:
JTA规范,当Hibernate使用JTA的时候需要,不过App Server都会带上,所以也是多余的。junit.jar:
Junit包,当你运行Hibernate自带的测试代码的时候需要,否则就不用。xalan.jar, xerces.jar, xml-apis.jar:
Xerces是XML解析器,Xalan是格式化器,xml-apis实际上是JAXP。一般App Server都会带上,JDK1.4也包含了解析器,不过不是Xerces,是Crimson,效率比较差,不过Hibernate用XML只不过是读取配置文件,性能没什么紧要的,所以也是多余的。 -
No Comments »

Comments