質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

Q&A

解決済

2回答

2226閲覧

C#でTaskを使ってINSERTやUPDATEを繰り返していくと段々遅くなる

asral

総合スコア10

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

0グッド

0クリップ

投稿2022/06/24 03:21

編集2022/06/27 02:09

前提

Visual Studio 2019 
フレームワーク: .NET core 3.1
コンソールアプリケーション

C#でTaskを使って複数のINSERTやUPDATEを繰り返すと、徐々に1件登録・更新を続けていくと少しずつ遅くなってしまいます。最初の数十件は1件目から2件目登録するのにそれほど時間はかかりませんが、数百件目となると1件処理するのに数秒かかるようになりました。
登録や更新しているDBはLocalに構築しています。

実現したいこと

可能であれば、この遅くなる現象が少しでも解消できればと思います。

発生している問題

1件目:00:00:00.86 2件目:00:00:00.73 ~ 100件目:00:00:01.54 101件目:00:00:01.44 ~ 200件目:00:00:03.16 201件目:00:00:03.28

該当のソースコード

C#

1 private static async Task<bool> ExitPrintImageMain() 2 { 3 try 4 { 5 int listcnt = 0; 6 7 foreach (var v in TestList) 8 { 9 await UpdTest1TransactionAsync(listcnt); 10 11 await InsTest1TransactionAsync(listcnt); 12 13 await InsTest2TransactionAsync(listcnt); 14 15 await InsTest3TransactionAsync(listcnt); 16 17 await UpdTest2TransactionAsync(listcnt); 18 19 await UpdTest3TransactionAsync(listcnt); 20 21 await UpdTest4TransactionAsync(listcnt); 22 23 await InsTest4TransactionAsync(listcnt); 24 25 await UpdTest4TransactionAsync(listcnt); 26 27 await InsTest5TransactionAsync(listcnt); 28 29 await InsTest6TransactionAsync(listcnt); 30 31        listcnt = listcnt + 1; 32 } 33 return true; 34 } 35 catch (Exception e) 36 { 37 Console.WriteLine(System.DateTime.Now + " : " + e.Message); 38 return false; 39 } 40 }

パラメータはもっと沢山ありますが、一部のみ記述しています。

C#

1 private static async Task InsTest1TransactionAsync(int listcnt) 2 { 3 try 4 { 5 DateTime dt = DateTime.Now; 6 7 parameter1.iTest_NO = listcnt; 8 parameter1.sTest_TIME = dt.ToString("yyyy/MM/dd HH:mm:ss") 9 10 await Tracsaction.Ins1Main(); 11 } 12 catch (Exception e) 13 { 14 Console.WriteLine(System.DateTime.Now + " : " + e.Message); 15 Log.PrintOut(System.DateTime.Now + " : " + e.Message, true); 16 } 17 } 18

C#

1 private static bool Ins1Main() 2 { 3 MySqlCommand cmd = new MySqlCommand(SQL.ins1, cn); 4 try 5 { 6 cmd.Parameters.Add(new MySqlParameter("i0", Program.parameter1.iTest_NO)); 7 cmd.Parameters.Add(new MySqlParameter("s1", Program.parameter1.sTest_TIME)); 8 9 cmd.Transaction = cn.BeginTransaction(System.Data.IsolationLevel.ReadCommitted); 10 cmd.ExecuteNonQuery(); 11 cmd.Transaction.Commit(); 12 cmd.Connection.Close(); 13 cmd.Dispose(); 14 return true; 15 } 16 catch (Exception e) 17 { 18 Console.WriteLine(System.DateTime.Now + " : " + e.Message + Environment.NewLine + "Execution RollBack Start!!"); 19 cmd.Transaction.Rollback(); 20 return false; 21 } 22 }

C#

1public const string ins1 = "INSERT tbl_test (Test_NO,Test_Time) VALUES(@i0,@s1)"

試したこと

Taskを使用せず同期処理に戻したりもしましたが、結果は変わりませんでした。

何らかのバッファが溜まってるのか、コネクションプール等も関係しているのかと思いましたが、改善点が分からなかったのでここで質問させていただきました。

よろしくお願いいたします。

補足情報

windows10
mysql5.6.20

上記のソースから指摘されたところを変更したソース

ひとまずcatchは省いています。以下はINSERTのみですが、UPDATEも同じように直しました。

C#

1private static async Task<bool> ExitPrintImageMain() 2 { 3 try 4 { 5 int listcnt = 0; 6 7 foreach (var v in TestList) 8 { 9 await UpdTest1TransactionAsync(listcnt); 10 11 await InsTest1TransactionAsync(listcnt); 12        listcnt = listcnt + 1; 13 } 14 return true; 15 } 16 }

parameter2~5は省いています。実際はちゃんと記述してます。

C#

1private static async Task InsTest1TransactionAsync(int listcnt) 2 { 3 try 4 { 5 DateTime dt = DateTime.Now; 6 7 parameter1.iTest_NO = listcnt; 8 parameter1.sTest_TIME = dt.ToString("yyyy/MM/dd HH:mm:ss") 9101112 parameter6.iTest_NO = listcnt; 13 parameter6.sTest_TIME = dt.ToString("yyyy/MM/dd HH:mm:ss") 14 15 await Tracsaction.Ins1Main(); 16 } 17   }

C#

1 private static bool Ins1Main() 2 { 3 MySqlCommand cmd = new MySqlCommand(SQL.ins1, cn); 4 try 5 { 6 cmd.Parameters1.Add(new MySqlParameter("i0", Program.parameter1.iTest_NO)); 7 cmd.Parameters1.Add(new MySqlParameter("s1", Program.parameter1.sTest_TIME)); 8 cmd.ExecuteNonQuery(); 9 cmd.Parameters.Clear(); 10111213 cmd = new MySqlCommand(SQL.ins6, cn); 14 cmd.Parameters6.Add(new MySqlParameter("i0", Program.parameter6.iTest_NO)); 15 cmd.Parameters6.Add(new MySqlParameter("s1", Program.parameter6.sTest_TIME)); 16 cmd.ExecuteNonQuery(); 17 cmd.Parameters.Clear(); 18 19 cmd.Transaction = cn.BeginTransaction(System.Data.IsolationLevel.ReadCommitted); 20 cmd.Transaction.Commit(); 21 cmd.Connection.Close(); 22 cmd.Dispose(); 23 return true; 24 } 25 }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2022/06/24 03:35

InsTest1TransactionAsync に await が無いので同期処理になっています。 Task は無関係のように思いますから、それを条件から外して考えてみてください。 インデックスを作っているか確かめたり、接続と切断をまとめてみたりすれば解決策がつかめるかもしれません。
退会済みユーザー

退会済みユーザー

2022/06/24 03:41

質問する際は何を何で作っているかを質問の一行目に書きましょう。(例: C# のコンソールアプリを Visual Studio 2022 でフレームワークを .NET Framework 4.8 として作っています・・・とか) 数百件も INSERT するなら、そもそものやり方が適してないのでは? バルクインサートなど、DB とのラウンドトリップ回数を減らすことを考える余地はないのですか? 非同期になっているようには見えないのですが、ちょっとそこは置いといて、非同期に何を期待してますか? コードを見る限り非同期だから早くなるということはなさそうに見えますが。
asral

2022/06/24 03:51

ありがとうございます。改修しておきます。 インデックスは各テーブルにはいくつか張っていますが、再度確認してみます。 やはり接続か切断ですかね・・・。毎回OPENしてcloseとdisposeすると確かに遅くなるみたいなのは見ましたが・・・。
KOZ6.0

2022/06/24 04:43

cmd.Connection.Close(); で、毎回切断しているように見えますが、意図的なものでしょうか?
asral

2022/06/24 06:01

>KOZ6.0様 毎回接続と切断しているのは意図的です。
KOZ6.0

2022/06/24 06:23 編集

SQL を実行するたびに Open/Close しているわけですね? コネクションプールで接続は残っていますが、そのたびにサーバーに対して生きているかどうか問い合わせを行うはずです。サーバー側の負荷が大変なことになりそうですが・・・ 1回の接続で 1000 回や 2000回の SQL を実行したところでそれほど遅くなるとは思えません。 Open/Close のタイミングを見直してみてはいかがでしょうか。
退会済みユーザー

退会済みユーザー

2022/06/26 23:12

質問者さん、その後無言ですが、回答を試して望む改善は得られたでしょうか。フィードバックをお願いします。回答が役に立たなかったならどこがダメかを書くと別の提案が出てくるかも。
guest

回答2

0

ベストアンサー

何らかのバッファが溜まってるのか、コネクションプール等も関係しているのかと思いましたが、改善点が分からなかったのでここで質問させていただきました。

数百回 INSERT を繰り返すとのことですが、質問のコードでは「接続プールから接続を取得して SQL 文を MySQL に発行、それを受けた MySQL は SQL 文をコンパイルして処理を行い結果を返す」というラウンドトリップ操作を数百回行っているはずです。

なので、バルクインサートなどで DB とのラウンドトリップ回数を減らすとか、一回の接続で複数の INSERT 処理を行うことを考えてはいかがですか。

それから、非同期の件ですが、非同期にすると早くなると期待しているとすればそれはないです(質問のコードは非同期になっているようには見えないのですが、ちょっとそこは置いといて)。

質問のコードに手を加えて非同期で実行できるようにしたとしても、以下の記事の画像の「② シングル CPU によるマルチスレッド(平行)」のようになっているはずなので、スレッドの切り替えのオバーヘッドの分遅くなることはあっても、早くなることはないです。

タスク並列ライブラリ (TPL)
http://surferonwww.info/BlogEngine/post/2020/12/27/task-parallel-library.aspx

上の記事に書いたような TPL をなどを使ってマルチ CPU/コアを使って「③ マルチ CPU によるマルチスレッド(並列)」のような形にできれば多少の効果はあるかもしれませんが、ボトルネックは多分接続プールか毎回の SQL 文のコンパイルでしょうから効果はなさそうな気がします。

【追記】

下のコメント欄の 2022/06/24 15:49 の私のコメントで、

SQL 文をパラメータ化し(質問のコードでも既にそうなっていると理解)、接続したらパラメータを差し替えながら複数の SQL 文を実行していくという感じです。後で回答欄に例を追記しておきます。

と書いた件です。SQL Server で SQL 文は UPDATE ですが、一回の接続で 3 回 UPDATE を実行しています。複数の SQL 文を実行するのでトランザクションに束ねていますが、それはこの追記の本題の「一回の接続で複数の SQL を実行」とは関係ないです。

string query = "UPDATE [TestDatabase].[dbo].[TestResult] " + "SET [StudentID]=@StudentID " + "WHERE [TestResultID]=@TestResultID"; using (var connection = new SqlConnection(connString)) { using (var command = new SqlCommand(query, connection)) { command.Parameters.Add( new SqlParameter("@StudentID", SqlDbType.Int)); command.Parameters.Add( new SqlParameter("@TestResultID", SqlDbType.Int)); connection.Open(); var sqltx = connection.BeginTransaction(); command.Transaction = sqltx; command.Parameters["@StudentID"].Value = 5; command.Parameters["@TestResultID"].Value = 1; command.ExecuteNonQuery(); command.Parameters["@StudentID"].Value = 6; command.Parameters["@TestResultID"].Value = 2; command.ExecuteNonQuery(); command.Parameters["@StudentID"].Value = 4; command.Parameters["@TestResultID"].Value = 3; command.ExecuteNonQuery(); sqltx.Commit(); } }

投稿2022/06/24 05:07

編集2022/06/24 06:58
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2022/06/24 05:25

このスレッドの本題とは関係ない話ですが、質問のコードではトランザクションは意味がなさそうです。もう一つ、どうしても必要と言うことでなければ try - catch は書くべくではないですし、さらにマズいのは Exception を catch していることです。
asral

2022/06/24 05:53

回答遅れまして申し訳ございません。 前提として、非同期で速度が・・・というのはこちらも原因ではないのではと思っています。単純に自分の知らない何かしら原因が、もしかしたらTaskが絡んでるかも?ということで記述した次第です。 ラウンドトリップ回数についてですが、バルクインサートというと INSERT INTO testdb.tbl_name (col_name1, col_name2, ...) VALUES (value1, value2, ...) ,(value1, value2, ...) .(value1, value2, ...) という感じですよね。おっしゃる通り、毎回接続してMYSQLにSQL発行・実行して切断・・・と繰り返すより遥かに速くなりそうです。段々遅くなる原因はこのラウンドトリップ回数の蓄積によるものでしょうか。 一度の接続で複数のINSERTというと、複数のSQLをcmd.ExecuteNonQuery();を実行して最後にtransaction.Commit();するイメージでしょうか。 返答がうまく伝えれてるか分かりませんが、回答ありがとうございます。
退会済みユーザー

退会済みユーザー

2022/06/24 06:49

> 段々遅くなる原因はこのラウンドトリップ回数の蓄積によるものでしょうか。 実際に質問者さんのコードを動かして確かめることができないので想像の域を超えてませんが、接続プールにある接続の枯渇、毎回の SQL 文の処置、ラウンドトリップが絡んでいると思います。 > 一度の接続で複数のINSERTというと、複数のSQLをcmd.ExecuteNonQuery();を実行して最後にtransaction.Commit();するイメージでしょうか。 SQL 文をパラメータ化し(質問のコードでも既にそうなっていると理解)、接続したらパラメータを差し替えながら複数の SQL 文を実行していくという感じです。後で回答欄に例を追記しておきます。
asral

2022/06/24 07:33

SurferOnWww様 回答ありがとうございます。参考にさせていただきます。
退会済みユーザー

退会済みユーザー

2022/06/26 23:10

質問者さん、その後無言ですが、回答を試して望む改善は得られたでしょうか。フィードバックをお願いします。回答が役に立たなかったならどこがダメかを書くと別の提案が出てくるかも。
asral

2022/06/27 00:44

返答ありがとうございます。 週末にソースを変更して3000件処理するよう実行したまま放置し、今日その結果を確認してみましたが、結論から言うと、変化はありませんでした。 回答でいただいたように各SQL(INSERT・UPDATE)をそれぞれ集約してcommand.ExecuteNonQuery();をかけ、最後にcommitを入れたのでまさに回答の【追記】に書いていただいた通りに合わせて変更した次第です。 OPENとCLOSEもINSERT・UPDATEが1回ずつなので、回数もかなり減ったのですが。 改修前と同じ動きなので、もしかしたら別の要因かなとも思っています。
退会済みユーザー

退会済みユーザー

2022/06/27 01:21

質問者さんが試したコード例を質問欄に追記していただけますか?
asral

2022/06/27 02:16

ひとまず元のソースからまずはまとめたところを追加しました。 もしかしたら意図した改修になっておらず、そのため変化がなかったかもしれません。
Zuishin
asral

2022/06/27 02:35

Zuishinさん ありがとうございます。サイト確認させていただきます。
退会済みユーザー

退会済みユーザー

2022/06/27 03:13 編集

> ひとまず元のソースからまずはまとめたところを追加しました。もしかしたら意図した改修になっておらず、そのため変化がなかったかもしれません。 私が提案したようにはなってないと思うのですが。(そこが改善されない原因なのかは分かりませんが) 質問者さんのコードで使っている MySqlParameter コンストラクタ は Value も一緒に追加するオーバーロードですよね。使うのはそれではないです。
退会済みユーザー

退会済みユーザー

2022/06/27 03:08

上に書いた「毎回の SQL 文の処置」の問題が解決できない=実行プランの再利用ができない・・・ということを疑ってます。
asral

2022/06/27 03:46

また間違えるのはよくないので確認ですが、こういうことでしょうか。 string ins1 = "INSERT tbl_test (Test_NO,Test_Time) VALUES(@i0,@s1)" MySqlCommand cmd = new MySqlCommand(ins1, cn); cmd .Parameters.Add( new SqlParameter("@i0", SqlDbType.Int)); cmd .Parameters.Add( new SqlParameter("@s1", SqlDbType.Char)); cmd .Parameters["@i0"].Value = listcnt; cmd .Parameters["@s1"].Value = dt.ToString("yyyy/MM/dd HH:mm:ss")
退会済みユーザー

退会済みユーザー

2022/06/27 03:52

それでいいと思います・・・が、簡単なサンプルで試してみてください。パラメータの命名規則が SQL Server とは違うようですので。
asral

2022/06/27 03:56

毎回ありがとうございます。 まずは一気に変えずにSQL文2つくらいで試します。勤務しながらのため遅くなるかもですが、なるべく今日中に結果を返せればと思います。
退会済みユーザー

退会済みユーザー

2022/06/27 05:22

言い忘れましたが、MySQL 用の Connector/NET の場合は、SqlParameter ⇒ MySqlParameter, SqlDbType ⇒ MySqlDbType となりますので注意してください。(SqlXxxxx は SQL Server 用の SqlClient の場合です)
asral

2022/06/27 09:16

ひとまず報告だけ。明日また続きを行います。 上記のように変更(cmd.Parameters.Add(new MySqlParameter・・・)後、 cmd.ExecuteNonQuery(); を実行しようとしたところで「入力文字列は、正しい形式ではありませんでした」になりました。おそらくこれが「パラメータの命名規則が SQL Serverと違う」ことかなと。ここをMYSQLに合わせる形にする必要があるかなと思っています。
asral

2022/06/27 09:17

>>言い忘れましたが、MySQL 用の Connector/NET の場合は、SqlParameter ⇒ MySqlParameter, SqlDbType ⇒ MySqlDbType となりますので注意してください。(SqlXxxxx は SQL Server 用の SqlClient の場合です) これを見る前に投稿してしまいました。 明日ここを直してみたいと思います。
asral

2022/06/29 01:14

色々あってようやく確認できました。ご報告まで。 ひとまず一か所だけ直して1500件で行ってみたところ、若干早くなっているような気がします。段々と遅くなるという現象は見られませんでした。これで後は全てに適用してみてどうなるかを試験してみようと思います。
asral

2022/06/29 07:44

全てに適用してみましたが、やはり段々と遅くなりました。 そこで思ったのが、前回一か所のみUPDATEを直した場合は比較的スムーズに処理しており、UPDATEを全て適用したときに遅くなったため、まずは原因の切り分けとしてインデックス・条件式の追加あたりを探ってみます。その他はクエリキャッシュあたりでしょうか。
guest

0

ひとまず改善されたので報告。
UPDATE文に条件式を加えると単純に速度が上がり、段々と遅くなる現象はなくなりました。UPDATEする際の検索に時間かかっていたのでしょうが、あまり納得いく結果とは言いづらいところでもあります。なるべく条件式は追加したくないというのはあったのですが、要因の大半を占めているとなるとしょうがないところはあるのでしょうが・・・。
回答頂いたSurferOnWwwさん、Zuishinさんありがとうございました。
個人的にはもうちょっと調べたいので、提示頂いた点を検証してみてさらに改善が見られたら追記します。

1件目:00:00:01.16 2件目:00:00:01.03 ~ 100件目:00:00:01.01 101件目:00:00:00.77 ~ 200件目:00:00:01.08 201件目:00:00:01.13

投稿2022/07/01 08:01

編集2022/07/01 08:02
asral

総合スコア10

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2022/07/01 08:05

> UPDATE文に条件式を加えると のところ、具体的にどのようにしたのか教えていただけませんか?
asral

2022/07/01 08:46

例えば以下のように元々TestNo、TestDete、TestFlgしか条件式に入れてなかったのですが、 " WHERE " + " TestNo = @TestNo AND " + " TestDete = @TestDete AND " + " TestFlg = @TestFlg AND "; さらに絞り込みでTestBunrui、TestClassNo、TestGropeといったカラムを条件式に加えた形になります。以降のパラメータの設定等はご提示しただいた回答の【追記】に従った内容となります。 public const string sql1 = "UPDATE " + " tbl_test_upsdate " + " SET " + " TestTanka = @TestTanka ," + " TestKazu = @TTestKazu , " + " TestTotalKin = @TestTotalKin " + " WHERE " + " TestNo = @TestNo AND " + " TestDete = @TestDete AND " + " TestFlg = @TestFlg AND " + " TestBunrui = @TestBunrui AND " + " TestClassNo = @TestClassNo AND " + " TestGrope = @TestGrope ";
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問