- PR -

ADO.NETで更新処理が遅い

投稿者投稿内容
rucio
ベテラン
会議室デビュー日: 2002/11/27
投稿数: 98
投稿日時: 2004-05-18 00:39
みなさんアドバイスありがとうございます。


SqlCommandでINSERT文を発行する方法でプログラムの改造を進めています。
簡単にテストしたところ少し速くなるようです。
もう少しまとまった結果が出たらこのスレッドでお知らせしたいと
思っています。

>「リンクサーバー」というらしいです。
わざわざ調べてくださって感謝します。
ただ、この機能は今回のドライバが対応していない可能性があるので
現在保留中です。

>「適当な列を同じ値で書き換え、RowStateを強制的にModifiedにし、UpdateCommand.CommandTextにINSERT文を書く」
私にはこれがどのような方法なのかわかりません。
「適当な列を同じ値で書き換え」の部分が分かりません。
結局のところ、
ODBCDataReader(ODBC側)からDataSet(SQL Server側)に
レコードをセットする処理は必要になるのですよね?
ODBC側をDataSetで拾ってきて、それのRowStateをModifiedにしても
それによって更新されるのはODBC側のデータベースと思えます。

>数万件程度でこれが遅くなるとは信じがたいです。
タスクマネージャを見ながら処理を実行すると
12000件くらいごとに使用メモリ(PF使用量)が20MBくらい
ずつ増えていくようです。
多分、確保したバッファが足りなくなって新たなバッファを
確保しに行っていると思うのですが、その動作が遅さの原因では
ないかとにらんでいます。
なお、このテーブルの項目数は40程度です。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2004-05-18 09:18
引用:

rucioさんの書き込み (2004-05-18 00:39) より:

>「適当な列を同じ値で書き換え、RowStateを強制的にModifiedにし、UpdateCommand.CommandTextにINSERT文を書く」
私にはこれがどのような方法なのかわかりません。
「適当な列を同じ値で書き換え」の部分が分かりません。
結局のところ、
ODBCDataReader(ODBC側)からDataSet(SQL Server側)に
レコードをセットする処理は必要になるのですよね?
ODBC側をDataSetで拾ってきて、それのRowStateをModifiedにしても
それによって更新されるのはODBC側のデータベースと思えます。


 未検証なので、実際にどうなるかわかりませんが、、、

Dim ds = New DataSet(〜)
Dim SelectCommand = New OdbcCommand("SELECT 〜", ODBCへの接続)
Dim InsertCommand = New SqlCommand("INSERT INTO 〜", Sql Serverへの接続)
Dim OdbcAdapter = New OdbcDataAdapter(〜)
Dim SqlAdapter = New SqlDataAdapter(〜)

' データを取り込む
OdbcAdapter.SelectCommand = SelectCommand
OdbcAdapter.Fill(ds)

' 強制modified
Dim row As DataRow
Dim anzensaku as Object
For Each row In ds.Tables(0).Rows
 row(0) = row(0) ' これが「同じ値で書き換える」
 'anzensaku = row(0)
 'row(0) = 何か
 'row(0).AcceptChanges()
 'row(0) = anzensaku ' これでModifiedになるでしょう ^^;
Next

SqlAdapter.UpdateCommand = InsertCommand
SqlAdapter.Update(ds.Tables(0))


こんな感じ。
取ってきたDataSetは、物理的にデータベースとは切れています。それを利用します。書き出す為のUpdateで使う接続は、SQL Server宛の接続です。そして、RowStateがModifiedならばUpdateCommandが適用されますが、SQL Server内には当然その行はありませんから、UpdateCommandにINSERT文を書きます。

 強制Modifiedは、イコールの左辺と右辺を同じ値にします。もしかしたらオプティマイザが命令を削除してしまうかもしれないので、anzensakuのように、いったん別の値を入れて、元の値で上書きします。Originとの差異でRowStateを決めていたら、別の値で上書きした直後にAcceptChangesメソッドをコールして、変更を確定します。
未記入
ベテラン
会議室デビュー日: 2003/06/26
投稿数: 76
投稿日時: 2004-05-19 09:33


[ メッセージ編集済み 編集者: 未記入 編集日時 2007-01-19 21:58 ]
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2004-05-19 10:48
 よく考えたら、別のデータベースへのアクセスなんだから、接続2つ作るほうがよい?読み込んだデータをDataSetにストアするのではなく、そのままSqlCommandのパラメータとしてSQL Serverに渡してしまう、と。メモリの拡張確保をしないし、ループ回るのも1回だけなので、かなり改善されると思います。
rucio
ベテラン
会議室デビュー日: 2002/11/27
投稿数: 98
投稿日時: 2004-05-20 17:12
Jittaさま、ぜうすさま返信ありがとうございます。

現在、ループの中で
SQL Insert文を直接発行する方法で開発を終了しました。

今回のケースでは
SQL Insert文の使用はSqlDataAdapter.Updateを使用するより
約25%の時間短縮になりました。

ここでいったんまとめておきます。
このスレッドでは話題にしなかったことも関連がありそうなことで
今回わかったことはまとめに含みます。

■まとめ

●SqlDataAdapter.Updateメソッドは遅い
SqlCommandでINSERT文を直接サーバーに投げる方が早い。

●DataSetを使うとメモリを大量消費する
テーブル設計にもよるが対象が数十万件以上の場合にはまず実用性がない。

●レコードの検索はDataTable.Rows.Findメソッドがかなり速い。
sqlCommandでSELECT文を発行してレコードを検索するのは遅い。
(レコードが既に登録されているか否かのチェック方法の話です)

●レコードを更新する必要がない場合はDataReaderを使う

異論・補足等あるかたはご意見伺わせてください。


さて、とりあえず我慢できる速さで動くようになりましたので
ここからは今後の参考のためと興味のために伺うのですが

>SqlAdapter.Update(ds.Tables(0))
この方法は理解しました。
ためしてはいませんが、大変興味深いと思っています。
しかし、今度は
>よく考えたら、別のデータベースへのアクセスなんだから、接続2つ作るほうがよい?
これが理解できません…。
もともと接続は2つありますよね?
DataSetにストアしないで渡すことなどできるのでしょうか?
これはさらによさそうな方法のようですが、
もう少し具体的に教えていただけませんか?

>テーブルAからCSVを作成し、SQLServerのインポートを利用してテーブルBへ登録する方法は...?
これはハードディスクへのアクセスが約2倍になりますよね。
CSVに落とした後は速いのかも知れませんが、
今回のケースではCSVに落とすところがかえって遅くなりそうなので
この方法は見送りました。
提案ありがとうございます。

速度向上のためにアドバイスしてくださったみなさんに改めてお礼を言います。
ありがとうございます!
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2004-05-20 17:45
引用:

rucioさんの書き込み (2004-05-20 17:12) より:

>よく考えたら、別のデータベースへのアクセスなんだから、接続2つ作るほうがよい?
これが理解できません…。


意図は、たぶん、これと同じ。
引用:

現在、ループの中で
SQL Insert文を直接発行する方法で開発を終了しました。



擬似コード:
コード:
ODBC接続 ← K ASPへの接続を作る
SQL接続 ← SQL Serverへの接続を作る
ODBCリーダ ← ODBC接続からの読み込み
ODBC接続.Open()
SQL接続.Open()
while (ODBCリーダ.Read() = True) {
    SQLコマンド ← SQL ServerへのINSERT文をODBCリーダからデータを取り出して作る
    SQLコマンド.ExecuteNoQuery()
}
ODBC接続.Close()
SQL接続.Close()

スキルアップ/キャリアアップ(JOB@IT)