異なるRDBMSからの2つの巨大な結果セット(ソースとターゲット)をロードしようとしていますが、私が苦労している問題は、これらの2つの巨大な結果セットをメモリに取得することです。
以下の考慮事項は、ソースとターゲットからデータをプルするクエリです。
SQL Server-_select Id as LinkedColumn,CompareColumn from Source order by LinkedColumn
_
Oracle-_select Id as LinkedColumn,CompareColumn from Target order by LinkedColumn
_
ソースのレコード: 12377200
ターゲットのレコード: 12266800
以下は、いくつかの統計で試したアプローチです:
1)ソースおよびターゲットデータを読み取るためのオープンデータリーダーアプローチ:
_Total jobs running in parallel = 3
Time taken by Job1 = 01:47:25
Time taken by Job1 = 01:47:25
Time taken by Job1 = 01:48:32
There is no index on Id Column.
_
ここでは主な時間が費やされています:var dr = command.ExecuteReader();
問題:commandtimeout
から0(infinity)
まで保持しなければならないタイムアウトの問題もあり、それは悪いことです。
2)ソースおよびターゲットデータを読み取るためのチャンクごとの読み取りアプローチ:
_ Total jobs = 1
Chunk size : 100000
Time Taken : 02:02:48
There is no index on Id Column.
_
3)ソースデータとターゲットデータを読み取るためのチャンクごとの読み取りアプローチ:
_ Total jobs = 1
Chunk size : 100000
Time Taken : 00:39:40
Index is present on Id column.
_
4)ソースおよびターゲットデータを読み取るためのオープンデータリーダーアプローチ:
_ Total jobs = 1
Index : Yes
Time: 00:01:43
_
5)ソースおよびターゲットデータを読み取るためのオープンデータリーダーアプローチ:
_ Total jobs running in parallel = 3
Index : Yes
Time: 00:25:12
_
LinkedColumnにインデックスを作成するとパフォーマンスが向上する一方で、インデックスがない可能性のあるサードパーティのRDBMSテーブルを処理していることが問題であることがわかりました。
データベースサーバーをできるだけ自由にしたいので、データリーダーアプローチは、あまりにも多くのジョブが並行して実行され、データベースサーバーに必要以上のプレッシャーをかけるため、良い考えとは思えません。
したがって、リソースメモリのレコードをソースからターゲットにフェッチし、1〜1レコードの比較を行って、データベースサーバーを解放したままにします。
注: C#アプリケーションでこれを実行し、SSISまたはリンクサーバーを使用したくありません。
更新:
_Source Sql Query Execution time in sql server management studio: 00:01:41
Target Sql Query Execution time in sql server management studio:00:01:40
_
メモリ内の巨大な結果セットを読み取る最良の方法は何でしょうか?
コード:
_static void Main(string[] args)
{
// Running 3 jobs in parallel
//Task<string>[] taskArray = { Task<string>.Factory.StartNew(() => Compare()),
//Task<string>.Factory.StartNew(() => Compare()),
//Task<string>.Factory.StartNew(() => Compare())
//};
Compare();//Run single job
Console.ReadKey();
}
public static string Compare()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var srcConnection = new SqlConnection("Source Connection String");
srcConnection.Open();
var command1 = new SqlCommand("select Id as LinkedColumn,CompareColumn from Source order by LinkedColumn", srcConnection);
var tgtConnection = new SqlConnection("Target Connection String");
tgtConnection.Open();
var command2 = new SqlCommand("select Id as LinkedColumn,CompareColumn from Target order by LinkedColumn", tgtConnection);
var drA = GetReader(command1);
var drB = GetReader(command2);
stopwatch.Stop();
string a = stopwatch.Elapsed.ToString(@"d\.hh\:mm\:ss");
Console.WriteLine(a);
return a;
}
private static IDataReader GetReader(SqlCommand command)
{
command.CommandTimeout = 0;
return command.ExecuteReader();//Culprit
}
_
DBレコードをフェッチするためのDataReaderよりも高速なものはありません(知っています)。
大規模なデータベースでの作業には課題が伴います。2秒未満で1000万件のレコードを読み取ることは非常に優れています。
より速くしたい場合は、次のことができます。
Sqlcmd.exeとProcessクラスを使用してクエリを実行し、結果をcsvファイルに書き込んでから、csvをc#に読み取ります。 sqlcmd.exeは、大規模なデータベースをアーカイブするように設計されており、c#インターフェイスよりも100倍速く実行されます。 linqメソッドの使用は、SQLクライアントクラスよりも高速です。
クエリを並列化し、同時にマージする結果をフェッチします: https://shahanayyub.wordpress.com/2014/03/30/how-to-load-large-dataset-in-datagridview/
最も簡単(そしてIMOはSELECT * allに最適)にハードウェアを投げることです: https://blog.codinghorror.com/hardware-is-cheap-programmers-are-expensive/
また、ベンチマークを歪める可能性があるため、リリースモードでPRODハードウェアでテストしていることを確認してください。
Javaから大きなデータベース結果セットを処理する必要がある場合は、JDBCを選択して、必要な低レベルの制御を提供できます。一方、アプリケーションですでにORMを使用している場合は、JDBCにフォールバックすると、さらに苦痛を伴う可能性があります。ドメインモデルをナビゲートすると、楽観的ロック、キャッシング、自動フェッチなどの機能が失われます。幸い、HibernateのようなほとんどのORMには、それを支援するオプションがいくつかあります。これらのテクニックは新しいものではありませんが、いくつかの選択肢から選択できます。
簡単な例。 100.000レコードのテーブル(クラス "DemoEntity"にマップされている)があるとします。各レコードは、約2KBのランダムな英数字データを保持する(DemoEntityのプロパティ "property"にマップされた)1つの列で構成されます。 JVMは-Xmx250mで実行されます。 250MBが、システム上のJVMに割り当てることができる全体の最大メモリであると仮定しましょう。あなたの仕事は、現在テーブルにあるすべてのレコードを読み取り、さらに指定されていないいくつかの処理を行い、最後に結果を保存することです。一括操作の結果であるエンティティは変更されないと仮定します
これは私が使用するパターンです。特定のレコードセットのデータをSystem.Data.DataTable
インスタンスに取得してから、すべての非管理対象リソースをできるだけ早く閉じて破棄します。パターンは、System.Data
インクルードSystem.Data.OleDb
、System.Data.SqlClient
などの下の他のプロバイダーでも機能します。OracleClient SDKは同じパターンを実装していると思います。
// don't forget this using statements
using System.Data;
using System.Data.SqlClient;
// here's the code.
var connectionstring = "YOUR_CONN_STRING";
var table = new DataTable("MyData");
using (var cn = new SqlConnection(connectionstring))
{
cn.Open();
using (var cmd = cn.CreateCommand())
{
cmd.CommandText = "Select [Fields] From [Table] etc etc";
// your SQL statement here.
using (var adapter = new SqlDataAdapter(cmd))
{
adapter.Fill(table);
} // dispose adapter
} // dispose cmd
cn.Close();
} // dispose cn
foreach(DataRow row in table.Rows)
{
// do something with the data set.
}
私は何年も前に同じような状況にありました。問題を見る前に、SQLを使用して2つのシステム間でデータを移動するのに5日間連続して実行しました。
私は別のアプローチをとりました。
ソースシステムからデータを抽出して、平坦化されたデータモデルを表す少数のファイルにデータを抽出し、各ファイルにデータを配置して、ファイルから読み取るときにすべてが適切な順序で自然に流れるようにしました。
次に、Javaプログラムを作成して、これらのフラット化されたデータファイルを処理し、ターゲットシステム用の個別のテーブルロードファイルを生成しました。たとえば、ソース抽出には、ソースからのデータファイルが12個未満でした30から40程度に変わったシステムは、ターゲットデータベースのファイルをロードします。
このプロセスは数分で実行され、私は完全な監査とエラー報告を組み込んだので、ソースデータの問題と不一致をすばやく見つけて修正し、プロセッサを再実行できました。
パズルの最後のピースは、ターゲットのOracleデータベースへの各ロードファイルに対して並列バルクロードを実行する、私が書いたマルチスレッドユーティリティでした。このユーティリティは、各テーブルにJavaプロセスを作成し、Oracleの一括テーブルロードプログラムを使用して、データをOracle DBにすばやくプッシュしました。
JavaとOracleの一括読み込み機能の組み合わせを使用すると、何百万ものレコードの5日間のSQL-SQL転送が30分になりました。そして、エラーは発生しませんでした。システム間で転送されたすべてのアカウントのすべてのペニーについて。
したがって、SQLボックスの外側で考え、Java、ファイルシステム、およびOracleのバルクローダーを使用することができます。また、ソリッドステートハードドライブでファイルIOを実行していることを確認してください。
私はこの問題を別の方法で処理すると思います。
しかし、前にいくつかの仮定をしてみましょう:
これらの基礎ポイントがあると、私は次のことを処理します。
より速く読みたい場合は、元のAPIを使用してデータをより速く取得する必要があります。 linqのようなフレームワークを避け、DataReaderに依存してください。ダーティリード(SQLサーバーではwith(nolock))などの必要な天気を確認してください。
データが非常に大きい場合は、部分読み取りを実装してみてください。データにインデックスを作成するようなもの。たぶんあなたは日付から-までの条件を入れてすべてを選択することができます。
その後、システムでスレッドを使用してフローを並列化することを検討する必要があります。実際には、ジョブ1から取得する1つのスレッド、ジョブ2から取得する別のスレッド。これにより、時間を大幅に削減できます。
専門性はさておき、ここにはもっと根本的な問題があると思います。
select [...] order by LinkedColumn
LinkedColumnにインデックスを付けるとパフォーマンスは向上しますが、問題は、インデックスのある、またはないサードパーティのRDBMSテーブルを扱っていることです。
データベースサーバーをできるだけ自由にしたい
If DBがその列にツリーベースのインデックスを持っていることを確認することはできません。これは、DBが何百万もの要素のソートで非常にビジーになることを意味します。それは遅く、リソースを大量に消費します。 SQLステートメントのorder by
を取り除き、アプリケーション側で実行して、結果をより速く取得し、DBへの負荷を減らします...またはDBにそのようなインデックスがあることを確認してください!!!
...このフェッチが一般的な操作であるか、まれな操作であるかに応じて、DBに適切なインデックスを適用するか、すべてをフェッチしてクライアント側で並べ替える必要があります。