📥

Db2のLOAD後にINSERTだけSQL0290Nで落ちる ― IMPORT/INGEST/LOADの選び分け

に公開

Db2に大量データを入れる手段は IMPORT / INGEST / LOAD の3つです。速度で比べた記事は多いのですが、現場で問題になるのは投入が終わった後です。速いからと LOAD を選ぶと、投入直後に別のジョブの INSERT だけが SQL0290N で落ちます。しかも SELECT は通るので、次の更新まで誰も気づきません。

この記事では3方式を ログ消費量・投入中の他業務・投入後の表スペース状態・失敗したときの戻り方 の4軸で実測し、どれを選ぶかの判断材料をまとめます。

まず結論:3方式は速度ではなくログ量と可用性で選ぶ

500,000行を同一定義の表に投入した実測値です。

方式 所要 ログページ 投入中の他業務の最大停止 投入直後の表スペース
IMPORT 54.5s 1,145 48.4秒(SELECT・INSERTとも) NORMAL
INGEST 2.4s 1,145 1.0秒 NORMAL
LOAD(既定) 0.4s 9 1.5秒 ※ BACKUP PENDING

LOAD の可用性だけは 3,000,000行 での計測値です(500,000行では投入が0.4秒で終わり、他業務との重なりを測れないため)。

この表から読み取れることは3つです。

  1. ログを減らせるのは LOAD だけ。 IMPORTINGEST は同量です。
  2. LOAD がログを減らす代償が BACKUP PENDING。 速さだけを見て選ぶと、投入後に更新が止まります。
  3. 他業務を最も長く止めるのは LOAD ではなく IMPORT

LOADの既定は COPY NO ― 表スペースが BACKUP PENDING に入る

アーカイブログ運用のDBで、3,000,000行を LOAD した直後(バックアップを取る前)の状態です。

LOAD の書き方 MON_GET_TABLESPACE.TBSP_STATE LIST TABLESPACES の State LOAD QUERY TABLE SELECT INSERT
オプション無し(既定) BACKUP_PENDING 0x0020 Normal 成功 SQL0290N
COPY NO BACKUP_PENDING 0x0020 Normal 成功 SQL0290N
COPY YES TO <dir> NORMAL 0x0000 Normal 成功 成功
NONRECOVERABLE NORMAL 0x0000 Normal 成功 成功

オプションを書かなければ COPY NO と同じです。LOAD はログを書かない代わりに、ロールフォワードで再現できないデータを表スペースに作ります。そのため次のバックアップを取るまで表スペースが更新を受け付けなくなります。

対処は3つのどれかです。

-- ① 投入分をコピーとして書き出す(リカバリー可能なまま。書き出し先の容量が要る)
LOAD FROM data.csv OF DEL INSERT INTO T_LOAD COPY YES TO /backup/loadcopy

-- ② リカバリー対象から外す(後で再投入できるデータに限る。ロールフォワードで表が無効になる)
LOAD FROM data.csv OF DEL INSERT INTO T_LOAD NONRECOVERABLE

-- ③ 既定のまま投入し、直後に表スペース単位でバックアップして解除する
BACKUP DB SAMPLE TABLESPACE (TSLOAD) ONLINE TO /backup

③はDB全体のバックアップである必要はありません。その表スペースだけをオンラインバックアップすれば NORMAL に戻ります

LOAD QUERY TABLE を見ても分からない

上の表の LOAD QUERY TABLE 列がこの状態でいちばん厄介です。

$ db2 "LOAD QUERY TABLE DB2INST1.T_LOAD"
 Tablestate:
  Normal

BACKUP PENDING は「表」ではなく「表スペース」の状態なので、表状態をいくら見ても出てきません。LOAD 後の確認は表スペース側を見ます。

SELECT TBSP_NAME, TBSP_STATE FROM TABLE(MON_GET_TABLESPACE('',-2))
  WHERE TBSP_NAME = 'TSLOAD'

SELECT は通り、INSERT だけ落ちる

BACKUP PENDING 中でも読み取りは通ります。落ちるのは更新だけです。

$ db2 "SELECT COUNT(*) FROM T_LOAD"
1
-----------
    3000000

$ db2 "INSERT INTO T_LOAD VALUES (9999999, 'x', 1)"
SQL0290N  Table space access is not allowed.  SQLSTATE=55039

投入後の確認を SELECT COUNT(*) で済ませていると「行数も合っているし読めている」で通過してしまい、次にその表を更新するジョブが動いた時点で初めて発覚します。SQL0290N の状態値の読み方は別記事にまとめています。

https://zenn.dev/firese/articles/db2-sql0290n-tablespace-access

投入中に他業務を最も長く止めるのは IMPORT

投入と時間帯を重ねて、SELECT専用プローブとINSERT専用プローブを別プロセスで回した結果です。

操作 投入の所要 投入中に成功したSELECT SELECTの最大停止 投入中に成功したINSERT INSERTの最大停止
LOAD ... ALLOW NO ACCESS(既定・3M行) 1.41s 4回 1.50s 4回 1.30s
LOAD ... ALLOW READ ACCESS(3M行) 3.33s 114回 0.27s 5回 3.16s
IMPORT(500k行) 48.4s 2回 48.35s 3回 48.31s
INGEST ... LOCKSIZE TABLE(500k行) 2.81s 25回 1.03s 20回 1.06s

この4パターンでエラーは1件も出ていません。 LOCKTIMEOUT = -1 の環境では、他業務は SQL0668N などで落ちるのではなく、ロック待ちのまま固まります。障害は「エラーが出る」形ではなく「応答が返らない」形で現れるので、監視でエラーコードだけを見ていると検知できません。

LOAD は既定が ALLOW NO ACCESS で表全体を止めますが、止まる時間そのものが短いのが実測です。ALLOW READ ACCESS を付ければ投入中も読めます。ただし更新は止まったままで、INSERTの最大停止は LOAD の所要時間とほぼ一致します。

一方 IMPORT は、行数が6分の1でも48秒間ブロックし続けました。IMPORT は通常のINSERTとして1行ずつ処理するため、投入が長引けばその間ずっと他業務が待たされます。「LOADは表をロックするから危険、IMPORTは安全」という選び方は、実測とは逆です。

ログ量は IMPORT と INGEST で同じ

500,000行投入時の db2pd -db sample -logs の Pages Written 差分です。

方式 所要 ログページ 容量
IMPORT(既定) 54.52s 1,145 4,580 KB
IMPORT ... COMMITCOUNT 5000 50.30s 1,146 4,584 KB
INGEST ... LOCKSIZE TABLE 2.36s 1,145 4,580 KB
LOAD ... COPY NO 0.36s 9 36 KB
LOAD ... NONRECOVERABLE 0.33s 9 36 KB

INGESTIMPORT の20倍以上速いのに、ログ消費量は1ページ単位まで同じでした。どちらも1行ずつログに書きます。速さの差は書き込み方(INGEST は複数スレッドで並列にSQLを流す)から来ており、ログ量には効きません。INGEST はログに優しい方式ではありません。

LOAD だけが127分の1です。これが LOAD の本質的な差であり、その代償が先ほどの BACKUP PENDING です。

COMMITCOUNT はログの総量を減らさない

SQL0964C(トランザクションログ満杯)を避けるために COMMITCOUNT を付けるのは定石ですが、減るのは同時に保持するログ空間であって、書き込む総量ではありません。実測でも 1,145 → 1,146 ページとむしろ増えています(コミットレコードの分)。

「ログ領域が足りないから COMMITCOUNT を付ける」は正しく、「ログ書き込み量を減らしたいから COMMITCOUNT を付ける」は誤りです。総量を減らしたいなら LOAD を選ぶしかありません。

https://zenn.dev/firese/articles/db2-sql0964c-log-full

INGEST は既定のロック粒度で倒れることがある

INGEST で500,000行を投入したとき、対象表の LOCKSIZE で結果が変わりました。

表の LOCKSIZE 結果
ROW(既定) SQL0912N The maximum number of lock requests has been reached で異常終了。挿入 0行
TABLE 500,000行を2.4〜2.6秒で正常完了

止まる行数は実行のたびに違いました(136,344 / 211,242 / 213,583行を読んだ時点)。共通しているのは結果で、途中まで入って止まるのではなく全部巻き戻り、挿入行数は0です。

これは LOCKLISTMAXLOCKS に依存する現象で、この環境は MAXLOCKS = 10 と小さめです。すべての環境で起きるわけではありませんが、INGESTSQL0912N で落ちたときは LOCKLIST / MAXLOCKS を増やすか、投入対象表を ALTER TABLE ... LOCKSIZE TABLE にして行ロックを取らせない、のどちらかで抜けられます。

ALTER TABLE T_ING LOCKSIZE TABLE

なお LOCKSIZE TABLE にすれば表全体のロックを取るので、投入中の他業務への影響は上の可用性の表のとおり(最大停止1.0秒)に変わります。

INGEST の進捗確認は2コマンド

INGEST は実行中に別セッションから進捗を見られます。

$ db2 "INGEST LIST"
Ingest job ID               = DB21201:20260803.163548.923377:00007:00005
Ingest temp job ID          = 1
Database Name               = SAMPLE
Target table                = DB2INST1.T_ING
Input type                  = FILE
Start Time                  = 08/03/2026 16:35:49.302225
Running Time                = 00:00:01
Number of records processed = 267000

$ db2 "INGEST GET STATS FOR 1"
Overall           Overall           Current           Current
ingest rate       write rate        ingest rate       write rate
(records/second)  (writes/second)   (records/second)  (writes/second)   Total records
----------------- ----------------- ----------------- ----------------- -----------------
          1915215            267000           1915215            267000            267000

GET STATS FOR に渡すのは INGEST LISTIngest temp job ID(この例では 1)で、長い Ingest job ID ではありません。FOR ID 1 のように ID を挟むと SQL0104N になります。存在しない番号を渡すと SQL2947N です。実行中のジョブが無ければ INGEST LISTSQL2982W を返します。

INGEST は既定でリスタート表を要求する

INGEST は既定でリスタート可能モードで動きます。中断した投入を途中から再開できるよう、進捗を SYSTOOLS.INGESTRESTART という表に記録するためです。この表が無いと、投入は1行も入らずに落ちます。

SQL2957N  The ingest operation failed to restart because the ingest utility
could not find the restart log table. Restart log table name:
"SYSTOOLS.INGESTRESTART".  SQLSTATE=42704

メッセージが「restart に失敗した」と読めるため、再開処理をしていないのに再開の話が出てきて混乱します。実際には投入そのものが始まっていません(挿入0行)。新しい環境で INGEST を初めて使うときに踏みます。

対処は2つです。

-- ① リスタート表を作る。以後は既定のまま使えて、中断時の再開も効く
CALL SYSPROC.SYSINSTALLOBJECTS('INGEST', 'C', CAST(NULL AS VARCHAR(128)), CAST(NULL AS VARCHAR(128)))

-- ② リスタートを使わない。表は不要になるが、中断したら最初からやり直し
INGEST FROM FILE data.csv FORMAT DELIMITED (...) RESTART OFF INSERT INTO T_ING VALUES (...)

RESTART OFF は表が無くても通るので、検証やワンショットの投入では手軽です。夜間バッチのように再実行の手間を減らしたいなら①を選びます。

投入方式の決め方

まず、投入前に決める4つです。

  • ログ領域が投入量に対して足りないLOAD。ただし COPY YES / NONRECOVERABLE / 直後の表スペースバックアップのどれをやるかを、選ぶ時点で決めておく。
  • 投入中も他業務を動かしたいINGEST、または LOAD ... ALLOW READ ACCESS
  • 投入後すぐに更新ジョブが動く → 既定の LOAD は選べない。COPY YESNONRECOVERABLE、または INGEST にする。
  • INGEST を初めて使う → 対象表の LOCKSIZESYSTOOLS.INGESTRESTART の有無を先に確認する。

そのうえで手順に組み込むのは次の3点です。

  1. ログ領域LOGFILSIZ × LOGPRIMARY + LOGSECOND)と投入量を突き合わせてから方式を決める
  2. 投入後の確認は表スペース状態で見る。LOAD QUERY TABLESELECT COUNT(*) はどちらも正常に見える
  3. 投入中の他業務は無応答で詰まる。監視をエラーコードだけに頼らない

📕 もっと深く:Db2運用を12章で体系化した本を書きました

この記事で扱ったデータ投入の選び分けは、Db2運用のほんの一部です。

Db2はRDBMS市場でシェア数%。情報が少なく、頼れるのは膨大な公式マニュアルだけ——そんな現状を変えたくて、「なぜこの値にするのか」「この障害のとき次に何を見るのか」という、マニュアルに載っていない現場の文脈を1冊にしました。実機で検証した内容と、Db2の設計上そうなる理由をまとめました。

アーキテクチャ/構成パラメーター選定/セキュリティ/バックアップ・リカバリ/HADR/pureScale/日常点検の自動化/メモリ・ロック競合/SQLチューニング/RUNSTATS・REORG/MON_GET監視/現場の難題集まで、全12章+運用シェルスクリプト集です。

https://zenn.dev/firese/books/db2-practical-bible

Db2の運用で踏んだ地雷や、扱ってほしいテーマがあれば、ぜひコメントで教えてください。次の記事のネタにさせてもらいます。

Discussion