模糊查詢是數據庫查詢中經常用到的,一般常用的格式如下:
(1)字段 like '%關鍵字%' 字段包含"關鍵字“的記錄 即使在目標字段建立索引也不會走索引,速度最慢
(2)字段 like '關鍵字%' 字段以"關鍵字"開始的記錄 可以使用到在目標字段建立的升序索引
(3)字段 like '%關鍵字' 字段以"關鍵字“結束的記錄 可以使用到目標字段建立的降序索引
對於無法使用索引的 '%關鍵字%' 模式,有沒有辦法優化呢,答案是肯定的,
在ORacle中提供了instr(strSource,strTarget)函數,比使用'%關鍵字%'的模式效率高很多。
instr函數說明:
INSTR
(源字符串, 目標字符串, 起始位置, 匹配序號)
在Oracle/PLSQL中,instr函數返回要截取的字符串在源字符串中的位置。只檢索一次,就是說從字符的開始
到字符的結尾就結束。
語法如下:
instr( string1, string2 [, start_position [, nth_appearance ] ] )
參數分析:
string1
源字符串,要在此字符串中查找。
string2
要在string1中查找的字符串.
start_position
代表string1 的哪個位置開始查找。此參數可選,如果省略默認為1. 字符串索引從1開始。如果此參數為正,從左到右開始檢索,如果此參數為負,從右到左檢索,返回要查找的字符串在源字符串中的開始索引。
nth_appearance
代表要查找第幾次出現的string2. 此參數可選,如果省略,默認為 1.如果為負數系統會報錯。
注意:
如果String2在String1中沒有找到,instr函數返回0.
示例:
SELECT instr('syranmo','s') FROM dual; -- 返回 1
SELECT instr('syranmo','ra') FROM dual; -- 返回 3
SELECT instr('syran mo','a',1,2) FROM dual; -- 返回 0
對比:
instr(title,'手冊')>0 相當於 title like '%手冊%'
instr(title,'手冊')=1 相當於 title like '手冊%'
instr(title,'手冊')=0 相當於 title not like '%手冊%'
模糊查詢優化:
了解了instr函數的用法,優化就變得簡單了,例如 %關鍵字% 等同於 instr(字段,'關鍵字')>0
實際應用:
t表中將近有1100萬數據,很多時候,我們要進行字符串匹配,在SQL語句中,我們通常使用like來達到我們搜索的目標。但經過實際測試發現,like的效率與instr函數差別相當大。下面是一些測試結果:
SQL> set timing on
SQL> select count(*) from t where instr(title,'手冊')>0;
COUNT(*)
----------
65881
Elapsed: 00:00:11.04
SQL> select count(*) from t where title like '%手冊%';
COUNT(*)
----------
65881
Elapsed: 00:00:31.47
SQL> select count(*) from t where instr(title,'手冊')=0;
COUNT(*)
----------
11554580
Elapsed: 00:00:11.31
SQL> select count(*) from t where title not like '%手冊%';
COUNT(*)
----------
11554580
另外,我在結另外一個2億多的表,使用8個並行,使用like查詢很久都不出來結果,但使用instr,4分鍾即完成查找,性能是相當的好。這些小技巧用好,工作效率提高不少。通過上面的測試說明,ORACLE內建的一些函數,是經過相當程度的優化的。
instr(title,’aaa’)>0 相當於like
instr(title,’aaa’)=0 相當於not like
特殊用法:
select id, name from users where instr('101914, 104703', id) > 0;
它等價於
select id, name from users where id = 101914 or id = 104703;
使用Oracle的instr函數與索引配合提高模糊查詢的效率
一般來說,在Oracle數據庫中,我們對tb表的name字段進行模糊查詢會采用下面兩種方式:
1.select * from tb where name like '%XX%';
2.select * from tb where instr(name,'XX')>0;
若是在name字段上沒有加索引,兩者效率差不多,基本沒有區別。
為提高效率,我們在name字段上可以加上非唯一性索引:
create index idx_tb_name on tb(name);
這樣,再使用
select * from tb where instr(name,'XX')>0;
這樣的語句查詢,效率可以提高不少,表數據量越大時兩者差別越大。但也要顧及到name字段加上索引后DML語句會使索引數據重新排序的影響。
另一種未知的方案:
有人說了用全文索引,我看了,步驟挺麻煩,但是是個不錯的方法,留着備用:
http://sandish.itpub.net/post/4899/464369
對cmng_custominfo 表中的address字段做全文檢索:
1,在oracle9201中需要創建一個分詞的東西:
BEGIN
ctx_ddl.create_preference ('SMS_ADDRESS_LEXER', 'CHINESE_LEXER');
--ctx_ddl.create_preference ('my_lexer', 'chinese_vgram_lexer'); 不用
end;
2,創建全文檢索:
CREATE INDEX INX_CUSTOMINFO_ADDR_DOCS ON cmng_custominfo(address) INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('LEXER SMS_ADDRESS_LEXER');
3,查詢時候,使用:
select * from cmng_custominfo where contains (address, '金色新城')>1;
4,需要定期進行同步和優化:
同步:根據新增記錄的文本內容更新全文搜索的索引。
begin
ctx_ddl.sync_index('INX_CUSTOMINFO_ADDR_DOCS');
end;
優化:根據被刪除記錄清除全文搜索索引中的垃圾
begin
ctx_ddl.optimize_index('INX_CUSTOMINFO_ADDR_DOCS', 'FAST');
end;
5,采用job做步驟4中的工作:
1)該功能需要利用oracle的JOB功能來完成
因為oracle9I默認不啟用JOB功能,所以首先需要增加ORACLE數據庫實例的JOB配置參數:
job_queue_processes=5
重新啟動oracle數據庫服務和listener服務。
2)同步 和 優化
--同步 sync:
variable jobno number;
BEGIN
DBMS_JOB.SUBMIT(:jobno,'ctx_ddl.sync_index(''INX_CUSTOMINFO_ADDR_DOCS'');', SYSDATE, 'SYSDATE + (1/24/4)');
commit;
END;
--優化
variable jobno number;
begin
DBMS_JOB.SUBMIT(:jobno,'ctx_ddl.optimize_index(''INX_CUSTOMINFO_ADDR_DOCS'',''FULL'');', SYSDATE, 'SYSDATE + 1');
commit;
END;
其中, 第一個job的SYSDATE + (1/24/4)是指每隔15分鍾同步一次,第二個job的SYSDATE + 1是每隔1天做一次全優化。具體的時間間隔,可以根據應用的需要而定
6,索引重建
重建索引會刪除原來的索引,重新生成索引,需要較長的時間。
重建索引語法如下:
ALTER INDEX INX_CUSTOMINFO_ADDR_DOCS REBUILD;
據網上一些用家的體會,oracle重建索引的速度也是比較快的,有一用家這樣描述:
Oracle 的全文檢索建立和維護索引要比ms sql server都要快得多,筆者的65萬記錄的一個表建立索引只需要20分鍾,同步一次只需要1分鍾。
因此,也可以考慮用job的辦法定期重建索引。