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つです。
-
ログを減らせるのは
LOADだけ。IMPORTとINGESTは同量です。 -
LOADがログを減らす代償が BACKUP PENDING。 速さだけを見て選ぶと、投入後に更新が止まります。 - 他業務を最も長く止めるのは
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 の状態値の読み方は別記事にまとめています。
投入中に他業務を最も長く止めるのは 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 |
INGEST は IMPORT の20倍以上速いのに、ログ消費量は1ページ単位まで同じでした。どちらも1行ずつログに書きます。速さの差は書き込み方(INGEST は複数スレッドで並列にSQLを流す)から来ており、ログ量には効きません。INGEST はログに優しい方式ではありません。
LOAD だけが127分の1です。これが LOAD の本質的な差であり、その代償が先ほどの BACKUP PENDING です。
COMMITCOUNT はログの総量を減らさない
SQL0964C(トランザクションログ満杯)を避けるために COMMITCOUNT を付けるのは定石ですが、減るのは同時に保持するログ空間であって、書き込む総量ではありません。実測でも 1,145 → 1,146 ページとむしろ増えています(コミットレコードの分)。
「ログ領域が足りないから COMMITCOUNT を付ける」は正しく、「ログ書き込み量を減らしたいから COMMITCOUNT を付ける」は誤りです。総量を減らしたいなら LOAD を選ぶしかありません。
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です。
これは LOCKLIST と MAXLOCKS に依存する現象で、この環境は MAXLOCKS = 10 と小さめです。すべての環境で起きるわけではありませんが、INGEST が SQL0912N で落ちたときは 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 LIST の Ingest temp job ID(この例では 1)で、長い Ingest job ID ではありません。FOR ID 1 のように ID を挟むと SQL0104N になります。存在しない番号を渡すと SQL2947N です。実行中のジョブが無ければ INGEST LIST は SQL2982W を返します。
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 YESかNONRECOVERABLE、またはINGESTにする。 -
INGESTを初めて使う → 対象表のLOCKSIZEとSYSTOOLS.INGESTRESTARTの有無を先に確認する。
そのうえで手順に組み込むのは次の3点です。
-
ログ領域(
LOGFILSIZ×LOGPRIMARY+LOGSECOND)と投入量を突き合わせてから方式を決める - 投入後の確認は表スペース状態で見る。
LOAD QUERY TABLEとSELECT COUNT(*)はどちらも正常に見える - 投入中の他業務は無応答で詰まる。監視をエラーコードだけに頼らない
📕 もっと深く:Db2運用を12章で体系化した本を書きました
この記事で扱ったデータ投入の選び分けは、Db2運用のほんの一部です。
Db2はRDBMS市場でシェア数%。情報が少なく、頼れるのは膨大な公式マニュアルだけ——そんな現状を変えたくて、「なぜこの値にするのか」「この障害のとき次に何を見るのか」という、マニュアルに載っていない現場の文脈を1冊にしました。実機で検証した内容と、Db2の設計上そうなる理由をまとめました。
アーキテクチャ/構成パラメーター選定/セキュリティ/バックアップ・リカバリ/HADR/pureScale/日常点検の自動化/メモリ・ロック競合/SQLチューニング/RUNSTATS・REORG/MON_GET監視/現場の難題集まで、全12章+運用シェルスクリプト集です。
Db2の運用で踏んだ地雷や、扱ってほしいテーマがあれば、ぜひコメントで教えてください。次の記事のネタにさせてもらいます。
Discussion