- PR -

ADO.NETで更新処理が遅い

投稿者投稿内容
rucio
ベテラン
会議室デビュー日: 2002/11/27
投稿数: 98
投稿日時: 2004-05-14 10:59
こんにちは。
現在 VS.NET2003 + VB.NET + SQL Server 2000 + ODBC環境で
ADO.NET を使ってプログラムしているのですが動作が遅くて困っています。
改善策について示唆していただけないでしょうか?

具体的にはテーブルAからテーブルBに
値をINSERTしていくだけの処理です。
テーブルAはODBCで接続しています。テーブルBはSQL Server上のテーブルです。
件数は数万件です。(将来的には数十万件の環境で実行するつもりです)

テーブルA(ODBC) → テーブルB(SQL Server)

核では次のように記述しています。
Dim sqlASPSelect As OdbcCommand
Dm adpServer As New SqlDataAdapter
Dim sqlServerSelect As SqlCommand
Dim cbServer As New SqlCommandBuilder
Dim dsServer As New DataSet
Dim NewRow As DataRow

(中略)

adpServer.Fill(dsServer, ServerTableName)
Reader = sqlASPSelect.ExecuteReader

Do While Reader.Read

'空の新規行を作成(この時点ではこの行はテーブルに属さない)
NewRow = dsServer.Tables(ServerTableName).NewRow

'新規行にデータをセット
Mapper(NewRow, Reader)
'↑この関数では
'NewRow("ID") = Reader("ID")
'NewRow("CODE") = Reader("AreaCode") & Reader("StoreCode")
'…のような処理を行っています。

'新規行をテーブルに追加
dsServer.Tables(ServerTableName).Rows.Add(NewRow)

Loop
'テーブルの更新をSQL Serverに反映
'(これが呼ばれるまではすべての変更はメモリ上の変更に過ぎない)
adpServer.Update(dsServer.Tables(ServerTableName))

■考えたこと

dsServer.Tables(ServerTableName).Rows.Add(NewRow)
とやるとメモリ上に新しいレコードを追加していくのですよね。
だからバッファが足りなくなると新しいバッファを確保しに行く処理が
走ると思うのです。
これが遅くなっている一員ではないかと考えています。
先に大量のバッファを確保するように指定する方法があれば早くなると
思って、現在私はこの方針で調査をしています。

けれど
どんな方法でもよろしいので、示唆をいただければ幸いです。
または、方法は上記とまったく異なってもうまくいっているやり方をご存知でしたら
ご教授ください。

なお、C#等で書き込んでいただいてもこちらでVB.NETに翻訳できますので
言語等問いません。

[ メッセージ編集済み 編集者: rucio 編集日時 2004-05-14 11:02 ]
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2004-05-14 11:29
INSERT文のVALUESの所にSELECT文を書くことにより、データを一旦クライアントに持ってくるのではなくサーバー側で直接処理してしまうのはだめなんですか?

参考:
http://www.atmarkit.co.jp/fnetwork/rensai/sql12/sql1.html
この「INSERTとSELECTの組み合わせ」の辺り
rucio
ベテラン
会議室デビュー日: 2002/11/27
投稿数: 98
投稿日時: 2004-05-14 11:37
返信ありがとうございます。

INSERT destinationtable ... SELECT ... FROM sourcetable

はdestinationtableとsourcetableがともに
サーバー(この場合はSQL Server)から参照できないとダメですよね?

今回は
destinationtableがSQL Server上、
sourcetalbeがODBC経由の別サーバー上にあるので
この方法は取れないと思います。

なお、このODBCドライバがSQL Serverのリンクサーバー機能に対応しているかは
調査中ですが対応していないようです。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2004-05-14 11:50
 テーブルAと、テーブルBがあって、テーブルBはSQL Server2000ですね。では、テーブルAのDBMSはなんでしょう?

 次に、どこで時間がかかっていますか?書かれている内容からは、その為の調査をされていないように思います。テーブルAからデータを取得する処理、テーブルBをメモリ上に複製する処理、テーブルBを作成する処理、テーブルBをデータベースへINSERTする処理、それぞれにどれくらいの時間がかかっているのでしょう?

 最後に、ODBCドライバがリンクできなければなりませんか?SQL Serverが、直にテーブルAを見に行くことはできればいいのではないですか?Oracleでいうところの「データベースリンク」なのですが。これができればストアドプロシージャで処理できるので、もう少し早くなると思います。

 もう一つ。文字列の連結は、&演算子を使うより、StringBuilderクラスを使う方が高速です。

 もう一つ。テーブルAから持ってくるときに、テーブルBの形式にできないですか?
コメントしてあるところで判断すると(Oracle形式ですが)
SELECT ID, AREACODE || STORECODE AS CODE …
これで、行を複製したりする手間がなくなる分、時間もメモリも短縮できますよね。取り込んだ行はRowStateがAddでないから追加できないって?そんなときはどこかの行じゃなくて列を同じ値で書き換えます。するとRowStateがModifiedになるので、UpdateCommandが適用されます。UpdateCommand.CommandTextの内容を、INSERT文にしてしまえば、データベースに行が追加されます。

[ メッセージ編集済み 編集者: Jitta 編集日時 2004-05-14 13:09 ]
甕星
ぬし
会議室デビュー日: 2003/03/07
投稿数: 1185
お住まい・勤務地: 湖の見える丘の上
投稿日時: 2004-05-14 12:57
パフォーマンスチューニングの基本は、もっとも処理時間がかかっている分部にたいして高速化を施すことです。推論じゃなくて実際に命令の実行にかかる時間を計ってボトルネックを明確にしたほうが良いです。

またパフォーマンスが問題になるような時には、DataSet&SqlCommandBuilderではなく、SqlCommandでINSERT文を発行するほうが良いです。CommandBuilderが自動生成するSQL文は必ずしも最適なものではありません。DataSetに格納するために一度メモリの確保も必要になるでしょうしね。
_________________
甕星 <mikahosi@abox9.so-net.ne.jp>
http://blogs.msmvp.jp/mikahosi/
rucio
ベテラン
会議室デビュー日: 2002/11/27
投稿数: 98
投稿日時: 2004-05-14 13:18
アドバイスありがとうございます。
みなさんの意見が聞けてとても参考になっています。

>テーブルAのDBMSはなんでしょう?
K ASPというものです。
Kはオフコンです。
私も今回はじめて使います。
ODBCドライバは「RDA-SV(V4, ODBC 3.0)」というものです。

ループ内で最も時間がかかっているのは
次の部分です。

'新規行をテーブルに追加
dsServer.Tables(ServerTableName).Rows.Add(NewRow)

この1行がループ内で80%以上の時間を使っています。

>Oracleでいうところの「データベースリンク」なのですが。
Oracleはよくわからないのですが、SQL Serverで同等の機能を
ご存じないでしょうか?

>もう一つ。テーブルAから持ってくるときに、テーブルBの形式にできないですか?
目下できないのです。
ODBCドライバの資料が不足していて、ODBC側で細かいSQLが記述できない状態です。
たとえば、このODBCドライバに対して次の3つはどれもエラーになります。
SELECT AREACODE || STORECODE ...
SELECT AREACODE + STORECODE ...
SELECT AREACODE & STORECODE ...

このような状況ですのでODBC側が遅いことはあきらめています。

>またパフォーマンスが問題になるような時には、DataSet&SqlCommandBuilderではなく、>SqlCommandでINSERT文を発行するほうが良いです。
その通りのようですね。
この方法は試してみます。

DataSetも下手に非接続にしないで、
ADOのRecordsetのように常時接続型のDataSetみたいなものがあれば
良いと思うのですが…。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2004-05-14 13:59
引用:

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

>Oracleでいうところの「データベースリンク」なのですが。
Oracleはよくわからないのですが、SQL Serverで同等の機能を
ご存じないでしょうか?


 すみません、SQL Serverには明るくないのです
 MSのサポート技術情報を検索したところ、「リンクサーバー」というらしいです。Oracleへの設定の方法が、ここにありました。

引用:

ODBCドライバの資料が不足していて、ODBC側で細かいSQLが記述できない状態です。
たとえば、このODBCドライバに対して次の3つはどれもエラーになります。


 ドライバではなく、データベースシステムがサポートするSQLの資料が欲しいところですが、サポートしてなさそうですね

 先に示した、「適当な列を同じ値で書き換え、RowStateを強制的にModifiedにし、UpdateCommand.CommandTextにINSERT文を書く」という手は使えそうです。SQL Serverは文字列の連結をサポートしていますよね?!
Valhalla
ベテラン
会議室デビュー日: 2002/09/03
投稿数: 53
投稿日時: 2004-05-17 13:57
こんにちわ、ひさしぶりに書き込みます。

引用:

rucioさんの書き込み (2004-05-14 13:18) より:
ループ内で最も時間がかかっているのは
次の部分です。

'新規行をテーブルに追加
dsServer.Tables(ServerTableName).Rows.Add(NewRow)

この1行がループ内で80%以上の時間を使っています。



ご自身も書いておられるように、これはメモリ上の変更に過ぎない
ので、数万件程度でこれが遅くなるとは信じがたいです。
(列の数が激しく多いとそうなるかもしれませんが

Reader.Readによるループではなく、単純なforループとダミーデータ
でも遅いですか?

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