業務で大量更新処理の速度改善を行ったので、大切な点を残しておきます。
プロジェクトを離れた前任者の一括処理が、20時間以上経っても終わらないという状況で相談を受けました。
結果としては複雑なチューニングよりも、業務要件の洗い出しと、基本的なSQLの組み方が最も効果的でした。
状況調査
まず、処理内容とSQL に登場するテーブル件数を確認しました。
一括処理は、1億レコードのテーブルを、その他のテーブルから集めた情報で更新するというものでした。
1億件ものレコードを全件洗い替えするため、20時間要していました。
SQL のイメージはこちらです。
-- 1億レコードを全件更新する処理(改善前)
UPDATE heavy_table t
INNER JOIN (
SELECT ...
) info ON ...
SET t.column = info.value;対象レコードを業務要件から絞り込み、大幅高速化
業務要件を確認したところ、全件の洗い替えではなく、その日に更新が入ったデータだけを洗い替えれば十分だと分かりました。
そのため、本当に更新が必要な対象は、1億ではなく数千件程度でした。
SQL のwhere 句で更新対象を絞り込むことで、約10分の1まで短縮されました。
この時点で、20時間かかっていた処理が2時間まで短縮されました。
-- 1億レコードのうち、本日更新分だけに絞り込む(約10分の1に短縮)
UPDATE heavy_table t
INNER JOIN (
SELECT ...
) i ON ...
SET t.column = i.value
WHERE t.id IN (
SELECT id
FROM other_table ...
);update 文でサブクエリを使わない
その日に更新が入ったデータの抽出をサブクエリで行っていたのですが、update 文とサブクエリを同時に使うよりも、サブクエリ結果を一時テーブルに入れてからupdate を行った方が速いことが分かりました。
私にはデータベース内部の挙動を正確に説明できないのですが、update 文にサブクエリを含めるとデータベースに適切な最適化を難しくさせたり、不必要なロックが発生するようです。
こちらの対策を行うことで、処理時間が30分になりました。
-- 本日分のIDを事前に一時テーブルへ投入(サブクエリの事前計算)
INSERT INTO tmp_id
SELECT id
FROM other_table ...;
-- 一時テーブルと結合して高速に更新(最終的に30分まで短縮)
UPDATE heavy_table t
INNER JOIN (
SELECT ...
) i ON ...
INNER JOIN tmp_id tmp ON t.id = tmp.id
SET t.column = i.value;まとめ
以前、SQLチューニングについて、先輩から教わったことを思い出しました。
「業務影響がある対策が一番効果があり、業務影響がない対策では大きな改善は難しい。」
今回は、はじめに業務要件を確認したのが良かったと思います。
チームでは並行して、ヒント句を入れたりクラウドなのでRDB のスケールアップを試みたりしましたが、おそらくそれらだけでは、1億件の更新を30分で終わらせることはできなかったと思います。
改めて先輩の知見に感謝です。





