トップ回答者
TableAdapterのFillメソッド、Updateメソッドで使用するTimeoutについて

質問
-
お世話になってます。
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]として機能すると思っていましたが、違うのでしょうか?
また、違うとすれば、どこで設定できるのでしょうか?宜しくお願いします。
回答
-
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 全体ではなく行単位に適用されます。
こちらはこの標準のタイムアウト指定のままで困ることはないように思います。
すべての返信
-
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 全体ではなく行単位に適用されます。
こちらはこの標準のタイムアウト指定のままで困ることはないように思います。 -
>K.Takaokaさん
返信ありがとうございます。1000万件以上あるテーブルに[Fill]をする、という10秒以上かかる処理でテストしてみましたが
[TransactionScope]の[Timeout]は無視されてしまいました。(追記)
[無視]という表現は、正しくありませんでした。
たとえば[TransactionScope]の[Timeout]を1秒に設定して、10秒かかる[Fill]を実行すると
10秒後に[Fill]が正常終了し、次の[tra.Complete();]でエラーが発生します。
なので
[処理を開始して、1秒経過時に終わっていなければエラー発生]という私が求めている仕様ならばダメですが
[処理が終了時に、1秒以上経過しているならばエラー発生]という仕様ならば問題ない動作になっています。
- 編集済み fanduon 2011年11月25日 4:34