【茶包射手日記】偶發型 Oracle Client 版本不相容錯誤
10 |
同事報案,有個 ASP.NET 網站「偶爾」會出現提供者與 Oracle 從屬版本不相容錯誤。這是 Unmanaged ODP.NET 的經典茶包, 舉凡 Oracle Client 沒裝好、存取權限跑掉、多版本並存都很容易踩到雷。(由衷建議大家改用 Managed ODP.NET,能有效改善生活品質)
但這次跟以前遇到的狀況不太一樣,不定期發生,出錯後 IISRESET 可恢復,若單純只是 Oracle Client 安裝或設定問題,不該時好時壞,看起來是枚變種茶包。
由同事提供資訊我找到一處可疑:該台 IIS 有多個 WebApplication,且部分 WebApplication 共用 AppPool。於是我大膽假設:會不會是共用 AppPool 的 WebApplication 使用不同版本 Oracle 彼此干擾?而啟動順序又決定是否出錯,例如:WebAP1 先啟動再跑 WebAP2 沒事;若 WebAP2 先起來則會讓 WebAP1 壞掉。
此推論若能成立,「不定期發生」跟「IISRESET 後正常」便可得到合理解釋。
我設計了一個實驗驗證假設,在 IIS 建立兩個 WebApplication - OraAP12 及 OraAP19,放入同一隻程式 default.aspx:
<%@ Page Language="C#" %>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
var cs = "Data Source=MYORA;User ID=*****;Password=****;";
var codeBase = typeof(Oracle.DataAccess.Client.OracleConnection).Assembly.CodeBase;
Response.ContentType = "text/plain";
try {
using (var cn = new Oracle.DataAccess.Client.OracleConnection(cs))
{
cn.Open();
var cmd = cn.CreateCommand();
cmd.CommandText = "SELECT 'HELLO' AS t FROM DUAL";
var dr = cmd.ExecuteReader();
dr.Read();
Response.Write(dr["t"] + "\n" + codeBase);
}
}
catch (System.ApplicationException ex)
{
Response.Write("ERROR-" + ex.Message);
if (ex.InnerException != null)
{
Response.Write("\n" + ex.InnerException.Message);
}
}
}
</script>
兩個資料夾只差在 bin\Oracle.DataAccess.dll 版本不同,OraAP12 是 2.122.1,OraAP19 則是 2.122.19,二者共用同一個 AppPool。
正常執行的話應要看到 HELLO 及 ODP.NET 版號:(遇有一個現象值得注意,Inline ASPX 採現場編譯,bin\Oracle.DataAccess.dll 會決定參照版本,但因 GAC 已註冊,實際用的是 GAC 版本。延伸閱讀:ASP.NET /bin 組件載入跟你想的不一樣)
來看看先啟動 OraAP12 或先啟動 OraAP19 結果會不會不一樣? 這種情境,Windows Terminal 的分割窗格特別好用,在 IISRESET 之後先啟用 OraAP19 再啟用 OraAP12 (下圖上方),兩個 WebApplication 都正常; 若 IISRESET 後先啟用 OraAP12 再啟用 OraAP19,則 OraAP19 會發生 'Oracle.DataAccess.Client.OracleConnection' 的類型初始設定式發生例外狀況。無法載入 DLL 'OraOps19.dll': 找不到指定的程序。 (發生例外狀況於 HRESULT: 0x8007007F)
錯誤。
由實驗結果可知- 若 WebApplication 共用 AppPool 又使用不同版本 Unmanaged ODP.NET,二者可能互相干擾,且出錯與否由 WebApplication 啟動順序決定。
因此,實務上應避免共用 AppPool,除了避免互相干擾,網站發生問題時能從工作管理員確認各 WebApplication 使用的 CPU、記憶體,有利於釐清問題。
最後不免推一把 - 多想兩分鐘,你可以不要用 Unmanaged ODP.NET,改用 Managed ODP.NET 人生更快活。
A issue of Oracle client version conflict between two web apllication sharing one AppPoool.
Comments
# by Huang
IIS集區分開是好習慣,雖然會有多個connection
# by Jackson245
廠商跟我說,網站設計一定要 SQL server,但當初規劃時沒列入這個項目。 目前只能用 SQL express 暫頂一下,資料量小於 100萬筆 但又說不贏廠商,請問這是真的嗎?
# by Jeffrey
to Jackson245,SQL Express 有性能及管理能力上的限制,細節可參考這篇 https://blog.darkthread.net/blog/sql-oracle-free-editions/,如果流量不大,是可以在發展初期先頂著,待不夠用時再無痛升級 Standard。(搬資料庫要花功夫就是了)
# by Andy
Dear 黑暗大 不好意思,想跟您請教個問題, 我有開發一個系統,用 Microsoft Visual Web Developer 2010 Express 進行開發。 系統開發完也使用了2~3年,沒什麼大問題,最近想把DB由 MS SQL 切換為 Oracle 遇到一些狀況。 描述如下: 我在網頁拉一個GridView進來,透過<設定資料來源>在 SELECT 及 UPDATE 自定義 SQL 陳述式, 達到讀取、更新 GridView 的內容。 資料來源為MS SQL 時:寫法如下 SELECT [EngineerNo], [Engineer], [CAEID] FROM [MechanismAnalysisEngineer] UPDATE [MechanismAnalysisEngineer] SET [Engineer] = @Engineer, [CAEID] = @CAEID WHERE [EngineerNo] = @EngineerNo ===>都沒問題 資料來源為Oravle 時:寫法如下 SELECT engineerno,engineer,caeid FROM mechanismanalysisengineer ===> 沒問題,可看到GridView內容 因為有上網查一下Oracle 如何指定變數...等,所以有把原先寫在MS SQL時的[]拿掉、@換成: (如下) UPDATE MECHANISMANALYSISENGINEER SET ENGINEER = :Engineer, CAEID = :CAEID WHERE ENGINEERNO = :ENGINEERNO ===> 出現錯誤【ORA-01008: 部分變數未被連結】 索性我先故意指定要改的ENGINEERNO (如下) UPDATE TC_ADM.MECHANISMANALYSISENGINEER SET ENGINEER = :Engineer, CAEID = :CAEID WHERE ENGINEERNO = 3 ===> 出現錯誤【ORA-01008: 部分變數未被連結】 直接強制要改的內容,不定義變數 (如下) UPDATE TC_ADM.MECHANISMANALYSISENGINEER SET ENGINEER = 'ABC', CAEID = '123' WHERE ENGINEERNO = 3 ===> 沒有問題,GridView 內容會針對我下的內容而改變 所以想跟黑暗大請教一下,是否能點出我在Oracle那邊定義變數的寫法問題出在哪兒? 還請黑暗大協助,感謝
# by Jeffrey
to Andy,錯誤蠻明確的,就是 OracleParameter 沒有完整對映 PL/SQL 中的 :變數,檢查一下 OracleCommand.Parameters.Add() 的部分有沒有寫錯,或是你貼上來大家幫忙找錯。(我有時也會鬼打牆,自已檢查幾遍都看不出來,別人一點就醒了)
# by Andy
to 黑暗大 請問是貼這一段嗎 <asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" DataKeyNames="ENGINEERNO" DataSourceID="SqlDataSource2" EmptyDataText="沒有資料錄可顯示。" Visible="False" onrowdatabound="GridView2_RowDataBound"> <Columns> <asp:CommandField ShowEditButton="True" /> <asp:BoundField DataField="ENGINEER" HeaderText="ENGINEER" SortExpression="ENGINEER" > </asp:BoundField> <asp:BoundField DataField="CAEID" HeaderText="CAEID" SortExpression="CAEID" /> </Columns> </asp:GridView> <asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:DNMDConnectionString %>" ProviderName="<%$ ConnectionStrings:DNMDConnectionString.ProviderName %>" SelectCommand="SELECT EngineerNo, Engineer, CAEID FROM MechanismAnalysisEngineer" UpdateCommand="UPDATE TC_ADM.MECHANISMANALYSISENGINEER SET ENGINEER = :Engineer, CAEID = :CAEID WHERE (ENGINEERNO = :ENGINEERNO)"> <UpdateParameters> <asp:Parameter Name="Engineer" Type="String" /> <asp:Parameter Name="CAEID" Type="String" /> <asp:Parameter Name="EngineerNo" Type="Int32" /> </UpdateParameters> </asp:SqlDataSource>
# by Jeffrey
to Andy, 我沒用過 SqlDataSource,所知有限。但有看了一些網路討論,你是不是應該用 <asp:ControlParameter> 才會繫結到 UI 欄位?參考:https://stackoverflow.com/q/11104135/288936
# by Andy
to 黑暗大 感謝黑暗大幫忙找尋解答, 改用<asp:ControlParameter> 後(如---下內容),仍出現錯誤【ORA-01008: 部分變數未被連結】; 另外,我有參考:https://blog.xuite.net/sugopili/computerblog/22869008 這篇===>(三)其他問題: 2. 的說明,將ProviderName設定為 System.Data.OracleClient。 出現錯誤【不支援關鍵字: 'provider'。】。 看來我還得再找找其他解答 ---------------------------------------------------------------------------------------------- <asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:DNMDConnectionString %>" ProviderName="System.Data.OleDb" SelectCommand="SELECT ENGINEERNO, ENGINEER, CAEID FROM TC_ADM.MECHANISMANALYSISENGINEER" UpdateCommand="UPDATE TC_ADM.MECHANISMANALYSISENGINEER SET ENGINEERNO = :ENGINEERNO, ENGINEER = :ENGINEER, CAEID = :CAEID WHERE (ENGINEERNO = :ENGINEERNO)"> <UpdateParameters> <asp:ControlParameter ControlID="GridView2" Name="Engineer" Type="String" /> <asp:ControlParameter ControlID="GridView2" Name="CAEID" Type="String" /> <asp:ControlParameter ControlID="GridView2" Name="EngineerNo" Type="Int32" /> </UpdateParameters> </asp:SqlDataSource>
# by Jeffrey
to Andy, SqlDataSource 是上古時代的技術,確實有可能只支援 System.Data.OracleClient 不支援 ODP.NET。若是如此,值得試試換 System.Data.OracleClient 再改回原本的 asp:Parameter 測試。 我發現你貼的程式碼 Provider = System.Data.OleDb,不是 System.Data.OracleClient,加上錯誤訊息"不支援關鍵字: 'provider'",會是 Provider 跟連線字串搞錯嗎?(OLEDB 連線字串會有 Provider=OraOLEDB.Oracle;Data Source=xxx,Oracle Client 不認得 Provider=...)
# by Andy
to 黑暗大 不好意思,因為我是先測改用<asp:ControlParameter>的結果,所以貼出來的還沒改ProviderName, 而將ProviderName改為System.Data.OracleClient的內容,因為出現錯誤【不支援關鍵字: 'provider'。】 ===>所以我就沒有PO出來了 ※補充說明: 錯誤1:是我能進到該頁面並看得到Gridview內容,只是要更新內容時出現錯誤【ORA-01008: 部分變數未被連結】 錯誤2:是我一進該頁面之後就馬上出現錯誤【不支援關鍵字: 'provider'。】 至於(值得試試換 System.Data.OracleClient 再改回原本的 asp:Parameter 測試) ===>也是出現同樣的錯誤【不支援關鍵字: 'provider'。】 這次怕您誤解,貼出內容如下。 <asp:SqlDataSource ID="SqlDataSource2" runat="server" ConnectionString="<%$ ConnectionStrings:DNMDConnectionString %>" ProviderName="System.Data.OracleClient" SelectCommand="SELECT ENGINEERNO, ENGINEER, CAEID FROM TC_ADM.MECHANISMANALYSISENGINEER" UpdateCommand="UPDATE TC_ADM.MECHANISMANALYSISENGINEER SET ENGINEERNO = :ENGINEERNO, ENGINEER = :ENGINEER, CAEID = :CAEID WHERE (ENGINEERNO = :ENGINEERNO)"> <UpdateParameters> <asp:Parameter Name="Engineer" Type="String" /> <asp:Parameter Name="CAEID" Type="String" /> <asp:Parameter Name="EngineerNo" Type="Int32" /> </UpdateParameters> </asp:SqlDataSource> 關於(OLEDB 連線字串會有 Provider=OraOLEDB.Oracle;Data Source=xxx,Oracle Client 不認得 Provider=...)===>我來找找有無相關文章 感謝黑暗大