1. 程式人生 > >隱式轉換帶來的麻煩

隱式轉換帶來的麻煩

oracle的隱式轉換有時候會給我們帶來很隱蔽的麻煩。看一個簡單的例子:

SQL>create table t1(val varchar2(10));

Table created.

SQL>insert into t1 values('XXX');

1 row created.

SQL>insert into t1 values('100');

1 row created.

SQL>commit;

Commit complete.

SQL>select * from t1 where val=100 and rownum<2;
select * from t1 where val=100 and rownum<2
                       *
ERROR at line 1:
ORA-01722: invalid number

再看另一個例子:

SQL>create table t2(val varchar2(10));

Table created.

SQL>insert into t2 values('100');

1 row created.

SQL>insert into t2 values('XXX');

1 row created.

SQL>commit;

Commit complete.

SQL>select * from t2 where val=100 and rownum<2;

VAL
----------
100

查詢成功了。仔細觀察兩個例子,唯一的不同僅僅是兩條資料的插入順序不一樣。資料庫理論告訴我們,元組(行)在關係(表)中是沒有順序的,我們的程式不應當也不能依賴行的儲存順序。為什麼這裡會導致不同的結果呢?原因在於val欄位型別是varchar2,在和數字100做比較的時候,oracle自動把val轉換成了to_number(val)。兩個例子都是全表掃描,對t1表,oracle首先讀到的第一條記錄是‘XXX',to_number自然就失敗了,對t2表,oracle讀到的第一條記錄是'100',to_number成功且條件匹配,由於rownum條件只讀取一條,所以立刻退出了,沒發生任何錯誤。如果去掉rownum條件,則兩個例子都失敗。當然,如果oracle不轉換val,而是把100轉換成to_char(100),那就沒問題了,但oracle認為數字比較總是比字串比較效率高,所以優先將字串型別轉換為數字型別。

這不僅僅發生在表,索引上同樣也會發生類似的情況。所以在設計資料庫結構的時候,一定不要為了方便用varchar2去替代其他資料型別,否則一條錯誤資料就可能讓程式崩潰。

查詢oracle自己提供的檢視也會碰到類似的情況,典型例子就是v$parameter,其中的value欄位型別是varchar2,但儲存的值有時候是需要轉換成數字來進行相應計算的。一個常見的場景是direct path read/write 事件,如果是對TEMP檔案進行讀寫,我們需要這樣來獲取檔名稱:(假設事件P1值是201)

select a.name from v$tempfile a, v$parameter b where b.name='db_files' and a.file# + b.value = 201;

上面的sql是從《Oracle Wait Interface: A Practical Guide to Performance Diagnositics & Tuning》這本書裡摘錄的,在我的環境下(11.2.0.1.0),得到的唯一結果是ORA-01722。繞過去的辦法是改變一下寫法:
select a.name from v$tempfile a, v$parameter b where b.name='db_files' and a.file# = 201 - b.value ;

這樣能查詢到正確結果。但這樣做僅僅是通過運氣(如果有興趣可以仔細研究一下兩條語句不同的執行計劃),正確的做法應該是始終保證比較型別的一致:
select a.name from v$tempfile a, v$parameter b where b.name='db_files' and b.value = to_char( 201 - a.file# ) ;


這樣的話就能保證無論什麼情況下都能得到正確結果。