仕事で、ERP Cloudの在庫情報を別サーバに移行するアドオンの開発をしてるのだけども。移行先で在庫数量を保持するテーブルは2つあって、日別に倉庫/商品ごとのデータを蓄積して、いわゆるASOF(時点)データを参照できる奴と、日中の取引情報が随時反映されてくる奴。後者は今日1日のデータしか持たない。
実現方法を簡略化して示すとこんな感じ。
上の図でいうバッチ処理Aが、夜間実行でその時点の在庫数量をさっきの2つのテーブルへ一度に登録してる。ただし当然ながら、この時リアルタイムのテーブルの方は洗い替えになる。なんのこっちゃなく、一旦全レコードを削除してから登録するだけの話なんだけど。
初めに設計書を書いた時、俺はDELETE文を使っていた。大雑把な構成で示すとこう。
INSERT INTO 日別在庫情報テーブル(...) VALUES...; DELETE FROM リアルタイム在庫情報テーブル(...); INSERT INTO リアルタイム在庫情報テーブル(...) VALUES...; COMMIT;
これをあるマネージャーにレビューしてもらったところ、「DELETEじゃなくてとらんけーとにしてほしいな」と言われた。
「ハイ!!!承知しました!!(と、とらんけー・・・?)」
と元気に返事をした後で、さっそくGoogle先生に泣きついた俺。
TRUNCATE または TRUNCATE TABLE ステートメントは、テーブルから全ての行を削除するSQLである。データベースが持つ整合性を維持する機構をスキップすることで高速な削除を実現している場合が多い。削除する行それぞれを記録するためのトランザクションログの出力を避けることで、効率的に全ての行を削除できる。
ウィキペディア
TRUNCATE…そういうのもあるのか。
なるほど、全件削除するならTRUNCATEの方がいいのか。確かに、リアルタイムのテーブルに保持されているデータは、1日分のみと言え、その量はざっと企業が扱う製品の全SKU数×全在庫組織数・・・。処理パフォーマンスのことも考えなきゃいかんわけだな。
と、TRUNCATEを全件DELETEの上位互換みたいに認識した俺は単純にこう修正した。
INSERT INTO 日別在庫情報テーブル(...) VALUES...; TRUNCATE リアルタイム在庫情報テーブル(...); INSERT INTO リアルタイム在庫情報テーブル(...) VALUES...; COMMIT;
そしたら今日、また別のマネージャーにレビューしてもらったところ、「ここ、TRUNCATEでいいの?」と言われた。
「ハイ!!??(ど、ど、どういうことだってばよ!?)」
曰く、「TRUNCATEって自動COMMITだったよね?万が一システムエラーで最終COMMIT前に落ちた時、日別在庫だけ更新されて、整合性合わなくなるんじゃない?」
自動こみっと?そうなの?ぐ、Google先生〜〜〜!!
TRUNCATEはDDL文であるため、
Oracle TimesTen In-Memory Database SQLリファレンス・ガイドDDLCommitBehavior
属性によって制御されます。DDLCommitBehavior
=0(デフォルト)の場合、TRUNCATE文の実行前と実行後にコミットが実施されます。
ほんまや・・・!
ちなみに、COMMITっていうのは、テーブルに対する編集を「確定」させる文のこと。INSERTやDELETE文でテーブルをいじっても、COMMITを宣言する前なら操作は取り消せる。バッチ処理の設計だと、一連の処理の最後までエラーがなかったことを確認してから一括でCOMMITするのが多い。今日のマネージャーが言ってるようなことが起こるのを防ぐために。
つまり、先輩の指摘は
INSERT INTO 日別在庫情報テーブル(...) VALUES...; (COMMIT;) //日別在庫情報への登録が確定する TRUNCATE リアルタイム在庫情報テーブル(...); (COMMIT;) //リアルタイム在庫情報の全件削除が確定する INSERT INTO リアルタイム在庫情報テーブル(...) VALUES...; (何らかのエラー発生→例外処理でのRollbackにより、前回Commitからの操作が取り消される) //結果・・・日別在庫情報へはデータ登録されているが、リアルタイム在庫情報は空っぽの状態
と、いうこと。
TRUNCATEを単に全件DELETEの上位互換と考えるのは少し危ない。そもそも、なぜTRUNCATEがDELETEより効率が良いかと言えば、通常DELETE操作が行なっているような、トランザクションログ出力などを省いているからだ。だから基本的にはTruncateしてしまうとRollback(取り消し)できないし、その時点までのトランザクションがCommitされてしまったりする制約があるみたい。
今回の機能の場合、リアルタイム在庫情報の全件削除がCommitされるのは大して問題じゃないが(夜間バッチが実行されてる時点で前日の情報はもういらないから)、日別在庫情報にだけデータが登録されている状態っていうのはあまりよろしくない。
じゃあ、やっぱりDELETE文に戻さなきゃないのか・・・?
と、諦めかけた時、マネージャーが「TRUNCATE使うならこうだね」、と
TRUNCATE リアルタイム在庫情報テーブル(...); INSERT INTO リアルタイム在庫情報テーブル(...) VALUES...; INSERT INTO 日別在庫情報テーブル(...) VALUES...; COMMIT;
あ・・・そっか・・・順番変えればいいのか。
確かにこれならリアルタイムの方が空っぽのままエラー終了することはあれど、少なくとも日別在庫にだけデータが入るってことはない。ほぇ〜〜。
たまにこんなアハ体験があるから、プログラミングは面白い。いや、俺の頭が固いだけか?