伺服器為Oracle
9i,NHibernate使用NHibernate.Driver.OracleDataClientDriver(Oracle.DataAccess.dll,使用ODTwithODAC1020221這個版本安裝。因為正式部署打算用Oracle
10g,所以用戶端NHibernate用了這個驅動,另外也是為了避免System.Data.OracleClient.dll操作Clob/Nclob時的問題),陸續發現不少奇怪的問題。不清楚是這個驅動跟Oracle
9i之間的相容性造成還是其它原因,不過最終基本都解決了。
1. 今天解決了2個效能方面的問題,原因都是日期類型的欄位造成。
1.1
第一個查詢是對一個60多萬的表執行單表查詢,有一個日期欄位建了索引,查詢會使用這個日期欄位。一開始查詢很慢,估計是NHibernate產生的SQL語句沒有使用到索引(把日誌中的SQL語句拿出來分析,查詢計劃中顯示是使用了索引的)。解決方案是按照使用NHibernate, Oracle Clob/NClob無法插入、亂碼問題的方式建立一個使用者自訂日期類型,將IDbCommand中對應參數的OracleDbType設定為OracleDbType.Date,然後在NHibernate中把日期欄位配置為這個類型。這樣查詢就能使用到索引了。
1.2 跟第一個問題基本類似。
也是一個查詢,涉及到多個表的關聯,有個表資料400多萬。同樣估計是一個日期欄位的索引沒有發揮作用而導致的低效(同樣,SQL語句抓出來用查詢計劃分析,顯示索引是使用上了),但是按照1.1中的方法之後還是不起作用。唯一不同之處是1.1用的ICriteria,而這個查詢用的是IQuery。
解決方案是把sql改成類似b.CreateDate>to_date(:startDate,
'YYYY-MM-DD')這種形式,然後使用IQuery.SetString()方法而不是IQuery.SetDateTime(),這樣就OK了。
第一個問題還好理解,如果參數類型沒有設定正確無法利用索引,比較正常,但這個方法無法運用在第二個類似的問題上就比較費解了。
2. 用Oracle.DataAccess 10.2這個版本調用Oracle 9i的Function/Procedure時問題比較多。
2.1 正常情況下調用應當是這個樣子:
OracleConnection con = new OracleConnection("connection string");
con.Open();
OracleCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "function / procedure name"; cmd.Parameters.Add(new
OracleParameter("param name", OracleDbType.Double).Value = 12.1);
int effectedRows = cmd.ExecuteNonQuery();
con.Close();
但在Oracle 9i上函數或預存程序聲明的參數類型為Number(老系統的東西,不敢改動),而ODAC
10.2的OracleDbType是沒有Number這個類型的,把參數設定成OracleDbType.Double、OracleDbType.Decimal等各種類型,均報錯無法轉換成數字類型。
只有改成下面這種方式才能成功調用:
OracleConnection con = new OracleConnection("connection string");
con.Open();
OracleCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = @"
Declare v_value Number;
Begin v_value:=function or procedure name(:p1, :p2, :returnValue);
End;";
cmd.Parameters.Add(new OracleParameter("p1", OracleDbType.Double).Value = 12.1);
cmd.Parameters.Add(new OracleParameter("p2", OracleDbType.Varchar2).Value =
"Test");
cmd.Parameters.Add(new OracleParameter("returnValue", OracleDbType.Int32,
ParameterDirection.Output).Value = 0);
int effectedRows = cmd.ExecuteNonQuery();
con.Close();
if (cmd.Parameters[2].Value != null && cmd.Parameters[2].Value != DBNull.Value)
{
int returnValue = Convert.ToInt32(cmd.Parameters[2].Value);
/*....*/
}
2.2 如果Function/Procedure有out型的參數,參數類型為Varchar2等字串型的,必須指定參數的Size屬性,否則報緩衝區溢位錯誤。
3. 極個別的情況下,用戶端已經把一條資料刪除了,用SQL查詢資料庫確實查不到這條資料,但是NHibernate仍然能夠擷取到這個對象。
確認NHibernate沒有開二級緩衝;確認NHibernate有向伺服器提交這個SQL語句,跟蹤到NHibernate,在執行DbCommand查詢時產生的SQL語句正確,參數設定正確,日誌中記錄的這些也是正確的,但NHibernate執行完之後的確返回了這條資料;監控資料庫伺服器,確認伺服器根本沒有執行這個SQL。所以把問題確定在ODAC
10.2這個驅動上面。但更奇怪的是用戶端(Web
Server,上面配置了Oracle網路服務)重起機器,問題還在;資料庫伺服器重起機器,問題還在,還能查出那條資料。後來再沒有發生這種情況,也無從查起。
2008.01.06
上面兩個問題都是Oracle沒有找准索引,從程式層面將參數資訊設定準確確實比較重要。
另外最佳化過程中不斷髮現Oracle找不準索引的情況,在不少查詢中添加了指定索引,甚至是JOIN TYPE的hints,倒忘了另外一個重要方面:測試庫經常大量的匯入測試資料,需要分析維護Oracle索引!