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

    質問

  • みなさま、こんにちは。
    表題の件につきまして、過去ログやネット検索などしましたが、
    中々、同様の現象や解決方法が見つからずに困っております。
    どなたかご意見いただければ幸いです。

    クライアント環境: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)

    2012年12月5日 9:55

すべての返信

  • コードを見て疑問に思ったのですが、using を使ってるのになぜわざわざ Dispose や Close を実行してるのでしょうか?

    #Oracle は判りません


    ひらぽん http://d.hatena.ne.jp/hilapon/

    2012年12月5日 10:16
    モデレータ
  • ひらぽんさん、回答ありがとうございます。

    作成当初は、using を使っておりますのでDispose や Closeは明示しておりませんでした。

    (抜ければ破棄されますので)

    今回の現象が見つかり、動作を明確にしたかった為、あえて記載している次第です。


    • 編集済み okahara 2012年12月5日 10:36
    2012年12月5日 10:35
  • タイムアウトしてるわけではないんですよね。
    トランザクション内でやるとどうなるのでしょう。

    using が Dispose を保証しているので余計に書くと逆に混乱しますよ。

    #isExist もちょっと。。。 http://www.aerith.net/design/bool-j.html


    • 編集済み galaco 2012年12月5日 14:19
    2012年12月5日 14:17
  • galacoさん回答ありがとうございます。
    命名に関しては、参考にさせていただきます。
    また、Dispose等は確かに混乱を招くので、削除しました。

    試しに呼び出し側でコネクションやトランザクションを作成し、
    データ取得、データ更新を行ってみましたが、やはり繰り返して行っていると同様の現象が発生しました。

    2012年12月6日 0:52
  • 根本的な解決でないですが、cmd.Execute~ の直前でコネクションの状態を調べて閉じていたら開くようにしてみるとか。

    Oracle に直接聞いた方がいいかもしれません。

    2012年12月6日 2:35
  • Oracleの場合まずはエラーコードを確認するのが第一歩です。

    DB側のアラートログ、トレースログ、リスナーログ、クライアント側のsqlnet.logに何か出ていないか確認してみてください。

    > ひらぽんさん

    UsingでDisposeが保証されいていてもCloseに関しては正常処理なので、あえて記述するってのはよくありますよ。

    2012年12月6日 3:12
  • galacoさん、かるあさん回答ありがとうございます。

    Oracleのサーバ側、クライアント側のログを調査してみます。

    また進展があり次第、掲載致します。

    2012年12月6日 6:55
  • 環境が Visual Studio 2005 とのことなので、.NET 2.0 の既知の不具合ですかね。

    .NET 3.5 SP1 のランタイム上で動かせば問題ないと思いますが・・・、今後のことを考えるなら ODP.NET への移行を考えたほうがよいかと思います。(.NET 2.0, 3.0, 3.5 SPなしは、サポートが終了しています)


    # KB に hotfix もあったんじゃないかと思います
    2012年12月6日 12:28
  • 回答を下さった皆様へ
    解決の糸口が見つかりましたので内容を記載します。
    また、考えてくださった皆様にお詫びがあります。
    記載したソースについて、皆様にわかりやすく伝えようと思い
    削った部分が多々ありました。そこに初歩的な問題がありました。
    その件について、心より詫び致します。


    以下、本来の内容です。
    --------------------------------------------------------------
    【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するようなものであれば問題なかったと思います。
    私の分析、認識不足でこのような現象が発生したと痛感しております。

    ご意見下さった皆様、ありがとうございました。
    また、申し訳ありませんでした。
    2012年12月7日 8:44
  • クラス設計にどうこう言うつもりはありませんが、connのusingを抜けた後にdbCon.GetConnection()すると落ちますよ?
    2012年12月7日 11:37
  • 書いてないですが、~DbConnect() に this.conn.Dispose() でも書いてあるんでしょうか?デストラクタで接続が閉じているとかでなければ、DbConnect のインスタンスがGCに改修されることと、OracleConnection のインスタンスが破棄されることに関連性はないので、何も問題のないコーディングです。

    また、メソッドのスコープに変数を定義しても、その変数に格納された参照が使われていなければGCの対象になります。このため、修正前後のソースコードではDbConnectのインスタンスについて、GCからみたらたいした違いがないことになります。(実際はチェックポイントの数が違ったりするとは思いますが)

    2012年12月10日 16:23
  • かるあさん、回答ありがとうございます。

    落ちる事は、理解しておりますので大丈夫です。

    今回のものについては、Usingの処理後にdbCon.GetConnection()を

    再度取得する事はないのでOKです。

    2012年12月11日 10:16
  • K.Takaokaさん 回答ありがとうございます。

    ご指摘の通り、DbConnect()のデストラクタにてDispose()を実装しております。

    その事もあり、今回のソース記述では問題がありました。

    2012年12月11日 10:21
  • > ご指摘の通り、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 ではコネクションプール等の実装があるので、まあ大丈夫とは思います。

    2012年12月19日 12:16