服務(wù)器為Oracle 9i,NHibernate使用NHibernate.Driver.OracleDataClientDriver(Oracle.DataAccess.dll,使用ODTwithODAC1020221這個(gè)版本安裝。因?yàn)檎讲渴鸫蛩阌肙racle 10g,所以客戶端NHibernate用了這個(gè)驅(qū)動(dòng),另外也是為了避免System.Data.OracleClient.dll操作Clob/Nclob時(shí)的問(wèn)題),陸續(xù)發(fā)現(xiàn)不少奇怪的問(wèn)題。不清楚是這個(gè)驅(qū)動(dòng)跟Oracle 9i之間的兼容性造成還是其它原因,不過(guò)最終基本都解決了。 1. 今天解決了2個(gè)性能方面的問(wèn)題,原因都是日期類型的字段造成。 1.1 第一個(gè)查詢是對(duì)一個(gè)60多萬(wàn)的表執(zhí)行單表查詢,有一個(gè)日期字段建了索引,查詢會(huì)使用這個(gè)日期字段。一開(kāi)始查詢很慢,估計(jì)是NHibernate生成的SQL語(yǔ)句沒(méi)有使用到索引(把日志中的SQL語(yǔ)句拿出來(lái)分析,查詢計(jì)劃中顯示是使用了索引的)。解決方法是按照使用NHibernate, Oracle Clob/NClob無(wú)法插入、亂碼問(wèn)題的方式創(chuàng)建一個(gè)用戶自定義日期類型,將IDbCommand中對(duì)應(yīng)參數(shù)的OracleDbType設(shè)置為OracleDbType.Date,然后在NHibernate中把日期字段配置為這個(gè)類型。這樣查詢就能使用到索引了。
1.2 跟第一個(gè)問(wèn)題基本類似。 也是一個(gè)查詢,涉及到多個(gè)表的關(guān)聯(lián),有個(gè)表數(shù)據(jù)400多萬(wàn)。同樣估計(jì)是一個(gè)日期字段的索引沒(méi)有發(fā)揮作用而導(dǎo)致的低效(同樣,SQL語(yǔ)句抓出來(lái)用查詢計(jì)劃分析,顯示索引是使用上了),但是按照1.1中的方法之后還是不起作用。唯一不同之處是1.1用的ICriteria,而這個(gè)查詢用的是IQuery。 解決方法是把sql改成類似b.CreateDate>to_date(:startDate, ’YYYY-MM-DD’)這種形式,然后使用IQuery.SetString()方法而不是IQuery.SetDateTime(),這樣就OK了。
第一個(gè)問(wèn)題還好理解,如果參數(shù)類型沒(méi)有設(shè)置正確無(wú)法利用索引,比較正常,但這個(gè)方法無(wú)法運(yùn)用在第二個(gè)類似的問(wèn)題上就比較費(fèi)解了。
2. 用Oracle.DataAccess 10.2這個(gè)版本調(diào)用Oracle 9i的Function/Procedure時(shí)問(wèn)題比較多。 2.1 正常情況下調(diào)用應(yīng)當(dāng)是這個(gè)樣子: 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上函數(shù)或存儲(chǔ)過(guò)程聲明的參數(shù)類型為Number(老系統(tǒng)的東西,不敢改動(dòng)),而ODAC 10.2的OracleDbType是沒(méi)有Number這個(gè)類型的,把參數(shù)設(shè)置成OracleDbType.Double、OracleDbType.Decimal等各種類型,均報(bào)錯(cuò)無(wú)法轉(zhuǎn)換成數(shù)字類型。 只有改成下面這種方式才能成功調(diào)用: 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型的參數(shù),參數(shù)類型為Varchar2等字符串型的,必須指定參數(shù)的Size屬性,否則報(bào)緩沖區(qū)溢出錯(cuò)誤。
3. 極個(gè)別的情況下,客戶端已經(jīng)把一條數(shù)據(jù)刪除了,用SQL查詢數(shù)據(jù)庫(kù)確實(shí)查不到這條數(shù)據(jù),但是NHibernate仍然能夠獲取到這個(gè)對(duì)象。 確認(rèn)NHibernate沒(méi)有開(kāi)二級(jí)緩存;確認(rèn)NHibernate有向服務(wù)器提交這個(gè)SQL語(yǔ)句,跟蹤到NHibernate,在執(zhí)行DbCommand查詢時(shí)生成的SQL語(yǔ)句正確,參數(shù)設(shè)置正確,日志中記錄的這些也是正確的,但NHibernate執(zhí)行完之后的確返回了這條數(shù)據(jù);監(jiān)控?cái)?shù)據(jù)庫(kù)服務(wù)器,確認(rèn)服務(wù)器根本沒(méi)有執(zhí)行這個(gè)SQL。所以把問(wèn)題確定在ODAC 10.2這個(gè)驅(qū)動(dòng)上面。但更奇怪的是客戶端(Web Server,上面配置了Oracle網(wǎng)絡(luò)服務(wù))重起機(jī)器,問(wèn)題還在;數(shù)據(jù)庫(kù)服務(wù)器重起機(jī)器,問(wèn)題還在,還能查出那條數(shù)據(jù)。后來(lái)再?zèng)]有發(fā)生這種情況,也無(wú)從查起。
2008.01.06 上面兩個(gè)問(wèn)題都是Oracle沒(méi)有找準(zhǔn)索引,從程序?qū)用鎸?shù)信息設(shè)置準(zhǔn)確確實(shí)比較重要。 另外優(yōu)化過(guò)程中不斷發(fā)現(xiàn)Oracle找不準(zhǔn)索引的情況,在不少查詢中添加了指定索引,甚至是JOIN TYPE的hints,倒忘了另外一個(gè)重要方面:測(cè)試庫(kù)經(jīng)常大量的導(dǎo)入測(cè)試數(shù)據(jù),需要分析維護(hù)Oracle索引!
|