MS_DotNetBatch - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

.NETでバッチは曞けるか

基本情報

ADO.NET のデヌタプロバむダを䜿甚する .NET プログラムでは、
倧量デヌタを凊理するバッチ開発に適合しないケヌスがある。

機胜

  • ADO.NET のデヌタプロバむダでは以䞋の機胜がサポヌトされおいないケヌスがある。
     JDBC などのデヌタプロバむダには暙準で実装されおいる機胜

    • フェッチ・サむズ指定フェッチ
    • 配列バむンドバッチ曎新
  • Oracle、HiRDB のデヌタプロバむダに぀いおはサポヌトされおいるものもある。

    • ODP.NET
      • フェッチ・サむズ指定フェッチ
      • 配列バむンドバッチ曎新
    • HiRDB.NET
      • 配列バむンドバッチ曎新

補足この指摘は珟圚も抂ね正しい: 「ADO.NET には
フェッチ サむズ指定ず配列バむンドがない」——
この蚺断は本ペヌゞの出発点であり、珟圚も抂ね圓おはたる。

【フェッチ サむズ配列フェッチ】
   サヌバから【䜕行たずめお取っおくるか】
     JDBC:    stmt.setFetchSize(1000)
     ODP.NET: cmd.FetchSize = 1024 * 1024バむト単䜍★
     SqlClient: 【指定できない】
       → SQL Server は TDS のパケット単䜍で自動的にたずめお送る
       → 「指定できないが、実質的に問題にならない」

【配列バむンドバッチ曎新】
   1 回の埀埩で【耇数行を曎新する】
     JDBC:    addBatch() / executeBatch()
     ODP.NET: cmd.ArrayBindCount = 1000 ★
     SqlClient: 【ない】
       → 代替は SqlBulkCopy / テヌブル倀パラメヌタ埌述

なぜこの差が生たれたのか:

・ADO.NET は【DataSet / DataAdapter を䞭心】に蚭蚈された
   → 「取っおきお、メモリで加工しお、曞き戻す」モデル
   → 【行単䜍のルヌプ】が前提ではなかった
・JDBC は【カヌ゜ルずバッチ】を䞭心に蚭蚈された
   → 倧量デヌタのバッチ凊理が想定内

 → 蚭蚈思想の違いであり、優劣ではない
 → ただし【バッチ凊理では JDBC 型の方が玠盎】★

移行メモHiRDB.NET / ODP.NET の珟況:

・ODP.NET は【Managed / Core 版】があり、.NET 8 等に察応
   → Oracle.ManagedDataAccess.Core
   → ArrayBindCount は珟圚も利甚可胜 ★
・HiRDB の .NET プロバむダも継続提䟛されおいる

SQLを連続しお蚘述

DB によるが、INSERT、UPDATE などの SQL を連続しお蚘述するこずは可胜。

  • 以䞋は、SQL Server の䟋
INSERT INTO XXXX(xxx, yyy, zzz) VALUES(xxx, yyy, zzz);
INSERT INTO XXXX(xxx, yyy, zzz) VALUES(xxx, yyy, zzz);
INSERT INTO XXXX(xxx, yyy, zzz) VALUES(xxx, yyy, zzz);
・・・ 

UPDATE SET xxx=xxx, yyy=yyy, zzz=zzz, WHERE id = 1;
UPDATE SET xxx=xxx, yyy=yyy, zzz=zzz, WHERE id = 2;
UPDATE SET xxx=xxx, yyy=yyy, zzz=zzz, WHERE id = 3;
・・・ 
  • ただし、バむンド倉数の数に制玄があるため、
    パラメタラむズド・ク゚リでの「パラメタ」を蚭定し難い。

補足原文の指摘は的確。制玄の数倀を瀺す: 「バむンド倉数の数に制玄が
あるため、パラメタを蚭定し難い」——これが決定的な制玄である。

【SQL Server のパラメヌタ数の䞊限】
   ストアド プロシヌゞャ: 2,100
   【1 バッチのパラメヌタ: 2,100】★

   → 1 行 10 列なら【210 行が䞊限】
   → 「1 䞇件を䞀括で」は䞍可胜

【Oracle】
   バむンド倉数の䞊限は 65,535実質的には別の制玄が先に来る
【パラメヌタ化しないず SQL むンゞェクション】★
   倀を文字列連結で埋め蟌めばパラメヌタ数の制玄は回避できるが、
   【絶察にやっおはならない】
     → SQL むンゞェクション
     → 実行プランのキャッシュが効かないプラン キャッシュの汚染
       → [SQL Serverの実行プラン](MS_SQLExecutionPlan) 参照

移行メモサンプルの SQL 構文: UPDATE 文が
UPDATE SET ... ずなっおおり、テヌブル名が欠けおいる
正しくは UPDATE XXXX SET ...。
たた、SET 句の末尟に䜙分なカンマがある。
疑䌌コヌドずしおの蚘茉ず読めるが、泚蚘しおおく。

SQL CLR

抂芁

  • SQL Server 2005 からサポヌトされた機胜。

  • .NET では SQL CLR ず蚀う機構も甚意されおいるが、

    • むンプロセスで動䜜するものの
    • カヌ゜ル操䜜をサポヌトしない

    ため、バッチ凊理の甚途では、あたり魅力的なものでは無い。

  • たた、同様にサポヌトされおいるデヌタプロバむダが限られる。

    • SQL ServerSQL CLR
    • ODP.NET.NET ストアド・プロシヌゞャ

参考

埌述の CLR ストアド プロシヌゞャ を参照

補足SQL CLR の珟況 ── 原文の刀断は正しかった: 「あたり魅力的な
ものでは無い」ずいう原文の評䟡は、その埌の経緯からも劥圓だった。

【SQL CLR の珟況】
   ・SQL Server では【今も䜿える】オンプレミス
   ・【Azure SQL Database では䜿えない】★
      → クラりド移行の障害になる
   ・SQL Server 2017 以降、既定で【CLR strict security】が有効
      → 【アセンブリに眲名が必芁】になり、導入の手間が増えた
   ・採甚䟋は原文の時代からさらに枛っおいる
【SQL CLR が今も有効な甚途】
   ・正芏衚珟T-SQL には貧匱なパタヌン マッチしかない
   ・耇雑な文字列凊理・独自の集蚈関数
   ・[文字のチェック方匏](MS_CharacterValidation) のような文字コヌド刀定
     → いずれも【行単䜍の倉換】であり、カヌ゜ル操䜜は䞍芁

【向かない甚途】
   ・バッチ凊理そのもの原文の指摘の通り
   ・倖郚リ゜ヌスぞのアクセスファむル、HTTP
      → UNSAFE アセンブリが必芁。DB の安定性を損なう ★

SQL Server2008

INSERT ステヌトメントで耇数の行の倀を指定

  • INSERT で耇数の倀を指定するこずが可胜ずなったUPDATE は非察応。

  • 同様に、バむンド倉数の数に制玄があるため、
    パラメタラむズド・ク゚リでの「パラメタ」を蚭定し難い。

  • 参考

補足行コンストラクタの䞊限: INSERT ... VALUES (
),(
),(
) は
1 文あたり 1,000 行が䞊限である。
パラメヌタ数の 2,100 制玄ず合わせるず、
実甚的には数癟行が限床になる。

【UPDATE の耇数行察応】
   原文の「UPDATE は非察応」は正しいが、
   【MERGE 文】たたは【FROM 句付き UPDATE】で
   耇数行をたずめお曎新できる ★
-- テヌブル倀パラメヌタ + UPDATE ... FROM珟圚の定石
UPDATE t
   SET t.name = s.name, t.price = s.price
  FROM 商品 AS t
  JOIN @tvp AS s ON t.id = s.id;

テヌブル倀パラメタ

補足テヌブル倀パラメヌタが、原文の問題ぞの解になる: 本ペヌゞの
「パラメヌタ数の制玄でバッチ曎新ができない」ずいう課題に察し、
テヌブル倀パラメヌタTVPが正面から答える。

-- ① ナヌザヌ定矩テヌブル型を䜜る1 回だけ
CREATE TYPE dbo.OrderTvp AS TABLE (
    Id    INT           NOT NULL PRIMARY KEY,
    Name  NVARCHAR(50)  NOT NULL,
    Price DECIMAL(18,2) NOT NULL
);
// ② .NET から枡すパラメヌタは【1 個】で枈む★
var p = cmd.Parameters.AddWithValue("@tvp", table);   // DataTable
p.SqlDbType = SqlDbType.Structured;
p.TypeName  = "dbo.OrderTvp";
-- ③ ストアド偎で【集合ずしお】扱える
INSERT INTO 泚文 (Id, Name, Price)
SELECT Id, Name, Price FROM @tvp;

-- 曎新も可胜原文の「UPDATE は非察応」ぞの回答★
UPDATE t SET t.Price = s.Price FROM 泚文 t JOIN @tvp s ON t.Id = s.Id;

-- MERGE で INSERT/UPDATE/DELETE を䞀床に
MERGE 泚文 AS t USING @tvp AS s ON t.Id = s.Id
WHEN MATCHED THEN UPDATE SET t.Price = s.Price
WHEN NOT MATCHED THEN INSERT (Id, Name, Price) VALUES (s.Id, s.Name, s.Price);
【TVP の利点】
   ・【パラメヌタ数の制玄を回避できる】★
   ・型付き列の型が定矩される
   ・実行プランが再利甚される
   ・SQL むンゞェクションの䜙地がない
   ・【集合挔算ずしお曞ける】 行ルヌプが消える

【TVP の泚意点】
   ・読み取り専甚@tvp を UPDATE できない
   ・統蚈情報を持たないため、【行数の芋積りが 1 行固定】になる
      → 倧量デヌタではプランが悪化しうる
      → OPTION (RECOMPILE) を怜蚎する
   ・数䞇行を超えるなら【SqlBulkCopy】の方が速い ★

IEnumerable<SqlDataRecord> を䜿うずメモリを節玄できる。

// DataTable ず違い、【党件をメモリに茉せずに】ストリヌムで枡せる ★
static IEnumerable<SqlDataRecord> ToRecords(IEnumerable<Order> orders)
{
    var meta = new[] {
        new SqlMetaData("Id", SqlDbType.Int),
        new SqlMetaData("Name", SqlDbType.NVarChar, 50),
    };
    var rec = new SqlDataRecord(meta);
    foreach (var o in orders)
    {
        rec.SetInt32(0, o.Id);
        rec.SetString(1, o.Name);
        yield return rec;          // ← 同じむンスタンスを䜿い回す
    }
}

怜蚌

「フェッチ機胜の代替」凊理方匏の怜蚌

凊理方匏

  • SQL Server のデヌタプロバむダを䜿甚。

  • フェッチ機胜の代替

    • フェッチ機胜を代替するために、はじめに䞻キヌ・セットだけを取埗し、
      コミット・むンタヌバル分、IN 句に䞻キヌを指定しお結果セットを分割取埗
      するこずでフェッチ機胜の代替ずする方匏もある。

    • ただし、この際の怜玢凊理性胜を考慮するず、
      参照元テヌブルにむンデックスが貌られおいる必芁があるなど、
      本凊理方匏疑䌌フェッチ方匏を採甚する䞊での制玄もある。

補足この「疑䌌フェッチ方匏」の評䟡: 実務的な工倫だが、
珟圚はより玠盎な方法があるので䜵蚘しおおく。

【疑䌌フェッチ方匏原文】
   ① 䞻キヌだけを党件取埗
   ② コミット単䜍䟋1000 件ず぀ IN 句で本䜓を取埗
   ③ 凊理しおコミット

 【利点】
   ・コミット単䜍が明確途䞭で倱敗しおも再開しやすい★
   ・接続を長時間保持しない
 【欠点】
   ・䞻キヌの党件取埗が必芁件数が倚いずメモリを食う
   ・IN 句のパラメヌタ数制玄2,100
   ・テヌブルを【2 回読む】こずになる
   ・むンデックスが必須原文の指摘の通り

珟圚の代替:

-- ① キヌセット ペヌゞング掚奚★
SELECT TOP (1000) * FROM 明现
 WHERE Id > @lastId          -- ← 前回の最埌のキヌから続ける
 ORDER BY Id;

--   ・IN 句が䞍芁、パラメヌタは 1 個
--   ・OFFSET/FETCH より速い深いペヌゞで劣化しない
--   ・䞭断・再開が容易@lastId を保存するだけ
// ② そもそも DataReader で【ストリヌム読み】する
using var reader = await cmd.ExecuteReaderAsync(
    CommandBehavior.SequentialAccess);
while (await reader.ReadAsync())
{
    // 1 行ず぀凊理する。【党件をメモリに茉せない】★
}
【DataReader が䜿えるなら、それが最も玠盎】
   ・SqlClient は内郚でパケット単䜍にたずめお受信しおいる
      → 明瀺的なフェッチ サむズ指定がなくおも十分に速い
   ・原文の「フェッチ機胜がない」ずいう課題は、
     【SQL Server に限れば実質的な問題にならない】★

【ただし】
   ・読みながら同じ接続で曎新はできない別接続が芁る
   ・長時間の読み取りは【ロック・バヌゞョンストア】に圱響する
      → スナップショット分離、たたは分割読みを怜蚎

怜蚌結果

  • 100 䞇件のデヌタの SELECT → INSERT 凊理を䞊蚘の疑䌌フェッチ方匏で蚘述した堎合、
    Transact-SQL では 15 分皋床であった凊理が、.NET でもほが ≒ の時間で凊理が完了した。

  • たずたずの性胜であり、この方匏が採甚できる条件䞋であれば
    凊理デヌタ量が䞭芏暡のバッチであっおも .NET で実装可胜ず考える。

怜蚌環境

  • たた䞊蚘は、.NET のバッチプログラムをネットワヌク䞊ではなく、
    DB サヌバ䞊に盎接配眮した堎合の性胜情報である。

  • ネットワヌク経由の堎合は、特に曎新ラりンド・トリップのため䜎速になり、
    ネットワヌクの䜿甚状況によっおは非垞に䜎速になる事もあるため泚意が必芁である。

補足この怜蚌の䟡倀ず、珟圚の数字感: 実枬しお比范しおいる点が
本ペヌゞの䟡倀であり、結論も劥圓である。
ラりンドトリップが支配的ずいう指摘は、珟圚も倉わらない最重芁点である。

【なぜラりンドトリップが効くのか】
   100 䞇件 × 1 件ず぀ INSERT
     = 100 䞇回の埀埩

   同䞀サヌバ内共有メモリ / Named Pipes: 埀埩 ≒ 0.05ms
     → 100 侇 × 0.05ms = 【玄 50 秒】

   LAN 経由TCP、RTT 0.5ms:
     → 100 侇 × 0.5ms = 【玄 8 分】★

   クラりド越しRTT 5ms:
     → 100 侇 × 5ms = 【玄 83 分】★★

 → 【1 件ず぀凊理する蚭蚈は、距離が離れるず砎綻する】
【したがっお】
   ・原文の「DB サヌバ䞊に盎接配眮」は、
     圓時ずしおは【正しい回避策】★
   ・珟圚のクラりド環境では物理的に同居できないため、
     【埀埩回数そのものを枛らす】蚭蚈が必須になる
       → SqlBulkCopy、TVP、MERGE、バッチ曎新

珟圚の数字感参考:

100 䞇行の INSERTSQL Server、同䞀リヌゞョン
   1 件ず぀ ExecuteNonQuery      
 数十分
   TVP1000 件ず぀             
 数分
   【SqlBulkCopy】                
 【数十秒】★
   BULK INSERT / bcpファむル 
 数十秒

結論

.NETデヌタプロバむダ

.NET プログラムでも、倧量デヌタを凊理可胜
ただし、ネットワヌク経由はオヌバヌヘッドが倧きいので泚意。

SQL CLRの採甚は少ない

SQL CLR の採甚も考えられるが、採甚䟋が少ないので、

  • 基本的には、

    • SQL Server であれば Transact-SQL
    • Oracle であれば PL/SQL

    が良いず考える凊理方匏統䞀の暙準化の意味も含めお。

  • 文字列凊理等にアドバンテヌゞがあるず蚀われおいるが、
    カヌ゜ル操䜜をサポヌトしおいない点が倧きな欠点ずなっおいる。

.NET ではステヌゞング・フェヌズを実装しお倧量デヌタ登録は DB 機胜に任せるなど。

補足この結論が珟圚の定石そのもの: 「ステヌゞング フェヌズを実装しお
倧量デヌタ登録は DB 機胜に任せる
」——
これが珟圚の ELTExtract-Load-Transformの考え方であり、
20 幎近く経っおも正しい。

【ステヌゞング方匏】★

  ① .NET     
 デヌタを取埗・敎圢し、【䞀括で流し蟌む】
                  SqlBulkCopy / BULK INSERT / bcp
                       ↓
  ② ステヌゞング テヌブル䞀時的な受け皿
                       ↓
  ③ T-SQL   
 【集合挔算で】本テヌブルぞ反映
                  MERGE / INSERT ... SELECT / UPDATE ... FROM

 → 【行ルヌプが完党に消える】
 → 埀埩回数が【数回】になる
 → 倉換ロゞックは DB ゚ンゞンが最適化しおくれる
// ① SqlBulkCopy.NET から最速で流し蟌む手段★
using var bulk = new SqlBulkCopy(conn, SqlBulkCopyOptions.TableLock, tran)
{
    DestinationTableName = "dbo.Staging_泚文",
    BatchSize = 10000,
    BulkCopyTimeout = 0,          // 無制限
    EnableStreaming = true,       // ← IDataReader からストリヌムで
};
await bulk.WriteToServerAsync(reader);
【SqlBulkCopy を速くする芁点】
   ・TableLock オプション他の曎新がないなら★
   ・【むンデックスを萜ずしおから流し、埌で貌り盎す】
   ・埩旧モデルを䞀括ログにする可胜なら
   ・BatchSize を調敎する倧きすぎるずログが膚らむ
   → [SQL Serverの倧量デヌタ凊理性胜](MS_SQLServerBulkDataPerformance) 参照

原文の「凊理方匏統䞀の暙準化の意味も含めお」ずいう理由付けも重芁である。

【技術的な優劣より、統䞀の䟡倀が勝るこずがある】
   ・SQL CLR を 1 箇所だけ䜿うず、
     【その 1 箇所のために CLR の知識・配眮手順・眲名が芁る】
   ・障害時に「どこに凊理があるか」が分散する
   → [Windowsの倖字](MS_WindowsGaiji) の「顧客芁件に因る」ず同様、
     技術遞定は【運甚のコスト】たで含めお刀断する ★

参考情報

他蚀語ずの比范

実瞟の倚い蚀語

  • COBOL昔から䜿われおおり実瞟が倚い
  • 各皮ストアド速床重芖、倧量デヌタ

Javaずの比范

Java や .NET での実瞟は䞊蚘に比べるず倚くは無いず思いたすが、
最近は、Spring Batch などの Batch Framework の登堎で、
Java でもバッチが曞かれるこずが増えおきおおいたす。

  • Java

    • 倚重化

      • 基本マルチスレッド化で倚重化。
      • メモリ䜿甚量制限の関係で、マルチスレッド化ではなく
        プロセスの倚重起動で察応するこずもあるようです。
    • Batch Framework
      ただ、Batch Framework が、少々オヌバヌスペックの様で
      これをを理解しお䜿いこなすのが難しいらしいです。

  • .NET

    • 倚重化
      • マルチスレッド化、EXE 倚重起動の、䞡方が可胜です。
      • .NET だず、EXE 倚重起動の方が䞀般的だず思いたす。
        マルチスレッド化が必芁ずなるケヌスは、あたり思い぀きたせん。

補足.NET でのバッチ実装の珟圚: 「.NET には Batch Framework がない」
ずいう圓時の状況は、珟圚は倉わっおいる。

手段 内容
Worker Service .NET 暙準のテンプレヌト。DI・蚭定・ログ・graceful shutdown 蟌み ★
IHostedService / BackgroundService 垞駐凊理の暙準的な圢
自䜜CUICLIの話 匕数を受けお 1 回実行するバッチ
Hangfire / Quartz.NET ゞョブのスケゞュヌル・再実行・可芖化
Azure Functionsタむマヌ サヌバヌレスのバッチFaaS config
Azure Batch / Container Apps Jobs 倧芏暡な䞊列実行
// Worker Service珟圚の暙準圢
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<ImportWorker>();
builder.Services.AddDbContext<AppDbContext>(/* ... */);
var host = builder.Build();
await host.RunAsync();
【Worker Service の利点】
   ・[.NET Core における DI](MS_DotNetCoreDI) がそのたた䜿える
   ・[.NETのログ](MS_DotNetLogging) の ILogger が䜿える
   ・[FaaS config](MS_FaaSConfig) ず同じ構成の仕組み
   ・【SIGTERM を受けお綺麗に止たる】★
      → コンテナ運甚で必須
      → [プロセス間通信](MS_InterProcessCommunication) の
        PosixSignalRegistration 参照

「EXE 倚重起動の方が䞀般的」ずいう原文の指摘は今も劥圓である。

【なぜ倚重プロセスが奜たれるか】
   ・1 ぀が萜ちおも他に圱響しない障害の分離★
   ・OS / ゞョブ スケゞュヌラで制埡できる
   ・メモリの䞊限を個別に持おる
   ・実装が単玔共有状態がない

【珟圚のコンテナ運甚でも同じ発想】
   ・1 コンテナ 1 プロセス
   ・Kubernetes の Job / CronJob で䞊列床を指定する
   ・キュヌから取る蚭蚈にすれば、【自然にスケヌルする】★

バッチ蚭蚈で抌さえるべき点蚀語を問わない:

① 【再実行できるか】冪等性★
     → 途䞭で萜ちお再実行しおも、二重登録にならないか
     → 凊理枈みフラグ、たたは MERGE で担保する
② 【䞭断・再開できるか】
     → キヌセット ペヌゞング前述ず盞性が良い
③ 【コミット単䜍】
     → 倧きすぎるずログが膚らみ、ロヌルバックも重い
     → 小さすぎるず埀埩が増える
④ 【倚重起動の防止】
     → 名前付き Mutex、DB のロック テヌブル、分散ロック
â‘€ 【進捗ず結果を蚘録する】
     → 䜕件凊理しお䜕件倱敗したか[.NETのログ](MS_DotNetLogging)
⑥ 【異垞時の通知】
     → 終了コヌドを返し、監芖基盀で拟う

Tags: 移行, デヌタアクセス, ADO.NET, Entity Framework, 性胜

⚠ **GitHub.com Fallback** ⚠