2009年6月23日星期二

Oracle EM Console 更改hostname后无法启动

Oracle EM Console 比较弱智,改了机器名或者IP经常就不能正常启动,用这个命令行来重新配置EM Console吧

emca -config dbcontrol db

2009年5月14日星期四

Oracle三种常用Join

The three most commonly used joins are Indexed Nested Loops, Hash Join, and Sort-Merge Join.

Indexed Nested Loops

The Nested Loop join is an iterative join: for each row in the first (inner) row source, lookup matching rows in the second (outer) row source. If the nested lookup of the second row source performs a Unique or Range Index Scan, then we call this Indexed Nested Loops.

Indexed Nested Loops is used primarily in low volume joins; it is efficient over small volumes and versatile enough to be used in a variety of situations. Although it is fully scalable, Indexed Nested Loops is inefficient over large data volumes.

Hash Join

The hash join is used for high-volume equi-joins (joins with equals predicates). Oracle performs a single read of the smaller row source (call this T1) and builds a hash table in memory. The join key is used as the hash-key of the hash table. Then a single pass of the larger row source (call this T2) is performed, hashing the join key of each row to obtain an address in the hash table where it will find matching T1 rows.

Provided T1 remains small enough to build the hash table in memory, T2 can be scaled up to any arbitrarily large volume without affecting throughput or exceeding temp space. If T1 cannot be hashed in memory, then a portion of the hash-table spills to disk. When the hash table is probed by T2, the rows with join keys that match those parts of the in-memory hash table are joined immediately; the rest are written to TEMP and joined in a second pass. The bigger T1 is, the smaller the proportion of the hash table that can fit in memory, and the larger the proportion of T2 that must be scanned twice. This slows the Hash Join down considerably and also makes the join non-scalable.

Sort-Merge

A sort-merge join works by reading each row-source in the join separately; sorting both sets of results on the join column(s); then concurrently working through the two lists, joining the rows with matching keys. Sort-Merge is generally faster than Indexed Nested Loops but slower than Hash Join for equi-joins. It is used almost exclusively for non-equi joins (>, <, BETWEEN) and will occasionally be used when one of the row sources is pre-sorted (eg. a GROUP BY inline view)

If both row sources are small then they may both be sorted in memory, however large sorts will spill to disk making then non-scalable.

There is no way to make a Sort-Merge join scalable. The only other way to resolve a non-equijoin is to use Nested Loops, which is slower. As volumes increase, Sort-Merge will continue to out-perform Nested Loops, but will eventually run out of Temp space. The only solution is to extend TEMP, or convert the join to Nested Loops (and then wait).

2009年3月23日星期一

Don't cache Singleton object in a serializable object

不要在可序列化对象中缓存Singleton

如果你有一个对象其中某个字段保存了一个对Singleton的引用,那么这个对象在序列化读取后会导致在同一个虚拟机里Singleton对象有两个。比如下面的例子

public class Test3 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        System.out.println(ABC.getInstance());
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new ObjectOutputStream(out).writeObject(ABC.getInstance());
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
        ABC abc = (ABC) in.readObject();
        System.out.println(abc);
        
    }
}

class ABC implements Serializable {
    private ABC() {}
    private static ABC instance = new ABC();
    public static ABC getInstance() {
        return instance;
    }
}

你会发现,打印出的两个ABC的instance是不一样的。解决方法很简单,在ABC加入这个方法
    protected Object readResolve() {
        return instance;
    }

这样可以重置instance到这个虚拟机中的Singleton实例,还可以把引用instance的字段作为transient字段节省IO时间。
但是这不是最好的办法,最好就是,如果你知道ABC是Singleton,那么就永远不要把ABC赋值到你的对象成员变量里,getInstance()因为是static方法,编译器通常会做inline的,频繁调用不会导致频繁方法栈操作,所以缓存它意义不大。

2009年3月22日星期日

Literal Pitfall

我们过去经常使用这样的方式定义常量, 比如我最不喜欢的java.util.Calendar类里面定义月份有
public static final int     APRIL  =   3
public static final int     MAY    =   4
public static final int     JUNE   =   5
...

我相信很多人也是这样定义常量或者枚举型。其实这样会有一个很严重的问题,和编译器的行为有关系。VM Spec 2.17.4中描述类初始化的发生条件时提到ClassA的某个常量字段比如ClassA.MAX被访问的时候不会导致ClassA类被初始化。

2.17.4 Initialization
........
A class or interface type T will be initialized immediately before one of the following occurs:

    * T is a class and an instance of T is created.

    * T is a class and a static method of T is invoked.

    * A nonconstant static field of T is used or assigned. A constant field is one that is (explicitly or implicitly) both final and static, and that is initialized with the value of a compile-time constant expression. A reference to such a field must be resolved at compile time to a copy of the compile-time constant value, so uses of such a field never cause initialization.

原因是如果ClassB引用ClassA.MAX,编译器会把ClassA.MAX的常量值复制到ClassB的常量池中。这样显然效率更高。

我们做一个实验,有两个类
public class ConstClass {
    public static final int TEST = 5;
}

public class RefClass {
    public static void main(String[] args) {
        System.out.println(ConstClass.TEST);
    }
}
编译后,执行RefClass明显应该打印5,我们用javap看一下pesudo code:
public class RefClass extends java.lang.Object{
public RefClass();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   iconst_5
   4:   invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   7:   return

}

这里你会看到iconst_5,RefClass并没有让VM加载ConstClass,事实上,你删除ConstClass.class也没有关系。
问题来了,如果你这样定义常量或者枚举值,将来如果ClassA.MAX的值你需要更改,那么你必须重新编译所有引用过这个值的类!那些类需要重新编译,这是非常难预测的,尤其是被频繁使用的API.

如何克服呢?有两种方式,一种是提供一个getTEST()来返回常量值,比如把刚才的类改成
public class ConstClass {
    public static final int TEST = 5;
    public static int getTEST() {
        return TEST;
    }
}
public class RefClass {
    public static void main(String[] args) {
        System.out.println(ConstClass.getTEST());
    }
}
由于getTEST()是static,编译器可能会inline他,因此效率不会太低

还有一种方式,是jdk1.5之后提供的enum
让我们重新写这两个类
public enum ConstClass2 {
    TEST(5);    
    private int value;
    ConstClass2(int v) {
        this.value = v;
    }
    public int getValue() {
        return this.value;
    }
}

public class RefClass2 {
    public static void main(String[] args) {
        System.out.println(ConstClass2.TEST.getValue());
    }
}
在用javap看看引用类
public class RefClass2 extends java.lang.Object{
public RefClass2();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   getstatic       #3; //Field ConstClass2.TEST:LConstClass2;
   6:   invokevirtual   #4; //Method ConstClass2.getValue:()I
   9:   invokevirtual   #5; //Method java/io/PrintStream.println:(I)V
   12:  return

}
你会发现这次5没有被复制到引用类的常量池,相反getstatic代替了iconst_,相当于ClassA.MAX会被解释成ClassA.getMAX(),这样效果其实和上面说的另外一种方法getTEST()是类似的。

其实enum还有其他的好处,JDK guide中说 (http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html)

  • Not typesafe - Since a season is just an int you can pass in any other int value where a season is required, or add two seasons together (which makes no sense).
  • No namespace - You must prefix constants of an int enum with a string (in this case SEASON_) to avoid collisions with other int enum types.
  • Brittleness - Because int enums are compile-time
    constants, they are compiled into clients that use them. If a new
    constant is added between two existing constants or the order is
    changed, clients must be recompiled. If they are not, they will still
    run, but their behavior will be undefined.
  • Printed values are uninformative - Because they are
    just ints, if you print one out all you get is a number, which tells
    you nothing about what it represents, or even what type it is.

其中第三点就是本文描述的问题,另外typesafe也是个问题,比如你完全可以把Integer.MAX_VALUE 传给 Calendar.set(Integer.MAX_VALUE, somevalue),另外没有命名空间而且打印出来也很不友好。

所以,总之,还是enum吧?

2009年1月18日星期日

用 PhantomReference 避免OutOfMemory

PhantomReference比Weak/Soft的引用强度都要低,PhantomReference.get()总是返回null,为什么呢?

其实PhantomReference而要是不可缺少的重要引用类型, 我们知道WeakReference在finalize方法调用或被gc清理之前会进入ReferenceQueue, 这时候没有任何strong reference引用对象,然后可以通过ReferenceQueue去做收尾工作。但是在finalize()方法,或者重新给这个对象一个引用使它reachable(从某个活动线程调用栈,或者静态变量可达), 使这个对象延长生命周期暂时不会被gc清理。而PhantomReference只会在对象被从内存中清除后才会进入队列,get()总是返回null (ReferenceQueue通知一个PhantomReference的时候,既然内存都已经物理释放了,当然也无法给你一个对象,所以get总是返回null也有这个原因), 所以你没有办法重新使这个对象再次reachable。PhantomReference只是提供了一种方式让你跟踪一个曾经产生过的对象,由此让你知道这个对象到底有没有被物理的清除。

比如当你的applet程序需要处理一个非常大的图片的时候,你可能希望图片处理结束并且内存被释放之后再处理下一个图片。如果用PhantomReference来引用上一个图片对象,当ReferenceQueue通知你的时候,你就可以知道上一个内存对象已经被物理清除,你可以继续下一个大内存对象的处理了。这样就可以避免由于GC线程优先级低导致上一个大内存对象还没有释放下一个大内存对象又被创建,让OutOfMemoryError出现的概率低一些。

还有一个好处,就是PhantomReference比用finalize方法好的多,因为VM对finalize的处理不如PhantomReference简单可靠,只不过是你要写的代码稍微多一点点而已。


Ethan有一个更完整的对四种reference的解释 http://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html

2009年1月4日星期日

Oracle 的锁基础原理

Oracle有以下类别的锁,本文主要只讨论DML的锁

•DML locks (data locks)

•DDL locks (dictionary locks)

•Oracle Internal Locks/Latches

•Oracle Distributed Locks

•Oracle Parallell Cache Management Locks

DML的锁是针对并发数据访问的,因此分行级别TX和表级别TM

两种经典锁

共享锁S表示其他事务可以读取这个资源但是任何事务都不可以更改这个资源,任何事物都不可以再加X锁。

排他锁X表示其他事务可以读取资源,但只有当前事务可以更改这个资源,其他任何事物都不可以再加S或者X锁。

行级锁

也叫事务锁,简称TX锁。两种经典锁,行级别上面只支持X锁(排它锁),Oracle没有行级别S锁(共享锁)。所有DML操作一般自动生成行级锁,行级别锁理论上比表级别锁效率高,因为他的锁定范围更小更精确。

Oracle当中一个记录被insert/update/delete/select for update之后,当前事务T1自动获得这行的X排他锁,另外一个事务T2如果要更改这条记录,另外一个事务T2会被阻塞,直到T1提交或者回滚。

比如

步骤 T1 T2 备注
1 update row1
2 update row1 T2被阻塞
3 commit T1提交,T2可以继续
4 commit

如果在第一步之后,你执行select * from v$lock where type in ('TX','TM'),你就会看到有一个行级别锁TX

image

其中LMODE=3和REQUEST=0,其中LMODE是已经获得的锁模式,取值从0到6, 对于行级别锁其实唯一的值是6,其他值都是对表级别锁的。REQUEST表示要求的锁,如果一个事务所要求的锁需要等待另一个事务释放,那么REQUEST可以看到这个事务需要请求什么类型的锁。

0 NO LOCK
1 NULL
2 RS (Row Share)
3 RX (Row Exclusive)
4 S (Share)
5 SRX (Share + Row Exclusive)
6 X (Exclusive)

这里这个行级别锁TX已经获得了在行row1上的排他锁。

如果在第二步执行之后,T2会被阻塞,你打开一个新的连接再一次执行select * from v$lock where type in ('TX','TM'),你就会看到多出来一个行级别锁TX

image

这个新多出来的行级别锁在row1上获得锁是0(无),要求锁是6(X),同时,刚才那个行级别锁的block标志位变成了1,表示有别的事务等待我释放这个锁。

然后如果你提交T1,T2获得锁,可以继续下去,相应的只剩下了T2的row1上的锁,而且LMODE从0变成了6, REQUEST从6变成了0

image

另外,这时候T1会自动在表级别也加一个锁,目的是防止事务过程中发生DDL操作。其中SID是session ID,可以通过V$session查看,ID1和ID2是rollback segment和transaction table entry.

表级锁

简称TM锁,可分为以下5种

RS: row share,意向锁,表示表内部分行有S锁了,部分行不许更改了

RX: row exclusive ,意向锁,表示表内部分行有变更,有X锁了

S: share ,整个表有S锁,这个表不可以更改

SRX: share + row exclusive,只有一个事务可以获得SRX锁,其他事务可以查询但是不可以更改

X: exclusive, 只有一个事务可以获得X表锁, 其他事务只能查询

RS,RX比较特殊,他们是DML操作产生的常见表级锁,他们都只是意向锁,真正的加锁粒度还是在行级,相当于某些行被锁之后,用这两个意向锁锁一下所属的表,数据库会比较容易判断行级别的锁情况。

从刚才的例子中可以看到,TX总是伴随着一个LMODE=3的TM锁,即RX锁,只要表中有行有X锁,表就会有RX锁。RX表示事务已经更改了某些行,比如insert/update/delete,获得了某些行X锁。RX不会阻塞RX锁,除非行锁上有阻塞。刚才的例子,如果两个事务更改的是两个记录,那么就不会互相阻塞。

如果执行了lock table test in row share mode你会看到LMODE=2(Row Share)的一个表级别锁。RS级别的锁只阻止X级别表锁,它不阻止RS, S等。

image

详细的锁之间的关系如下(相信大多数人就算是理解了也要要想半天的)image

SQL Server, Oracle 和 MySQL 事务隔离等级实现差别

基本概念

脏读:包含未提交数据的读。例如,事务1 更改了某行。事务2 在事务1 提交更改之前读取已更改的行。如果事务1 回滚更改,则事务2 便读取了逻辑上从未存在过的行。

不可重复读取:当某个事务不止一次读取同一行,并且一个单独的事务在两次(或多次)读取之间修改该行时。因为在同一个事务内的多次读取之间修改了该行,所以每次读取都生成不同值,从而引发不一致问题。

幻像:通过一个任务,在以前由另一个尚未提交其事务的任务读取的行的范围中插入新行或删除现有行。带有未提交事务的任务由于该范围中行数的更改而无法重复其原始读取。如果某个连接设置其事务隔离级别为可串行,则 SQL Server 使用键范围锁定以防止幻像。

SQL-92 定义了下列四种隔离级别

隔离级别

脏读

不可重复读取

幻像

未提交读

提交读

可重复读

可串行读

我们使用三个主流数据库测试,分别是Oracle 10g, SQL Server 2005和MySQL 5.1 (InnoDB only)

测试幻读

首先创建一个表test,并插入三条测试数据

CREATE TABLE test
(
c1 INTEGER,
c2 INTEGER
);

insert into test values(1,1);
insert into test values(2,2);
insert into test values(3,3);
commit;

SQL Server消除幻读测试
Steps T1 T2 备注
1

set transaction isolation level serializable;

begin transaction;

select * from test;


T1事务启动,设置隔离等级到Serializable, 然后查询一下test,会看到3条记录
2

begin transaction;

select * from test;
insert into test values(100, 100);
T2启动,插入一行并提交,这时候SQL Server会阻塞这个插入, 因为T1加了范围锁
3 select * from test;
commit;

T1第二次查询应该看到和第一次一样的三行,然后提交


commit; 只有T1提交之后,T2才可以插入




从sp_lock输出来看,在第一步之后,T1对test表加了S锁,所以T2可以选test的数据但是无法更改

Oracle消除幻读测试

Steps T1 T2 备注
1

set transaction isolation level serializable;

begin transaction;

select * from test;


T1事务启动,设置隔离等级到Serializable, 然后查询一下test,会看到3条记录
2

begin transaction;

select * from test;
insert into test values(100, 100);
commit;
T2启动,插入一行并提交,这时候Oracle不会阻塞这个插入,然后T2成功提交
3 select * from test;
commit;

T1第二次查询仍然看到和第一次一样的三行,然后提交
4 select * from test;
新事物开始,这时候T1才会看到刚才T2插入的记录




从V$lock来看,整个过程Oracle没有加锁,Oracle使用了SCN从回滚段重建出来当初时刻的数据提供一个snapshot 来保证T1不受T2的影响。

MySQL消除幻读测试

Steps T1 T2 备注
1

set transaction isolation level serializable;

start transaction;

select * from test;


T1事务启动,设置隔离等级到Serializable, 然后查询一下test,会看到3条记录
2

start transaction;

select * from test;
insert into test values(100, 100);
T2启动,插入一行并提交,这时候MySQL会阻塞这个插入, 因为T1加了范围锁
3 select * from test;
commit;

T1第二次查询应该看到和第一次一样的三行,然后提交


commit; 只有T1提交之后,T2才可以插入




和SQL Server一样的处理方式,但是MySQL也有snapshot, 效果和Oracle一样,语法是START TRANSACTION WITH CONSISTENT SNAPSHOT;从这个角度来说MySQL实现还是不错的,比SQL Server好一些。

测试不可重复读

SQL Server消除不可重复测试

Steps T1 T2 备注
1

set transaction isolation level repeatable read;

begin transaction;

select * from test;


T1事务启动,设置隔离等级到Serializable, 然后查询一下test,会看到3条记录
2

begin transaction;

select * from test;
update test set c2=200 where c1=2;
T2启动,更改一行,这时候SQL Server会阻塞这个更改, 因为T1在记录行上加了X和U锁 (排他更新锁)
3 select * from test;
commit;

T1第二次查询应该看到和第一次一样记录,然后提交


commit; 只有T1提交之后,T2才可以更新




Oracle消除不可重复测试

Oracle不支持REPEATABLE READ这个隔离等级,我们需要用更高的Serializable级别来测试

Steps T1 T2 备注
1

set transaction isolation level serializable;

begin transaction;

select * from test;


T1事务启动,设置隔离等级到Serializable, 然后查询一下test,会看到3条记录
2

begin transaction;

select * from test;
update test set c2=200 where c1=2;
commit;
T2启动,更改一行并提交,这时候Oracle不会阻塞这个更新,然后T2成功提交
3 select * from test;
commit;

T1第二次查询仍然看到和第一次一样的记录,然后提交
4 select * from test;
新事物开始,这时候T1才会看到刚才T2更改的记录




从V$lock来看,整个过程Oracle没有加锁,Oracle使用了SCN从回滚段重建出来当初时刻的数据提供一个snapshot 来保证T1不受T2的影响。

MySQL消除不可重复测试

Steps T1 T2 备注
1

set transaction isolation level serializable;

start transaction;

select * from test;


T1事务启动,设置隔离等级到Serializable, 然后查询一下test,会看到3条记录
2

start transaction;

select * from test;
update test set c2=200 where c1=2;
commit;
T2启动,更改一行并提交,这时候MySQL不会阻塞这个更新.
3 select * from test;
commit;

T1第二次查询应该看到和第一次一样的记录




对于不可重复读,MySQL和Oracle一样采取了类似快照的方式,有点出乎意料,这点上MySQL实现比SQL Server要好一些。

测试脏读

Read Committed可以消除脏读,由于比较简单,各个数据库实现差别不大,不会加特殊的锁,不再详细测试。这也是我们大多数情况下的数据库默认事务隔离等级。

总结

事务隔离等级不同的数据库实现有差别,有时候必须要清楚的了解之间的差别才能避免应用程序在多个数据库上兼容性和稳定性。

对于REPEATABLE READS,SQL Server设定隔离等级到REPEATABLE READ,对所有select过的数据加锁,阻止其他事务更新数据。但是其他事务可以插入幻读记录。而Oracle不直接支持这个等级,必须设定更高的SERIALIZABLE,Oracle使用SCN和Rollback segment重构snapshot,MySQL也是使用类似的snapshot,因次理论上讲SQL Server的这个隔离级别上并发性能比较差。

对于PHANTOM幻读,三种数据库都是设定到SERIALIZABLE等级,但是SQL Server是通过加范围锁阻塞其他事务的插入和更新,Oracle使用snapshot, MySQL两种方式都支持。

另外,由于SERIALIZABLE级别上,SQL Server使用的是范围锁,所以其他数据无法插入或者更新,而Oracle不会阻塞其他事务更新数据,Oracle假设大多数情况下多个事务不会更新同一条记录,但是如果其他更新的记录和当前事务碰巧修改了同一条记录,Oracel会通过乐观锁发现这种情况,并报错臭名昭著的ORA-8177 Cannot serialize access for this transaction. 所以Oracle的Serializable虽然性能高,但是不可以用于长时间的事务或者频繁的OLTP系统。如果有这样的需要,必须通过lock table实现。

尽管这几个数据库都实现了ACID但是各有千秋,实现跨数据库的应用的时候需要小心。还有就是,不管数据库本身事务隔离等级和锁的实现效率差别如何,关键还是良好的架构,不好的架构往往在单节点低压力测试的时候速度很快但是在高并发的多处理器或者集群上横向伸缩的时候性能下降很快,Facebook或者LiveJournal用一大堆MySQL照样跑的很好, 不比昂贵的Oracle RAC差,架构决定伸缩性,而不应该迷信数据库本身。