none
TableAdapterのFillメソッド、Updateメソッドで使用するTimeoutについて RRS feed

  • 質問

  • お世話になってます。

    SqlServer2005のDBにあるテーブルに対して
    ・[TableAdapter]の[Fill]メソッドで、[DataTable]に落とし込む。
    ・[TableAdapter]の[Update]メソッドで、DBに追加or更新or削除

    上記の処理を[TableAdapter]の挙動を調べるために実行しているのですが
    どうも設定したはずの[Timeout]が(私の意図したように)機能していないのです。

    設定の仕方は、[partial]を使用して[TableAdapter]の各[SqlCommand]の[CommandTimeout]を1に設定しています。

    テストの仕方は、10万件以上あるテーブルに対して[Fill]、1万件以上のデータを[Update]を実行していますが、設定したはずの[Timeout]の1秒は無視され、10秒以上経過してから正常終了します。
    (1000万以上データがあるテーブルに[Fill]したとき、90秒ほど経過してタイムアウトエラーが発生しました。90秒なんて設定していません)

    [SqlCommand]単独で[ExecuteNonQuery]を実行すると、正常にタイムアウトエラーが発生してくれましたが、この[SqlCommand]を設定した[SqlDataAdapter]の[Fill]では無視されました。

    てっきり[TableAdapter]が使用している[SqlCommand]の[CommandTimeout]に秒数を指定すれば、[Fill]や[Update]での[Timeout]として機能すると思っていましたが、違うのでしょうか?
    また、違うとすれば、どこで設定できるのでしょうか?

    宜しくお願いします。

    2011年11月22日 5:16

回答

  • TableAdapter の Fill メソッドの内部では、ExecuteReader を実行して得た DataReader を使い、ループしてデータを読み込むようになっています。
    ExecuteNonQuery の結果は fanduon さんと同様でしたが、試しに自分で ExecuteReader して SqlDataReader の Read() をループをしてみたところ、Fill での結果と同様になりました。

    SqlCommand.CommandTimeout プロパティ
    http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqlcommand.commandtimeout(v=VS.100).aspx

    上記サイトには「ネットワーク読み取りに対する累積タイムアウト」と書いてありますが、想像として、ExecuteReader の時間や1回あたりの SqlDataReader.Read の時間がとても短いときは、正常に累積としてはカウントされていないのかもと思いました。
    というより、そもそも CommandTimeout の影響は、1回の Read の実行ごとに独立している可能性もあるのかなと思いました。

    とても長い場合の検証になってしまいますが、デッドロックさせた場合の挙動を確認してみたところ、ExecuteReader する前にデッドロックになるようにしておくと ExecuteReader でタイムアウトが発生しますし、Read のループ中にデッドロックさせると、何度目かの Read の際にタイムアウトになりました。
    その時のタイムアウトまでの時間はほぼ指定通りのようでした。

    fanduon さんは Fill 時に 90秒でタイムアウトすることがあったと書かれていますが、想像として、たまたまその時は 90秒経過した際にようやく累積タイムアウトとしてのカウント値が1秒になったのではと思いました(追記:もしくは 90秒目の Read がたまたま1秒以上かかったとか)。
    (それと 90秒と聞いて ASP.NET の executionTimeout も頭をよぎりました)

    この想像の裏付けをと思いインターネットを検索したのですが、見つけることはできませんでした。

    自作の仕組みになりますが、TableAdapter.Fill でもタイムアウトさせるようなコードを考えてみました。
    いかがでしょうか。

     

    private Stopwatch _timeoutTimer;
    
    private void button1_Click(object sender, EventArgs e)
    {
        // (DataTable.RowChanged ではなく、自動生成された MyTable の RowChanged イベント)
        myDatabaseDataSet.MyTable.MyTableRowChanged += MyTable_RowChangedForTimeout;
        try
        {
            _timeoutTimer = Stopwatch.StartNew();
            this.MyTableTableAdapter.Fill(ds.MyTable);
        }
        finally
        {
            ds.MyTable.MyTableRowChanged -= MyTable_RowChanged;
            _timeoutTimer = null;
        }
    }
    
    private void MyTable_RowChangedForTimeout(object sender, MyDatabaseDataSet.MyTableRowChangeEvent e)
    {
        // 1秒したらタイムアウト
        if (_timeoutTimer != null && _timeoutTimer.ElapsedMilliseconds > 1000)
            throw new Exception("timeout");
    }
    
    

     

    次に TableAdapter の Update ですが、このメソッドでは変更行分がループ処理され、行ごとに ExecuteNonQuery が実行されます。そのため、タイムアウトの指定値は Update 全体ではなく行単位に適用されます。
    こちらはこの標準のタイムアウト指定のままで困ることはないように思います。

    • 編集済み TH01 2011年11月24日 4:43 文中への追記など
    • 回答としてマーク fanduon 2011年11月25日 4:31
    2011年11月24日 4:24

すべての返信

  • TableAdapter の Fill メソッドの内部では、ExecuteReader を実行して得た DataReader を使い、ループしてデータを読み込むようになっています。
    ExecuteNonQuery の結果は fanduon さんと同様でしたが、試しに自分で ExecuteReader して SqlDataReader の Read() をループをしてみたところ、Fill での結果と同様になりました。

    SqlCommand.CommandTimeout プロパティ
    http://msdn.microsoft.com/ja-jp/library/system.data.sqlclient.sqlcommand.commandtimeout(v=VS.100).aspx

    上記サイトには「ネットワーク読み取りに対する累積タイムアウト」と書いてありますが、想像として、ExecuteReader の時間や1回あたりの SqlDataReader.Read の時間がとても短いときは、正常に累積としてはカウントされていないのかもと思いました。
    というより、そもそも CommandTimeout の影響は、1回の Read の実行ごとに独立している可能性もあるのかなと思いました。

    とても長い場合の検証になってしまいますが、デッドロックさせた場合の挙動を確認してみたところ、ExecuteReader する前にデッドロックになるようにしておくと ExecuteReader でタイムアウトが発生しますし、Read のループ中にデッドロックさせると、何度目かの Read の際にタイムアウトになりました。
    その時のタイムアウトまでの時間はほぼ指定通りのようでした。

    fanduon さんは Fill 時に 90秒でタイムアウトすることがあったと書かれていますが、想像として、たまたまその時は 90秒経過した際にようやく累積タイムアウトとしてのカウント値が1秒になったのではと思いました(追記:もしくは 90秒目の Read がたまたま1秒以上かかったとか)。
    (それと 90秒と聞いて ASP.NET の executionTimeout も頭をよぎりました)

    この想像の裏付けをと思いインターネットを検索したのですが、見つけることはできませんでした。

    自作の仕組みになりますが、TableAdapter.Fill でもタイムアウトさせるようなコードを考えてみました。
    いかがでしょうか。

     

    private Stopwatch _timeoutTimer;
    
    private void button1_Click(object sender, EventArgs e)
    {
        // (DataTable.RowChanged ではなく、自動生成された MyTable の RowChanged イベント)
        myDatabaseDataSet.MyTable.MyTableRowChanged += MyTable_RowChangedForTimeout;
        try
        {
            _timeoutTimer = Stopwatch.StartNew();
            this.MyTableTableAdapter.Fill(ds.MyTable);
        }
        finally
        {
            ds.MyTable.MyTableRowChanged -= MyTable_RowChanged;
            _timeoutTimer = null;
        }
    }
    
    private void MyTable_RowChangedForTimeout(object sender, MyDatabaseDataSet.MyTableRowChangeEvent e)
    {
        // 1秒したらタイムアウト
        if (_timeoutTimer != null && _timeoutTimer.ElapsedMilliseconds > 1000)
            throw new Exception("timeout");
    }
    
    

     

    次に TableAdapter の Update ですが、このメソッドでは変更行分がループ処理され、行ごとに ExecuteNonQuery が実行されます。そのため、タイムアウトの指定値は Update 全体ではなく行単位に適用されます。
    こちらはこの標準のタイムアウト指定のままで困ることはないように思います。

    • 編集済み TH01 2011年11月24日 4:43 文中への追記など
    • 回答としてマーク fanduon 2011年11月25日 4:31
    2011年11月24日 4:24
  • Fill, Update を

    using (var tra = new TransactionScope(
        TransactionScopeOption.Required, 
        TimeSpan.FromSeconds(1)))
    {
        // Fill, Update
        tra.Complete();
    }
    


    と、TransactionScope で囲むという手で対応できませんかね?

    ※ TransactionScope はタイムアウトまでの時間を減らすことはできますが、増やすことはできません。

     

     

    2011年11月25日 3:33
  • >TH01さん
    返信ありがとうございます。
    詳細なテストまでしていただいて、ありがとうございます。

    そのストップウォッチを使った方法だとうまく行きそうですね。
    [Update]全体に対しても[Timeout]を設定したいので、こちらもストップウォッチを使わせてもらおうと思います。

    どうも、ありがとうございました。

    2011年11月25日 4:00
  • >K.Takaokaさん
    返信ありがとうございます。

    1000万件以上あるテーブルに[Fill]をする、という10秒以上かかる処理でテストしてみましたが
    [TransactionScope]の[Timeout]は無視されてしまいました。

    (追記)
    [無視]という表現は、正しくありませんでした。
    たとえば[TransactionScope]の[Timeout]を1秒に設定して、10秒かかる[Fill]を実行すると
    10秒後に[Fill]が正常終了し、次の[tra.Complete();]でエラーが発生します。
    なので
    [処理を開始して、1秒経過時に終わっていなければエラー発生]という私が求めている仕様ならばダメですが
    [処理が終了時に、1秒以上経過しているならばエラー発生]という仕様ならば問題ない動作になっています。



    • 編集済み fanduon 2011年11月25日 4:34
    2011年11月25日 4:08