Sunday, October 29, 2006

Apache配置虚拟主机


本文转自 http://www.ideagrace.com/html/doc/2005/10/27/00686.html

Apache配置虚拟主机
发表于:2005-10-27 阅读:713次



虚拟主机使得一台服务器可以运行多个网站。本文介绍如何配置服务器使其支持虚拟主机,包括基于IP的虚拟主机和基于名称的虚拟主机,以及通过运行多个服务daemon实现一台机器运行多个网站的方法。

一、虚拟主机及其应用(1月15日)

   虚拟主机就是运行在同一物理服务器上的具有不同名字的多个Web网站。它的基本思想是:即使所有网站都运行在同一个服务器上,Apache也知道用户访问的是哪一个网站,而且能够正确地返回用户所请求的内容。

   虚拟主机是这样一种特殊的技术:它使得我们可以在一台机器上运行多个Web网站,支持许多不同的域名和一个域之内的多个名字,从而使得一台通过DSL接入的家用计算机看起来象是一屋子的服务器。这是一种非常巧妙的技术。

  为什么要使用虚拟主机

   或许你现在已经有了自己的应用虚拟主机的设想。下面是本人通常使用虚拟主机的情况。我在一家Web设计公司工作。为客户开发Web网站时,我们总是为该客 户配置一个专用的虚拟主机。这些虚拟主机往往是基于名称的虚拟主机,因为这设置起来比较快,而且几乎总是只在内部进行。也就是说,在内部DNS服务器上设 置一下名称,这样内部的主机就可以看到这个Web网站,而外面的浏览器只能看到我们开发服务器上公开的内容。

   这种做法有着许许多多的好处。它使得我们能够设置网站的目录结构,其设置方法又和开发完成后实际运行的网站完全一样。它使得我们能够完全地分离不同网站的 文件,不会出现搞不清某个文件属于哪个网站的混乱。此外,从开发者的角度来看,它使得我们能够在DreamWeaver中设置完全独立的网站,而不仅仅是 设置同一网站的不同子目录。

   最后,如果使用虚拟主机,不论我们同时开发的客户项目有多少,都无需分别为每个网站配置一个新的服务器。在任何时候这都是使用虚拟主机最主要的理由。

二、基于IP的虚拟主机(1月15日)

   我们可以用两种方法设置虚拟主机。这两种方法只是服务器上配置的不同,对客户来说没有什么区别——也就是说,用户无法知道他们所使用的虚拟主机属于哪一种类型,甚至不会知道自己正在使用虚拟主机。

   这两种类型分别是基于IP的(IP-based)虚拟主机和基于名称的(Name-Based)虚拟主机。简单地说,两者的区别在于:使用基于IP的虚拟 主机时,每一个虚拟主机都有一个不同的IP地址,而基于名称的虚拟主机都有相同的IP地址,但它们的名称不同。两种类型各有优点,但它们的实现方法并没有 什么大的差别。

   在基于IP的虚拟主机环境中,同一个服务器机器上运行着多个Web网站,但每一个Web网站都有其自己的IP地址。为了实现这一点,我们首先要在操作系统 上设置这些IP地址。大多数现代操作系统都允许我们为一台机器设置任意多的IP地址,具体的设置方法对每种操作系统来说都不同。本文不准备在方面作深入的 介绍。如果你不熟悉如何在自己的操作系统上进行设置,请向熟悉的专家请教。一般这种设置并不是很困难。或者,你也可以跳过这种基于IP的虚拟主机,直接转 入下面介绍的基于名称的虚拟主机。

   有些老式的操作系统要求每个NIC(network interface card,网卡)对应一个IP地址,但现在一般不再有这种限制。

   在机器上设置好多个IP地址之后,我们还要在DNS(域名服务器)中设置这些IP地址,这样DNS就能够解析我们赋予这些IP地址的名称。这里我们不准备介绍DNS记录的具体设置方法,你可以要求DNS服务器的管理员设置这些记录。

   接下来要做的工作就直接和Apache有关了。假定我们已经将所有的IP地址设置到了机器上,每一个IP地址都有相应的DNS记录,要在Apache的httpd.conf配置文件中增加的内容如下所示。

   这里我们假定有三个IP地址,对应的名称分别为name1.mydomain.com、name2.mydomain.com和 name3.mydomain.com。当然,这里指定的名称也可以属于完全不同的域,例如www.mydomain.com 和 www.myotherdomain.com。

   加入httpd.conf配置文件的内容如下所示:





DocumentRoot /usr/local/apache/name1_www

ServerName name1.mydomain.com

ErrorLog /usr/local/apache/logs/name1_logs/error_log

TransferLog /usr/local/apache/logs/name1_logs/access_log





ServerAdmin webmaster@name2.mydomain.com

DocumentRoot /usr/local/apache/name2_www

ServerName name2.mydomain.com

ServerAlias name2





DocumentRoot /usr/local/apache/name3_www

ServerName name3.mydomain.com

ScriptAlias /use/local/apache/name3_cgi







   注意我们并不需要为每个虚拟主机指定所有的配置细节。凡是没有指定的配置细节都将从服务器主配置文档继承。例如在上面的第二个虚拟主机中,我们没有指定记录日志的地方,这个虚拟主机将把日志信息写入主服务器日志文件。

   另外也请注意在第三部分中我们指定了一个CGI目录。对于其他两个部分来说,当用户访问一个包含/cgi-bin/的URL时,都默认使用主服务器配置中所指定的CGI目录,这是因为它们的虚拟主机配置中都没有指定CGI目录。

   几乎所有的配置指令都可以用于区。在服务器说明文档中,我们可以注意到每一个指令都有其合法应用场所的说明,某个指令是否可以在虚拟主机配置中使用应该已经相当清楚。

   请记住务必在重新启动服务器之前测试一下新的配置文件:



/usr/local/apache/bin/apachectl configtest





   另外一个方便的技巧是使用SeverAlias指令,它对基于名称的虚拟主机同样适用。在LAN内访问内部服务器时,我们往往希望使用比较简单的服务器名 字,例如“name2”,而不是它的全称“name2.mydomain.com”。Apache无法知道“name2”和 “name2.mydomain.com”实际上是同一个Web网站,使用ServerAlias指令可以确保我们在使用简称时不会得到意想不到的页面。

   重新启动服务器应用新的配置文件之后,Apache就能够根据URL判断用户访问的是哪一个Web网站,在此基础上提供正确的页面。即使所有网站在同一物理服务器上也绝不会出错。
三、基于名称的虚拟主机(1月15日)

   当然,许多时候我们不能奢侈到给一台机器指定多个IP地址的地步。IP地址已经越来越紧张,而且在很多时候,例如象我是通过DSL接入,我们只能占有一个IP地址。在这种情况下,基于名称的虚拟主机正好适用。

   使用基于名称的虚拟主机时,我们无需为机器设定多个IP地址,但仍旧涉及到DNS服务器的管理员,要由他们为我们的机器设置一个以上的DNS记录。这些额 外的记录称为C-record,或者CName(指向某台机器的主记录称为AName,或A-record)。我们可以让任意数量的CName指向一台特 定的机器。

   配置DNS服务器使它将所有指定的名称解析为服务器的IP地址之后,我们还要将下面的内容加入Apache服务器的配置文件:



NameVirtualHost 192.168.1.1



ServerName name1.mydomain.com

DocumentRoot /usr/local/apache/name1docs

ServerAlias name1





ServerName name2.mydomain.com

DocumentRoot /usr/local/apache/name2docs

ServerAlias name2





ServerName name3.mydomain.com

DocumentRoot /usr/local/apache/name3docs

ServerAlias name3







   正如配置基于IP的虚拟主机,我们可以在这里使用任何适用的配置指令,本例的配置应该说是相当简单的。

   下面是一些注意事项。

   首先,我们必须用NameVirtualHost指令指定哪个IP地址负责响应对虚拟主机的请求。我们可以联合运用基于IP和基于名称的虚拟主机——也就 是说,可以在同一服务器上同时地配置这两种虚拟主机,因此我们必须指定哪一个IP地址用于基于名称的虚拟主机。如果我们要在服务器的内部接口和外部接口 (即Intranet服务器和Internet服务器)上设置虚拟主机,必须同时指定这两个地址:



NameVirtualHost 192.168.1.1

NameVirtualHost 208.32.54.7





   此外,除了不同的地址之外,我们还可以在不同的端口上运行虚拟主机,当然这会使系统变得非常复杂:



NameVirtualHost 192.168.1.1:80

NameVirtualHost 192.168.1.1:90





   注意确保所有的名称都已经在DNS中登记,否则用户将不能访问到自己想要访问的页面。

   虚拟主机能够正确发挥作用的窍门之一在于浏览器告诉了服务器它正在向谁请求内容。有些仍在使用的较老的浏览器不能正确地做到这一点,它们会从机器上的“默 认”服务器读取内容。不过我们几乎无需为此担心,因为至少在最近5年来(或更早)发布的浏览器都具有这个能力。在Apache里,针对那些不能好好配合的 浏览器,我们还有相应的解决办法,不过这就留给你自己去研究。如果你对此感兴趣,请从http: //www.apache.org/docs/manual/vhosts/index.html开始。

四、运行多个daemon(1月15日)

   事实上我们还有第三种在一个服务器上运行多个Web网站的方法,这就是在同一机器上运行多个完全独立的服务daemon,每个daemon提供一个Web 网站的服务。这种方法的缺点在于每个daemon必须在不同的端口上运行,这是因为我们无法让多个服务同时监听同一端口。

   我们可以用-f命令行选项让Apache服务器使用指定的配置文件:



/usr/local/apache/bin/apache -f /usr/local/apache/conf/name2_httpd.conf





   这使得我们可以运行任意数量(只要机器能够支持)的服务daemon,每一个服务都以不同的配置运行。当然,此时应当确保每一个配置文件都有一个端口指令指向不同端口,否则自第一个以后的其他服务daemon都将不能启动并提示下列错误信息:



Could not bind to port 80: Port already in use.





   结束语:虚拟主机使得我们可以在同一服务器机器上运行多个Web网站,它有着许多好处。总而言之,它使得你可以对上司说:“我们并不需要为了运行其他18个网站去购买新的机器”。


©2005 Idea Grace

Friday, October 20, 2006

关于Delphi接口不能强制转化的补充说明

其实Delphi为了速度的原因,对于接口是采用直接解析接口在实例中的偏移来得到的.这个可以从下面的代码中看到:

var
mInt: ITestInterface;
mInt2: ITestInterface2;
begin
mInt := TTestInterface2.Create;
mInt2 := TTestInterface2.Create;
end;

其中 mInt := TTestInterface2.Create 代码为:
mov dl, $01
mov eax, [$00452e4]
call TObject.Create
mov edx, eax
jz +$03
sub edx, -$0C
lea eax, [ebp-$04]
call @IntfCopy

而 mInt2 := TTestInterface2.Create 代码为:
mov dl, $01
mov eax, [$00452e4]
call TObject.Create
mov edx, eax
jz +$03
sub edx, -$0C
lea eax, [ebp-$08]
call @IntfCopy

但对于接口之间的转化时, CSDN的halfdream(哈欠) 兄说的很对, Delphi在call @IntfCast里调用了@QueryInterface, 而这里面的代码, 其实是在System的TObject对象的GetInterface这个方法中, 但这个方法有个问题, 它需要GUID, 但我们实际可以在Delphi中使用无GUID的接口, 那么就会发现, 根本无法进行转化

比如:
INoGUIDInterface = interface
procedure NoGUID;
end;

INoGUIDInterface2 = interface
procedure NoGUID2;
end;

var
mInt: INoGUIDInterface;
mInt2: INoGUIDInterface2;
begin
mInt := TTestNoGUIDInterface2.Create;
mInt.NoGUID;

mInt2 := mInt as INoGUIDInterface2;
mInt2.NoGUID2;
end;

编译就会出错了, Operator not applicable to this operand type 根本就不支持. 所以表明接口时还是随手加上GUID的比较好

Thursday, October 19, 2006

对于delphi7的接口不能强制转化的问题

今天在发现delphi里的接口是不能强制转化的, 比如如下代码:
ITestInterface = interface(IUnknown)
['{DE8E7E63-BD1B-4BD8-B59A-43F4B2B3A3DC}']
procedure Chk;
end;

ITestInterface2 = interface(IUnknown)
['{3B47B113-4664-4CCC-9A69-E378F2D2363E}']
procedure Chk2;
end;

var
mInt: ITestInterface;
mInt2: ITestInterface2;
begin
mInt := TTestInterface2.Create;
mInt.Chk;

ITestInterface2(mInt).Chk2;

mInt2 := mInt as ITestInterface2;
mInt2.Chk2;
end;

从运行情况可以看出,在第一种情况 ITestInterface2(mInt).Chk2; 下,实际运行的还是ITestInterface下的procedure Chk;而第二种情况 mInt2 := mInt as ITestInterface2; 就能正确的运行ITestInterface2下的procedure Chk2;了.这是为什么呢?
原来这都出自Delphi的编译之手,在进行强制转化 ITestInterface2(mInt).Chk2; 时, 代码是:
mov eax, [ebp-$04]
mov edx, [eax]
call dword ptr [edx+$0c]
而进行 mInt.Chk; 调用时的代码也是这个,这说明强制转化时,编译器根本就没有把ITestInterface转化为ITestInterface2,而是理解为直接的对象强制转化,然后希望能在方法表里找到对应的方法,而且在对接口进行方法的解析时,也只是以方法的声明顺序,而不是实际的方法名来进行解析.所以在Chk为ITestInterface的第一个方法,而Chk2也为ITestInterface2的第一个方法时,就会直接的调用了ITestInterface的Chk,而不是ITestInterface2的Chk2.这个结论也可能通过下面这个实验来表明.
ITestInterface = interface(IUnknown)
['{DE8E7E63-BD1B-4BD8-B59A-43F4B2B3A3DC}']
procedure Chk;
end;

ITestInterface2 = interface(IUnknown)
['{3B47B113-4664-4CCC-9A69-E378F2D2363E}']
procedure Chk2;
procedure Chk22;
end;

var
mInt: ITestInterface;
mInt2: ITestInterface2;
begin
mInt := TTestInterface2.Create;
mInt.Chk;

ITestInterface2(mInt).Chk22;

mInt2 := mInt as ITestInterface2;
mInt2.Chk2;
end;
在这种情况下,就会报地址出错,那是因为ITestInterface根本就没有第二个方法.当然下面的代码也能说明问题
ITestInterface = interface(IUnknown)
['{DE8E7E63-BD1B-4BD8-B59A-43F4B2B3A3DC}']
procedure Chk;
procedure Chk1;
end;

ITestInterface2 = interface(IUnknown)
['{3B47B113-4664-4CCC-9A69-E378F2D2363E}']
procedure Chk2;
procedure Chk22;
end;

var
mInt: ITestInterface;
mInt2: ITestInterface2;
begin
mInt := TTestInterface2.Create;
mInt.Chk;

ITestInterface2(mInt).Chk22;

mInt2 := mInt as ITestInterface2;
mInt2.Chk2;
end;
在这种情况下,程序会调用ITestInterface的Chk1方法.

好上面说了强制转化时发生的情况,下面说用as来转化时发生的情况.

当用as来转化时,代码就变为:
lea eax, [ebp-$08]
mov edx, [ebp-$04]
mov ecx, $00452bc4
call @IntfCast
mov eax, [ebp-$08]
mov edx, [eax]
call dword prt [edx+$0c]
发现,在用as标志转化后,就会使编译器知道要在类实例中查找实际的接口ITestInterface2而不是用mInt所声明的ITestInteface.

以上说明Delphi7的编译器对接口的支持还是不够好,造成在强制转化时,不能正确的识别出要求的接口类型来.

Tuesday, October 10, 2006

Inside Microsoft .Net IL Assembly的一个错误

在这本书中, Part1的Enhancing the code的Code Retention一节中指出如果把
.class public explicit CharArray8 extends [mscorlib]System.ValueType { .size 8 }
这句放在
.field public static valuetype CharArray8 Format at FormatData
之前, 但在Even类之后的话,编译会出错, 但经过实际编译发现,这样子也是没有问题的, 看来要不是书上写错了,要不就是微软改进了它的编译器.

Monday, October 09, 2006

关于emacs比较好的站点

Emacs for Windows: http://learn.tsinghua.edu.cn:8080/2002315162/index.html
这个网站上有emacs的windows版本, 直接就是可执行文件,而不用自己再去编译了.

Emacs 中文化指南: http://man.chinaunix.net/newsoft/Emac/book.html
这个网站实在是好,站长同学把他使用emacs的经验都写在上面了.很多东东都是可以直接复制来用.

emacs在windows上使用时,它没有建立一个缺省的.emacs配置文件,可以直接在emacs上使用~/.emacs来建立配置文件.

下面这个代码可以最大化emacs
(w32-send-sys-command 61488)

Thursday, September 21, 2006

如何得到控制台程序的输出

所有的程序都有标准输入,标准输出,标准错误这三个管道存在, 所以我们想得到控制台程序的输出,可以通过重定向这些管道来实现.

首先, 我们要建立一个管道作为控制台程序的标准输出管道, 因为这个管道要转给控制台程序使用, 所以它应该是可以被控制台程序继承的句柄.
ZeroMemory(@mSa, SizeOf(mSa));
mSa.nLength := SizeOf(mSa);
mSa.bInheritHandle := True; //设定为可以为子进程继承的句柄
mSa.lpSecurityDescriptor := nil;

if not CreatePipe(mOutRd, mOutWr, @mSa, 0) then raise Exception.Create('建立管道出错');

第二步, 复制读管道的句柄, 因为读管道的句柄在建立时是一个可继承句柄, 它可能会被子进程使用, 那样会造成信息的丢失与父进程的堵塞, 所以我们要复制成一个不可以继承句柄, 并且它实际上在子进程中是没有用的, 所以我们在复制成功后要关闭它.

if not DuplicateHandle(GetCurrentProcess, mOutRd, GetCurrentProcess,
@mOutRdDup, 0, False, DUPLICATE_SAME_ACCESS) then
raise Exception.Create('复制读句柄出错');

CloseHandle(mOutRd);

第三步, 我们要建立控制台子进程了
ZeroMemory(@mStartupInfo, SizeOf(mStartupInfo));
mStartupInfo.cb := SizeOf(mStartupInfo);
mStartupInfo.dwFlags := mStartupInfo.dwFlags or STARTF_USESTDHANDLES; //将标准输出重定向
mStartupInfo.hStdOutput := mOutWr;
mStartupInfo.hStdError := mOutWr;
ZeroMemory(@mProcess, SizeOf(mProcess));

if not CreateProcess(nil, cmdLine,nil, nil, True, 0, nil, WorkDir, mStartupInfo,mProcess) then
raise Exception.Create('启动程序出错');

CloseHandle(mProcess.hProcess);
CloseHandle(mProcess.hThread);

第四步, 关闭写句柄, 要不然当子进程关闭了自己后, 会因为写句柄还被父进程所打开, 而使下面的ReadFile无法得知管道已结束, 从而堵塞.

if not CloseHandle(mOutWr) then
raise Exception.Create('标准输出句柄关闭出错');

while ReadFile(mOutRdDup, mBuff, 256, mRealRead, nil) do begin
if mRealRead = 0 then Break;
//作点自己爱作的事情吧
end;

完整的代码如下:
var
mRealRead: DWORD;
mOutRd, mOutWr, mOutRdDup: THandle;
mSa: TSecurityAttributes;
mProcess: TProcessInformation;
mStartupInfo: TStartupInfo;
mBuff: array[0..255] of Char;
begin
ZeroMemory(@mSa, SizeOf(mSa));
mSa.nLength := SizeOf(mSa);
mSa.bInheritHandle := True;
mSa.lpSecurityDescriptor := nil;

if not CreatePipe(mOutRd, mOutWr, @mSa, 0) then raise Exception.Create('建立管道出错');

if not DuplicateHandle(GetCurrentProcess, mOutRd, GetCurrentProcess,
@mOutRdDup, 0, False, DUPLICATE_SAME_ACCESS) then
raise Exception.Create('复制读句柄出错');

CloseHandle(mOutRd);
ZeroMemory(@mStartupInfo, SizeOf(mStartupInfo));
mStartupInfo.cb := SizeOf(mStartupInfo);
mStartupInfo.dwFlags := mStartupInfo.dwFlags or STARTF_USESTDHANDLES;
mStartupInfo.hStdOutput := mOutWr;
mStartupInfo.hStdError := mOutWr;
mStartupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
ZeroMemory(@mProcess, SizeOf(mProcess));


if not CreateProcess(nil, cmdLine,nil, nil, True, 0, nil, WorkDir, mStartupInfo,mProcess) then raise Exception.Create('启动程序出错');

CloseHandle(mProcess.hProcess);
CloseHandle(mProcess.hThread);

if not CloseHandle(mOutWr) then
raise Exception.Create('标准输出句柄关闭出错');

while ReadFile(mOutRdDup, mBuff, 256, mRealRead, nil) do begin
if mRealRead = 0 then Break;
mmo1.Lines.Add(mBuff);
Application.ProcessMessages;
end;

Wednesday, September 20, 2006

如何修改文件版本号

修改文件版本号, 其实就是修改文件的资源. MSDN上没有明说如何修改文件的版本号的方法, 因为MS认为版本号应该是属于文件创建者的, 而不是如我们这样的文件所有者应该去修改的. 但资源总是资源, MS提供了如何修改资源的函数, 当然这些函数也可以修改版本号.

首先, 我们要找到文件的版本信息资源:
mSize := GetFileVersionInfoSize('FileName.exe');
SetLength(mVersion, mSize);
GetFileVersionInfo('FileName.exe', mRes, mSize, mVersion);
这个代码能让我们得到文件的版本信息资源, 得到的结果就放在mVersion中, 其实这个就是一串的二进制形的资源内存块.

第二步, 我们要找到文件的版本信息:
VerQueryValue(mVersion, '\', mFixV, mLen);
mFixVersion1 := PVSFixedFileInfo(mFixV);
这一步能让我们得到mFixVersionInfo, 它是mVersion这个资源内存块中指向文件的版本信息的那一部分的指针.

第三步, 修改文件的版本信息:
mFixVersion^.dwFileVersionMS := $10001; //高位低位都为1
mFixVersion^.dwFileVersionLS := 26 shl 16 + 26; //高位低位都为26
这两步演示了如何修改文件的版本号, dwFileVersionMS的高位是Major Version, 低位是Minor Version, dwFileVersionLS的高位是Release Version, 低位是Build Version.

第四步, 保存修改的内容, 下面就可以参考MSDN上的UpdateResource部分的例子了, 因为都是资源嘛:
mHExe := BeginUpdateResource('FileName.Exe', False);
if VerQueryValue(mVersion, '\VarFileInfo\Translation', mTranslate, mLen) then begin
//这步是因为基本都是单区域资源的, 如果是多区域的, 可以参考MSDN中的作法,
//将mTranslate/sizeof(LANGANDCODEPAGE)来得到第一个
mLang := PLANGANDCODEPAGE(mTranslate);
if UpdateResource(mHExe, RT_VERSION, MakeIntResource(1), mLang^.wLanguage, mVersion, mSize) then
EndUpdateResource(mHExe, False);
FreeLibrary(mHExe);

这样, 我们就修改了文件的版本号了, 可以类推出如何修改版本资源中的其它资源.

完整的代码如下
type
PLANGANDCODEPAGE = ^LANGANDCODEPAGE;
LANGANDCODEPAGE = record
wLanguage: Word;
wCodePage: Word;
end;

var
mSize: Integer;
mLen, mLen1: UINT;
mTranslate, mFixV: Pointer;
mHExe, mRes: THandle;
mVersion: array of Byte;
mFixVersion: VS_FIXEDFILEINFO;
mFixVersion1: PVSFixedFileInfo;
mLang: PLANGANDCODEPAGE;
begin
mSize := GetFileVersionInfoSize('FileName.exe', mRes);
SetLength(mVersion, mSize);
GetFileVersionInfo('FileName.exe', mRes, mSize, mVersion);
VerQueryValue(mVersion, '\', mFixV, mLen);
mFixVersion1 := PVSFixedFileInfo(mFixV);
mFixVersion1^.dwFileVersionMS := $10001;
mFixVersion1^.dwFileVersionLS := 26 shl 16 + 26;
mHExe := BeginUpdateResource('FileName.exe', False);
if VerQueryValue(mVersion, '\VarFileInfo\Translation', mTranslate, mLen) then begin
mLang := PLANGANDCODEPAGE(mTranslate);
if UpdateResource(mHExe, RT_VERSION, MakeIntResource(1), mLang^.wLanguage, mVersion, mSize) then
EndUpdateResource(mHExe, False);
end
FreeLibrary(mHExe);

Wednesday, September 13, 2006

有关instr函数的问题

关于pl/sql中的instr函数, 当要查找的字符串或被查找的字符串为NULL时, 返回的值也是NULL.

关于oracle9以上自定义聚集函数的例子

以下是自定义聚集函数的代码例子, 抄自http://yangtingkun.itpub.net/post/468/3380

SQL> CREATE OR REPLACE TYPE T_LINK AS OBJECT (
2 STR VARCHAR2(30000),
3 STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX IN OUT T_LINK) RETURN NUMBER,
4 MEMBER FUNCTION ODCIAGGREGATEITERATE(SELF IN OUT T_LINK, VALUE IN VARCHAR2) RETURN NUMBER,
5 MEMBER FUNCTION ODCIAGGREGATETERMINATE(SELF IN T_LINK, RETURNVALUE OUT VARCHAR2, FLAGS IN NUMBE
R) RETURN NUMBER,
6 MEMBER FUNCTION ODCIAGGREGATEMERGE(SELF IN OUT T_LINK, CTX2 IN T_LINK) RETURN NUMBER
7 )
8 /

SQL> CREATE OR REPLACE TYPE BODY T_LINK IS
2 STATIC FUNCTION ODCIAGGREGATEINITIALIZE(SCTX IN OUT T_LINK) RETURN NUMBER IS
3 BEGIN
4 SCTX := T_LINK(NULL);
5 RETURN ODCICONST.SUCCESS;
6 END;
7
8 MEMBER FUNCTION ODCIAGGREGATEITERATE(SELF IN OUT T_LINK, VALUE IN VARCHAR2) RETURN NUMBER IS
9 BEGIN
10 SELF.STR := SELF.STR || VALUE;
11 RETURN ODCICONST.SUCCESS;
12 END;
13
14 MEMBER FUNCTION ODCIAGGREGATETERMINATE(SELF IN T_LINK, RETURNVALUE OUT VARCHAR2, FLAGS IN NUMBE
R) RETURN NUMBER IS
15 BEGIN
16 RETURNVALUE := SELF.STR;
17 RETURN ODCICONST.SUCCESS;
18 END;
19
20 MEMBER FUNCTION ODCIAGGREGATEMERGE(SELF IN OUT T_LINK, CTX2 IN T_LINK) RETURN NUMBER IS
21 BEGIN
22 NULL;
23 RETURN ODCICONST.SUCCESS;
24 END;
25 END;
26 /

SQL> CREATE OR REPLACE FUNCTION F_LINK(P_STR VARCHAR2) RETURN VARCHAR2
2 AGGREGATE USING T_LINK;
3 /

Monday, September 11, 2006

oracle 8i 关键字列表

Oracle8 SQL Reference
Release 8.0

A58225-01


Oracle Reserved Words and Keywords

This appendix lists Oracle reserved words and keywords.

lists Oracle reserved words. Words followed by an asterisk (*) are also ANSI reserved words.


Note:

In addition to the following reserved words, Oracle uses system-generated names beginning with "SYS_" for implicitly generated schema objects and subobjects. Oracle discourages you from using this prefix in the names you explicitly provide to your schema objects and subobjects to avoid possible conflict in name resolution.



Table C-1 Reserved Words

ACCESS

AUDIT

COMPRESS

DESC*

ADD*

BETWEEN*

CONNECT*

DISTINCT*

ALL*

BY*

CREATE*

DROP*

ALTER*

CHAR*

CURRENT*

ELSE*

AND*

CHECK*

DATE*

EXCLUSIVE

ANY*

CLUSTER

DECIMAL*

EXISTS

AS*

COLUMN

DEFAULT*

FILE

ASC*

COMMENT

DELETE*

FLOAT*

FOR*

LONG

PCTFREE

SUCCESSFUL

FROM*

MAXEXTENTS

PRIOR*

SYNONYM

GRANT*

MINUS

PRIVILEGES*

SYSDATE

GROUP*

MODE

PUBLIC*

TABLE*

HAVING*

MODIFY

RAW

THEN*

IDENTIFIED

NETWORK

RENAME

TO*

IMMEDIATE*

NOAUDIT

RESOURCE

TRIGGER

IN*

NOCOMPRESS

REVOKE*

UID

INCREMENT

NOT*

ROW

UNION*

INDEX

NOWAIT

ROWID

UNIQUE*

INITIAL

NULL*

ROWNUM

UPDATE*

INSERT*

NUMBER

ROWS*

USER*

INTEGER*

OF*

SELECT*

VALIDATE

INTERSECT*

OFFLINE

SESSION*

VALUES*

INTO*

ON*

SET*

VARCHAR*

IS*

ONLINE

SHARE

VARCHAR2

LEVEL*

OPTION*

SIZE*

VIEW*

LIKE*

OR*

SMALLINT*

WHENEVER*

LOCK

ORDER*

START

WHERE

WITH*


lists Oracle keywords. Keywords marked with asterisks (*) are also ANSI reserved words. For maximum portability to other implementations of SQL, do not use these words as schema object names.

ACCOUNT

CACHE_INSTANCES

CONSTRAINT*

DEGREE

ACTIVATE

CANCEL

CONSTRAINTS*

DEREF

ADMIN

CASCADE*

CONTENTS

DIRECTORY

AFTER

CAST

CONTINUE*

DISABLE

ALLOCATE*

CFILE

CONTROLFILE

DISCONNECT

ALL_ROWS

CHAINED

CONVERT*

DISMOUNT

ANALYZE

CHANGE

COST

DISTRIBUTED

ARCHIVE

CHARACTER*

COUNT*

DML

ARCHIVELOG

CHAR_CS

CPU_PER_CALL

DOUBLE*

ARRAY

CHECKPOINT

CPU_PER_SESSION

DUMP

AT*

CHOOSE

CURRENT_SCHEMA

AUTHENTICATED

CHUNK

CURRENT_USER*

EACH

AUTHORIZATION

CLEAR

CURSOR*

ENABLE

AUTOEXTEND

CLOB

CYCLE

END*

AUTOMATIC

CLONE

ENFORCE

CLOSE*

DANGLING

ENTRY

BACKUP

CLOSED_CACHED_OPEN_

CURSORS

DATABASE

ESCAPE*

BECOME

COALESCE*

DATAFILE

ESTIMATE

BEFORE

COLUMNS

DATAFILES

EVENTS

BEGIN*

COMMIT*

DATAOBJNO

EXEMPT*

BFILE

COMMITTED

DBA

EXCEPTIONS

BITMAP

COMPATIBILITY

DEALLOCATE*

EXCHANGE

BLOB

COMPILE

DEBUG

EXCLUDING

BLOCK

COMPLETE

DEC*

EXECUTE*

BODY

COMPOSITE_LIMIT

DECLARE*

EXPIRE

COMPUTE

DEFERRABLE

EXPLAIN

CACHE

CONNECT_TIME

DEFERRED

EXTENT

EXTENTS

INDEXES

MANAGE

NLS_CALENDAR

EXTERNALLY

INDICATOR*

MASTER

NLS_CHARACTERSET

IND_PARTITION

MAX*

NLS_ISO_CURRENCY

FAILED_LOGIN_ATTEMPTS

INITIALLY

MAXARCHLOGS

NLS_LANGUAGE

FALSE

INITRANS

MAXDATAFILES

NLS_NUMERIC_

CHARACTERS

FAST

INSTANCE

MAXINSTANCES

NLS_SORT

FIRST_ROWS

INSTANCES

MAXLOGFILES

NOS_SPECIAL_CHARS

FLAGGER

INSTEAD

MAXLOGHISTORY

NLS_TERRITORY

FLUSH

INT*

MAXLOGMEMBERS

NOARCHIVELOG

FORCE

INTERMEDIATE

MAXSIZE

NOCACHE

FOREIGN*

ISOLATION*

MAXTRANS

NOCYCLE

FREELIST

ISOLATION_LEVEL

MAXVALUE

NOFORCE

FREELISTS

MEMBER

NOLOGGING

FULL

KEEP

MIN*

NOMAXVALUE

FUNCTION

KEY*

MINEXTENTS

NOMINVALUE

KILL

MINIMUM

NONE

GLOBAL*

MINVALUE

NOORDER

GLOBALLY

LAYER

MOUNT

NOOVERIDE

GLOBAL_NAME

LESS

MOVE

NOPARALLEL

GROUPS

LIBRARY

MTS_DISPATCHERS

NORESETLOGS

HASH

LIMIT

MULTISET

NOREVERSE

HASHKEYS

LINK

NORMAL

HEADER

LIST

NATIONAL*

NOSORT

INSTANCE

LOB

NCHAR*

NOTHING

HEAP

LOCAL*

NCHAR_CS

NUMERIC*

LOG

NCLOB

NVARCHAR2

IDLE_TIME

LOGFILE

NEEDED

OBJECT

IF

LOGGING

NESTED

OBJNO

INCLUDING

LOGICAL_READS_PER_CALL

NEW

OBJNO_REUSE

INDEXED

LOGICAL_READS_PER_

SESSION

NEXT*

OFF

OID

PLSQL_DEBUG

RESIZE

SHARED_POOL

OIDINDEX

POST_TRANSACTION

RESTRICTED

SHRINK

OLD

PRECISION*

RETURN

SKIM_UNUSABLE_INDEXES

ONLY*

PRESERVE*

RETURNING

SNAPSHOT

OPCODE

PRIMARY*

REUSE

SOME*

OPEN*

PRIVATE

REVERSE

SORT

OPTIMAL

PRIVATE_SGA

ROLE

SPECIFICATION

OPTIMIZER_GOAL

PRIVILEGE

ROLES

SPLIT

ORGANIZATION

PROCEDURE*

ROLLBACK*

SQLCODE*

OVERFLOW

PROFILE

ROWLABEL

SQLERROR*

OWN

PURGE

RULE

SQL_TRACE

STANDBY

PACKAGE

QUEUE

SAMPLE

STATEMENT_ID

PARALLEL

QUOTA

SAVEPOINT

STATISTICS

PARTITION

SCAN_INSTANCES

STOP

PASSWORD

RANGE

SCHEMA*

STORAGE

PASSWORD_GRACE_TIME

RBA

SCN

STORE

PASSWORD_LIFE_TIME

READ*

SCOPE

STRUCTURE

PASSWORD_LOCK_TIME

REAL*

SD_ALL

SUM*

PASSWORD_REUSE_MAX

REBUILD

SD_INHIBIT

SWITCH

PASSWORD_REUSE_TIME

RECOVER

SD_SHOW

SYSDBA

PASSWORD_VERIFY_

FUNCTION

RECOVERABLE

SEGMENT

SYSOPER

PCTINCREASE

RECOVERY

SEG_BLOCK

SYSTEM

PCTTHRESHOLD

REF

SEG_FILE

PCTUSED

REFERENCES*

SEQUENCE

TABLES

PCTVERSION

REFERENCING

SERIALIZABLE

TABLESPACE

PERCENT

REFRESH

SESSIONS_PER_USER

TABLESPACE_NO

PERMANENT

REPLACE

SESSION_CACHED_

CURSORS

TABNO

PLAN

RESET

SHARED

TEMPORARY*

RESETLOGS

THAN

TRUE

UNLOCK

VALIDATION

THE

TRUNCATE

UNRECOVERABLE

VALUE

THREAD

TX

UNTIL*

VARRAY

TIME

TYPE

UNUSABLE

VARYING*

TIMESTAMP

UNUSED

WHEN

TOPLEVEL

UBA

UPDATABLE

WITHOUT

TRACE

UNARCHIVED

USAGE*

WORK*

TRACING

UNDER

USE

WRITE*

TRANSACTION*

UNDO

USING*

TRANSITIONAL

UNLIMITED

XID

TRIGGERS



oracle 8i上的优化参数

/*+ FULL(tablename) */: 明确的指定进行全表扫描

/*+ ROWID(tablename) */: 明确的选择根据rowid进行表扫描

/*+ CLUSTER(tablename) */: 明确的指定对指定表进行群集扫描, 只能应用于tablename的条件中有群集对象时

/*+ HASH(tablename) */: 明确的选择对指定表进行hash扫描, 只能应用于tablename的条件中有群集对象时

/*+ HASH_AJ */: 转化一个not in子查询为一个anti-join的hash扫描

/*+ HASH_SJ */: 转化一个EXISTS子查询为一个semi-join的hash扫描

/*+ INDEX(TABLENAME INDEXNAME) */: 明确的指定使用某个表的某个索引, 指定了这个优化参数, 优化器对这个表将不考虑进行全表扫描或用这个表的其它索引. 如果对表指定了多个索引, 那么优化器将会考查每一个索引的成本, 选择成本最小, 或者选择多个索引联合使用, 然后将结果再组合起来, 当这都建立在, 优化器认为这样作是成本最低的情况下. 如果没有指定索引, 优化器会考查表上每一个可以使用的索引.

/*+ INDEX_ASC(tablename indexname) */: 这个优化参数在缺省时与 INDEX 优化参数是一样的, 除非你的数据库中的索引扫描的缺省行为被改变了. 缺省行为是对索引进行正序扫描

/*+ INDEX_COMBINE(tablename indexname) */ : 如果不指定索引, 那么优化器将使用bitmap索引进行布尔组合, 而不管是不是成本最低的. 如果没有bitmap索引的话, 也会强制的转化一个索引为bitmap索引来进行布尔组合. 如果指定了索引,那也与上叙一样, 但会使用指定的索引.

/*+ INDEX_DESC(tablename indexname) */: 与 INDEX 优化参数一样, 不过将会以逆序进行索引扫描.

/*+ INDEX_FFS(tablename indexname) */: 会用索引快速扫描代替全表扫描.

/*+ MERGE_AJ */: 转化一个not in子查询为一个 merge anti-join 查询

/*+ MERGE_SJ */: 转化一个exists子查询为一个merge semi-join 查询

/*+ AND_EQUAL(tablename indexname indexname.....) */: 明确指定建立一种执行计划, 这个计划会把根据指定索引得到列进行组合. 这个优化参数必须指定至少二个索引, 但不能多于五个

/*+ USE_CONCAT */: 将where语句中的or运行转化为union all形的操作, 如果这个操作的成本比没有这个操作的成本低的话

/*+ ORDERED */: 指定oracle根据from后面表的顺序进行join操作

/*+ STAR */: 指定实行star plan, star plan将最大的表放在join操作的最后, 并进行nested loops join. 这个优化参数所用的语句,必须至少有三个表

/*+ USE_NL(TABLENAME) */: 在很多情况下 nested loops join比sort-merge join要快的返回第一行记录, 造成更快的反应速度,这个参数就是指定表要进行 nested loops join, 而不是sort-merge join

/*+ USE_MERGE(TABLENAME) */: 指定表要进行sort-merge join

/*+ USE_HASH(TABLENAME) */: 指定表进行hash join

/*+ DRIVING_SITE(TABLENAME) */: 指定表是在其它服务器上的

Sunday, September 10, 2006

有关成本统计数据新旧对执行计划的影响

有时候执行计划一直不是你想要的, 但oracle又一直用这样的执行计划, 那么可能是因为oracle中对表的成本统计数据太旧了. 这时, 可以运行 analyze table 表名 compute statistics 来对表的成本统计数据进行刷新.

什么是buffer(sort)

BUFFER (SORT)

Description

Performs a memory sort on a row source

Versions

This operation was introduced in Oracle 9.0.1 though it may have been used in limited circumstances in some earlier versions

This operation is implemented in the following versions


9.0.1
9.2.0

Examples

Example 1

This example was developed using Oracle 9.2.0.1 on Windows 2000

This example requires the following table and index definitions

    CREATE TABLE t1 (c1 NUMBER);
CREATE INDEX T1_C1 ON T1(C1);

At least one of the tables must be analysed

    ANALYZE TABLE t1 COMPUTE STATISTICS;

The statement

    SELECT t1.c01, t2.c01
FROM t1;

generates the following execution plan

   0     SELECT STATEMENT Optimizer=CHOOSE
1 0 MERGE JOIN (CARTESIAN)
2 1 TABLE ACCESS (FULL) OF 'T1'
3 1 BUFFER (SORT)
4 3 TABLE ACCESS (FULL) OF 'T2'
Example 2

This example was developed using Oracle 9.2.0.1 on Windows 2000

This example requires the following table and index definitions

    CREATE TABLE t1 (c1 NUMBER,c2 NUMBER);

In Oracle 9.0.1 and above the statement

    SELECT c1,c2 FROM t1
CONNECT BY c1 = PRIOR c2
START WITH c1 = 0;

generates the following execution plan

   0      SELECT STATEMENT Optimizer=CHOOSE
1 0 CONNECT BY (WITH FILTERING)
2 1 NESTED LOOPS
3 2 TABLE ACCESS (FULL) OF 'T1'
4 2 TABLE ACCESS (BY USER ROWID) OF 'T1'
5 1 NESTED LOOPS
6 5 BUFFER (SORT)
7 6 CONNECT BY PUMP
8 5 TABLE ACCESS (FULL) OF 'T1'

Thursday, September 07, 2006

有关 ORA-01779无法修改与非键值保存表对应的列 的解释

当对视图进行update时, 会出现ora-01779错误, 这个错误是因为要修改的字段在视图中不能保证物理上唯一,多出现在与多表关联时, 一个表因与另一个表的关联, 原表中的字段产生了多条记录的原故. 具体可以见 http://forums.oracle.com/forums/message.jspa?messageID=1266445
下面是其中一个回帖:
For a table to be key preserved in a view, the primary key of that table, if it were present in the view, ciould serve as a primary key of the view.

In your case, m.id cannot be a PK of the view because there could be more than one row in c pointing to the same row in m. Table c is key preserved (thus updateable) in the inner join version because each row of c can only appear once in the view.

Wednesday, August 30, 2006

从带各种符号的字段中把中文分离出来的方法

前些天, 在工作中遇到要从管道的名称中的解析出管道所在的路进行统计的问题. 系统中, 管道的名称基本都以"XX路-XX路"输入, "XX路"就是管道所在的道路名. 麻烦的是根据管道的结点, 名称里还加入了"XX路1-XX路1"或"XX路001#-XX路001#"之类的无关符号. 如果都要一个个进行解析, 那只能疯掉, 还好Oracle提供了一个批量的函数TRANSLATE. 这个函数就是把一个字符串中某些指定的字符用另一些字符来代替, 于是, 只要写成 TRANSLATE(管道名称, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ#_''"+-', ' ') 就能把名称中基本含有的无关的字符全变成了空格了, 而且如果字符是连续的, 那也会合成为一个空格, 真是美妙啊. 下面是TRANSLATE函数的英文原帮助:

TRANSLATE
Syntax
TRANSLATE(char, from, to)

Purpose
Returns char with all occurrences of each character in from replaced by its corresponding character in to. Characters in char that are not in from are not replaced. The argument from can contain more characters than to. In this case, the extra characters at the end of from have no corresponding characters in to. If these extra characters appear in char, they are removed from the return value. You cannot use an empty string for to to remove all characters in from from the return value. Oracle interprets the empty string as null, and if this function has a null argument, it returns null.


Example 1
The following statement translates a license number. All letters 'ABC...Z' are translated to 'X' and all digits '012 . . . 9' are translated to '9':



SELECT TRANSLATE('2KRW229',
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'9999999999XXXXXXXXXXXXXXXXXXXXXXXXXX') "License"
FROM DUAL;

License
--------
9XXX999

Example 2
The following statement returns a license number with the characters removed and the digits remaining:



SELECT TRANSLATE('2KRW229',
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'0123456789')
"Translate example"
FROM DUAL;

Translate example
-----------------
2229