質問者
データベースコネクションが取得出来ない現象について

質問
-
みなさま、こんにちは。
表題の件につきまして、過去ログやネット検索などしましたが、
中々、同様の現象や解決方法が見つからずに困っております。
どなたかご意見いただければ幸いです。
クライアント環境:WindowsXP(10gClient,11gClient混在)
サーバ環境:Windows Server 2008
データベース:Oracle Database 11g Release 2 (11.2.0.1.0)
開発環境:VS2005(C#)
接続(ADO.NET)
【詳細】
Windows系の業務アプリケーションを作成しております。(Web系ではありません)
画面の中で、ある一覧より明細を選択し、登録ボタンにて該当の明細を更新するという画面処理を作成しました。
DBへも正常に接続し、データ取得やデータ更新など問題なく動作するのですが、
明細選択→データ更新を繰り返していると、不定期ですが突然コネクションが
出来なくなり、エラーとなってしまう現象が起きてしまいます。
Exception MSG【操作が無効です。接続は閉じています。】
DebugModeにてExceptionの際にコネクションの内容を確認すると状態が[Closed]になっていました。
DB接続(Pool Size)などが問題でコネクションが取得できていないのかなと思い、
Oracleの接続部分など確認しましたが、pooling=trueとしており、
また、V$SESSION等も確認し接続が増えていないことは確認しました。
毎回、新たな接続で、使用後は破棄(GCまかせですが)されているのに
何が問題で、コネクションが取得出来なくなるのか困っております。
以下簡単にですがソースです。
【DB接続】
クラス:DbConect
コネクション取得メソッド
public static OracleConnection GetConnection()
{
string connectString = "User ID=XXX; Password=XXX; Data Source=XXX; pooling=true";
OracleConnection con = new OracleConnection(connectString);
con.Open();
return con;
}
---------------------------------------------------------------------------
【DB接続呼び出し側】
クラス:TestTable
public bool GetData()
{
bool isExist = false;
using (OracleConnection con = DbConect.GetConnection())
{
using (OracleCommand cmd = con.CreateCommand())
{
isExist = GetData(cmd);
cmd.Dispose();
}
con.Close();
con.Dispose();
}
return isExist;
}
public bool GetData(OracleCommand cmd)
{
bool isExist = false;
StringBuilder sql = new StringBuilder("");
sql.Append("SELECT ");
sql.Append(" * ");
sql.Append("FROM ");
sql.Append(" TEST_TABLE ");
cmd.CommandText = sql.ToString();
using (OracleDataReader rd = cmd.ExecuteReader())
{
if (rd.Read())
{
isExist = true;
}
rd.Close();
rd.Dispose();
}
return isExist;
}
try~catch等は上位側に仕掛けております。
---------------------------------------------------------------------------
取得したTrace情報も記載します。
Test , XXX.YYY.Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null , 操作が無効です。接続は閉じています。
場所 System.Data.OracleClient.OracleConnection.GetOpenInternalConnection()
場所 System.Data.OracleClient.OracleConnection.get_ErrorHandle()
場所 System.Data.OracleClient.OracleCommand.Execute(OciStatementHandle statementHandle, CommandBehavior behavior, Boolean needRowid, OciRowidDescriptor& rowidDescriptor, ArrayList& resultParameterOrdinals)
場所 System.Data.OracleClient.OracleCommand.Execute(OciStatementHandle statementHandle, CommandBehavior behavior, ArrayList& resultParameterOrdinals)
場所 System.Data.OracleClient.OracleCommand.ExecuteReader(CommandBehavior behavior)
場所 System.Data.OracleClient.OracleCommand.ExecuteReader()
場所 XXX.YYY.Test.TestTable.GetDat(OracleCommand cmd)
すべての返信
-
環境が Visual Studio 2005 とのことなので、.NET 2.0 の既知の不具合ですかね。
.NET 3.5 SP1 のランタイム上で動かせば問題ないと思いますが・・・、今後のことを考えるなら ODP.NET への移行を考えたほうがよいかと思います。(.NET 2.0, 3.0, 3.5 SPなしは、サポートが終了しています)
# KB に hotfix もあったんじゃないかと思います- 編集済み K. Takaoka 2012年12月6日 12:29
-
回答を下さった皆様へ
解決の糸口が見つかりましたので内容を記載します。
また、考えてくださった皆様にお詫びがあります。
記載したソースについて、皆様にわかりやすく伝えようと思い
削った部分が多々ありました。そこに初歩的な問題がありました。
その件について、心より詫び致します。
以下、本来の内容です。
--------------------------------------------------------------
【DB接続】
クラス:DbConect
メンバ変数
private OracleConnection con = null;
private DbConect()
{
string connectString = "User ID=XXX; Password=XXX; Data Source=XXX; pooling=true";
this.con = new OracleConnection(connectString);
this.con.Open();
}
public static DbConect GetInst()
{
return new DbConect();
}
public OracleConnection GetConnection()
{
return this.con;
}
--------------------------------------------------------------
【DB接続呼び出し側】
クラス:Test
【NG】
public bool GetData()
{
bool isExist = false;
using (OracleConnection con = DbConect.GetInst().GetConnection())
{
using (OracleCommand cmd = con.CreateCommand())
{
isExist = GetData(cmd);
}
con.Close();
}
return isExist;
}
※DbConectの実体がメソッドを抜けるまで保障されておらず、
GCのタイミングで破棄される為、本来消えてはいけないコネクションが
実際のデータ取得時に不定期になくなるような事になっておりました。
【修正】
public bool GetData()
{
bool isExist = false;
DbConect dbCon = DbConect.GetInst();
using (OracleConnection con = dbCon.GetConnection())
{
using (OracleCommand cmd = con.CreateCommand())
{
isExist = GetData(cmd);
}
con.Close();
}
return isExist;
}
※DbConectをメソッド変数とし、メソッドが抜けるまで保障されるように記載。
--------------------------------------------------------------
記載したDbConectクラスについては、皆様それぞれご意見があるかとは思われますが、
何卒、ご容赦下さい。
端的に考えれば、DbConect自身を生成せずコネクションを生成して
returnするようなものであれば問題なかったと思います。
私の分析、認識不足でこのような現象が発生したと痛感しております。
ご意見下さった皆様、ありがとうございました。
また、申し訳ありませんでした。 -
書いてないですが、~DbConnect() に this.conn.Dispose() でも書いてあるんでしょうか?デストラクタで接続が閉じているとかでなければ、DbConnect のインスタンスがGCに改修されることと、OracleConnection のインスタンスが破棄されることに関連性はないので、何も問題のないコーディングです。
また、メソッドのスコープに変数を定義しても、その変数に格納された参照が使われていなければGCの対象になります。このため、修正前後のソースコードではDbConnectのインスタンスについて、GCからみたらたいした違いがないことになります。(実際はチェックポイントの数が違ったりするとは思いますが)
-
> ご指摘の通り、DbConnect()のデストラクタにてDispose()を実装しております。
そのことをキチンと書かないと、誰もまともな回答はできないです。
そういった判断は質問者には難しいことなのでよくある話なのですが、今回の場合はまさに接続をクローズしている処理が書かれていたわけですので、第三者からみればそのような情報が書かれていないなんてことは想像が難しい部分です。> その事もあり、今回のソース記述では問題がありました。
前の投稿を確認して頂けていますか?
前の投稿に書いているように、変数宣言の位置を変更しても何も改善されませんよ。発生頻度が少なくなって運よく数百回程度のテストでエラーが出なくなっただけではないかと思います。運悪く発生する1回が本番の致命的なタイミングになって困るのは自分自身じゃないでしょうか。
また、2つ前の投稿にも書いていますが、.NET 2.0 の OracleConnection には接続に関する既知の不具合がありますので、.NET 3.5 SP1 や ODP.NET の利用も考慮してく
ださいね。以下は蛇足です。
デストラクタでマネジドリソースに対してアクセスするのは危険ですので、やめましょう。
DbConnect クラスがデストラクタで処理される時、そのメンバである OracleConnection についても別のルートから参照がない限りは同時に破棄されます。外側のクラスである DbConnect に可能なことは、OracleConnection を保持するメンバ変数に対して null を代入することだけです。(代入することに意味もありませんが)よく記載される典型的な実装は、
~DbConnect() { this.Dispose(false); } public void Dispose() { this.Dispose(true); } protected virtual void Dispose(boolean disposing) { if (disposing) { // お好みに応じて ObjectDisposedException を throw // if (this.conn == null) // throw new ObjectDisposeException(); if (this.conn != null) this.conn.Dispose(); } this.conn = null; }
こんなかんじになります
オマケ。
(少し情報が古いのですが)Oracle の OCI 等の低レベルドライバは初期化
オプションによってスレッドに依存したデータ保持を行うかどうかが指定できました。
このことは、アプリケーションとは別スレッドで動作する GC から、OracleConnection
や OracleCommand の Dispose を呼び出すと、Oracle のデータベースドライバレベルで
不整合が発生し、何かトンデモナイ異常事態に出くわす可能性があるということです。※ Oracle 7 の頃は、並列化が入ったばかりでスレッドまたぐと即死していました。
.NET ではコネクションプール等の実装があるので、まあ大丈夫とは思います。