由於工作需要,筆者在HP UX, Soralis 上面設置了Oracle Intermedia來實現全文檢索。目前已經投入實際使用。設置過程中有許多問題和經驗,拿來和大家交流。本文依據的是Oracle 8.1.6 和8.1.7兩個版本,不能保證適用於其他版本。
目前全文檢索功能幾乎所有主流數據庫都支持。此前筆者曾在sql server 2000上實現,感覺非常簡單,方便,但創建全文檢索索引的時間比較長,通常要十幾個小時。Oracle 的全文檢索建立和維護索引都要快得多,筆者的65萬記錄的一個表建立索引只需要20分鐘,同步一次只需要1分鐘。但設置就要復雜得多。
一.設置過程
1.首先,檢查你的數據庫是否安裝了intermedia
這可以通過檢查是否有ctxsys用戶和ctxapp角色(role). 如果沒有這個用戶和角色,意味著你的數據庫創建時未安裝intermedia功能。你必須修改數據庫以安裝這項功能。
修改過程:
運行 $Oracle_HOME/bin/dbassist, 選擇'modify database', 然後在選擇數據庫功能時將j server 和 intermedia 都選上(安裝intermedia必須同時安裝JServer).強烈建議你在做這個改動前先備份整個數據庫。
2.設置extproc
Oracle 是通過所謂的‘外部調用功能’(external procedure)來實現intermedia的,因此正確地設置extproc是關鍵一步。
首先要配置listener 使它能監聽intermedia 調用的請求。你可以通過運行$ORACLE_HOME/bin/netassit 來進行配置,也可以手工修改配置文件:$Oracle_HOME/network/admin/listener.ora ,然後重新啟動listener。下面以一個例子來講述如何手工修改配置文件。
打開listener.ora文件,在修改前,通常有如下內容(假定使用缺省listener):
LISTENER =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = MYDATABASE)(PORT = 1521))
)
SID_LIST_LISTENER =
(SID_DESC =
(GLOBAL_DBNAME = mydatabase.world)
(ORACLE_HOME = /u01/app/Oracle/product/8.1.6)
(SID_NAME = mydatabase)
)
這個listener還沒有配置extproc, 因此,需要為它增加對extproc的監聽,辦法就是分別增加description 和 sid_desc. 修改後的listner.ora 如下:
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = MYDATABASE)(PORT = 1521))
)
(DESCRIPTION =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
)
)
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = mydatabase.world)
(ORACLE_HOME = /u01/app/Oracle/product/8.1.6)
(SID_NAME = mydatabase)
)
(SID_DESC =
(PROGRAM = extproc)
(SID_NAME = PLSExtProc)
(ORACLE_HOME = /u01/app/Oracle/product/8.1.6)
)
)
注意上面的host, global_dbname,sid_name,Oracle_home應填寫你的數據庫的實際值,但program一項必須填寫extproc.
其次,要配置服務器端的tnsnames.ora文件。該文件的位置在$Oracle_HOME/network/admin下面。同樣可以通過運行netasst來進行配置。
在tnsnames.ora文件中需要增加如下一項:
EXTPROC_CONNECTION_DATA,EXTPROC_CONNECTION_DATA.WORLD =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC))
)
(CONNECT_DATA =
(SID = PLSExtProc)
)
)
注意其中,KEY 和SID必須與listener.ora中的key 和sid_name對應相同。
修改完成後,重新啟動listener (先用lsnrctl stop, 然後 lsnrctl start), 然後,使用tnsping 來測試一下是否配置正確:
tnsping extproc_connection_data 或者
tnsping extproc_connection_data.world,如果配置正確,會顯示:
Attempting to contact (ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC)) OK(140毫秒)
否則請檢查你的上述兩個文件,並注意,在修改後一定要重新啟動listener,但並不需要重新啟動數據庫。
3.設置詞法分析器(lexer)
Oracle實現全文檢索,其機制其實很簡單。即通過Oracle專利的詞法分析器(lexer),將文章中所有的表意單元(Oracle 稱為 term) 找出來,記錄在一組 以 dr$開頭的表中,同時記下該term出現的位置、次數、hash 值等信息。檢索時,Oracle 從這組表中查找相應的 term,並計算其出現頻率,根據某個算法來計算每個文檔的得分(score),即所謂的‘匹配率’。而lexer則是該機制的核心,它決定了全文檢索的效率。Oracle 針對不同的語言提供了不同的 lexer, 而我們通常能用到其中的三個:
basic_lexer: 針對英語。它能根據空格和標點來將英語單詞從句子中分離,還能自動將一些出現頻率過高已經失去檢索意義的單詞作為‘垃圾’處理,如if , is 等,具有較高的處理效率。但該lexer應用於漢語則有很多問題,由於它只認空格和標點,而漢語的一句話中通常不會有空格,因此,它會把整句話作為一個term,事實上失去檢索能力。以‘中國人民站起來了’這句話為例,basic_lexer 分析的結果只有一個term ,就是‘中國人民站起來了’。此時若檢索‘中國’,將檢索不到內容。
chinese_vgram_lexer: 專門的漢語分析器,支持所有漢字字符集。該分析器按字為單元來分析漢語句子。‘中國人民站起來了’這句話,會被它分析成如下幾個term: ‘中’,‘中國’,‘國人’,‘人民’,‘民站’,‘站起’,起來’,‘來了’,‘了’。可以看出,這種分析方法,實現算法很簡單,並且能實現‘一網打盡’,但效率則是差強人意。
chinese_lexer: 這是一個新的漢語分析器,只支持utf8字符集。上面已經看到,chinese vgram lexer這個分析器由於不認識常用的漢語詞匯,因此分析的單元非常機械,像上面的‘民站’,‘站起’在漢語中根本不會單獨出現,因此這種term是沒有意義的,反而影響效率。chinese_lexer的最大改進就是該分析器 能認識大部分常用漢語詞匯,因此能更有效率地分析句子,像以上兩個愚蠢的單元將不會再出現,極大 提高了效率。但是它只支持 utf8, 如果你的數據庫是zhs16gbk字符集,則只能使用笨笨的那個Chinese vgram lexer.
如果不做任何設置,Oracle 缺省使用basic_lexer這個分析器。要指定使用哪一個lexer, 可以這樣操作:
第一.在ctxsys用戶下建立一個preference:
begin ctx_ddl.create_preference('my_lexer','chinese_vgram_lexer'); end;
第二.在建立intermedia索引時,指明所用的lexer:
create index myindex on mytable(mycolumn) indextype is ctxsys.context
parameters('lexer my_lexer');
這樣建立的全文檢索索引,就會使用chinese_vgram_lexer作為分析器。
4.使用job定時同步和優化
在intermedia索引建好後,如果表中的數據發生變化,比如增加或修改了記錄,怎麼辦?由於對表所發生的任何dml語句,都不會自動修改索引,因此,必須定時同步(sync)和優化(optimize)索引,以正確反映數據的變化。
在索引建好後,我們可以在該用戶下查到Oracle自動產生了以下幾個表:(假設索引名為myindex):
DR$myindex$I,DR$myindex$K,DR$myindex$R,DR$myindex$N
其中以I表最重要,可以查詢一下該表,看看有什麼內容:
select token_text, token_count from DR$I_RSK1$I where rownum<=20;
這裡就不列出查詢接過了。可以看到,該表中保存的其實就是Oracle 分析你的文檔後,生成的term記錄在這裡,包括term出現的位置、次數、hash值等。當文檔的內容改變後,可以想見這個I表的內容也應該相應改變,才能保證Oracle在做全文檢索時正確檢索到內容(因為所謂全文檢索,其實核心就是查詢這個表)。那麼如何維護該表的內容呢?總不能每次數據改變都重新建立索引吧!這就用到sync 和 optimize了。
同步(sync):將新的term 保存到I表;
優化(optimize):清除I表的垃圾,主要是將已經被刪除的term從I表刪除。
Oracle提供了一個所謂的ctx server來做這個同步和優化的工作,只需要在後台運行這個進程,它會監視數據的變化,及時進行同步。但筆者使用ctxserver碰到了許多問題,Oracle 北京的support也建議不使用,而是用以下的兩個job來完成(該job要建在和表同一個用戶下):
-- sync:
VARIABLE jobno number;
BEGIN
DBMS_JOB.SUBMIT(:jobno,'ctx_ddl.sync_index(''myindex'');',
SYSDATE, 'SYSDATE + (1/24/4)');
commit;
END;
-- optimizer
VARIABLE jobno number;
BEGIN
DBMS_JOB.SUBMIT(:jobno,'ctx_ddl.optimize_index(''myindex'',''FULL'');',
SYSDATE, 'SYSDATE + 1');
commit;
END;
其中, 第一個job的SYSDATE + (1/24/4)是指每隔15分鐘同步一次,第二個job的SYSDATE + 1是每隔1天做一次全優化。具體的時間間隔,你可以根據自己的應用的需要而定。至此,你的全文檢索功能已設置完成。
二、常見錯誤
下面就一些常見的錯誤信息給出解釋和解決辦法:
1, sync 失敗
DRG-10595: ALTER INDEX T_DOC6_CT失敗
DRG-50857: Oracle error in drsxsopen
ORA-01480: STR 賦值變量缺少空後綴
解決:這是8i的一個bug, 但可以避免它,方法是在同步之前先發一個語句:
alter session set nls_language=American;
2.create index 失敗
ORA-29855: 執行 ODCIINDEXCREATE 例行程序時出錯
ORA-20000: interMedia Text 錯誤:
ORA-06512: 在"CTXSYS.DRUE", line 126
ORA-06512: 在"CTXSYS.TEXTINDEXMETHODS", line 54
ORA-06512: 在line 1
解決:這是8.1.6.3之前的版本的一個bug, 在處理中文時,某個特殊字符造成的。向Oracle要補丁,或者自己去metalink.Oracle.com 下載(需要CSI 號碼)。
3.create index 失敗
RA-29855: 執行 ODCIINDEXCREATE 例行程序時出錯
ORA-20000: interMedia Text 錯誤:
DRG-50704: Net8 監聽器沒有運行或無法啟動外部過程
ORA-28575: 無法打開與外部過程代理程序的 RPC 連接
ORA-06512: 在"CTXSYS.DRUE", line 126
ORA-06512: 在"CTXSYS.TEXTINDEXMETHODS", line 54
ORA-06512: 在line 1
解決:明顯的extproc配置不當。仔細閱讀本文基本設置的第二步。
4.訪問建有索引的表時失敗
ora-29861: 域索引標記為loading/failed/unusable
解決:這是該表的一個intermedia索引有問題,該索引要麼沒有正確建立,要麼是某次同步失敗導致它狀態異常。先查到是哪個索引:
Select idx_name,idx_status from ctxsys.ctx_indexes;
然後同步該索引或者強制刪除它:
重建:alter index myindex rebuild online parameters('sync');
刪除:drop index myindex force;
5.使用chinese_lexer失敗
ERROR at row 1:
ORA-29855: err on ODCIINDEXCREATE
ORA-20000: interMedia Text err:
DRG-10502: index 1386 is not existing.
DRG-11102: the lexer cann't analyze as SIMPLIFIED CHINESE_CHINA.ZHS16GBK
ORA-06512: 在"CTXSYS.DRUE", line 126
ORA-06512: 在"CTXSYS.TEXTINDEXMETHODS", line 54
ORA-06512: 在line 1
解決:chinese_lexer 只支持utf8字符集。現在你面臨抉擇:忍受chinese vgram lexer的愚蠢,或者將數據庫字符集改到 utf8, 但面對可能引起你的應用不能正確處理中文的風險(先咨詢Oracle support, 並且與你的應用軟件提供商聯系)。
6.升級或應用patch後失敗
ORA-29856: err when execute ODCIINDEXDROP
ORA-20000: interMedia Texterr
ORA-06508: PL/SQL: can not find program unit beingcalled
ORA-06512: at "CTXSYS.DRUE", line 126
ORA-06512: at"CTXSYS.TEXTINDEXMETHODS", line 229
ORA-06512: at line 1
解決:這是intermedia的某個object 沒有正確產生或者編譯。用ctxsys用戶登錄後,運行 $oracle_home/ctx/admin/dr0pkh.sql 和 $oracle_home/ctx/admin/dr0plb.sql 以重新產生所有的package.你也可以直接察看dba_objects視圖,找出那些屬於ctxsys用戶並且status 為invalid的東西,重新產生或者重新編譯。(你可能會發現有許多這種東西,不要驚訝,Oracle不會因此而崩潰)。
7.create index 失敗
ERROR 位於第 1 行:
ORA-29855: 執行 ODCIINDEXCREATE 例行程序時出錯
ORA-20000: interMedia Text 錯誤:
DRG-50857: Oracle error in driddl.IndexResume
ORA-04030: 在嘗試分配 524288 字節 (cursor work he,QERHJ Bit vector)時進程內存不足
ORA-06512: 在"CTXSYS.DRUE", line 126
ORA-06512: 在"CTXSYS.TEXTINDEXMETHODS", line 214
ORA-06512: 在line 1
解決:引起這個問題可以有多種原因,首先你可以將sort_area_size這個參數減小到不多於2M,這可以防止Oracle在創建索引時分配太多的sort 內存而耗盡資源。 但如果這不起作用,而且你是8.1.7, 則恭喜,你hit 了bug 1391737. 該bug 在你要建索引的字段,如果某條記錄的長度超過2000字符時引起Oracle耗盡內存資源。別無它法,除了打 8.1.7.1B 的補丁。
Oracle Intermedia 的應用還有許多豐富的話題,比如它那24個操作符、字典等。有時間我們繼續再談。