【上級】DAO.Recordsetの「BatchUpdate」を自作する:トランザクション管理による一括更新の高速化
開発現場でよく見かける光景がある。
数千件、数万件のレコードをループで回し、1件ごとに `Update` メソッドを叩くコード。そして「Accessは遅い」と嘆くエンジニアたち。
断言しよう。遅いのはAccessではなく、あなたの書き方だ。
ADOには `BatchUpdate` という強力な一括更新機能が存在するが、我らがAccessのネイティブエンジンであるDAOの `Recordset` には、残念ながらそのまま使える「BatchUpdate」というメソッドはない。しかし、DAOのトランザクション制御(`BeginTrans` / `CommitTrans`)を正しくアーキテクチャに組み込むことで、擬似的に、かつ圧倒的な速度を誇る「バッチ更新」を自作することは容易に可能だ。
今回は、実務の現場で即座に使える、堅牢かつ極限までチューニングされたトランザクション管理による一括更新の実装パターンを伝授する。
—
1. なぜ「1件ごとの更新」は地獄のように遅いのか?
データベースの更新処理において最大のボトルネックは、CPUの演算速度でもメモリの容量でもない。「ディスク(あるいはネットワーク)へのI/O」と「トランザクションログの書き込み」だ。
デフォルトの状態(トランザクションを明示的に制御していない状態)でレコードセットをループさせると、Access(Jet/ACEエンジン)は次のような処理を暗黙的に行っている。
1. 1件書き換える (`Edit` -> 値の代入 -> `Update`)
2. その瞬間にトランザクションログをストレージにフラッシュする
3. インデックスを更新する
これが10,000件あれば、10,000回ディスクへの書き込みが発生する。ストレージがSSDであっても、OSやファイルのロック制御、MDB/ACCDBのページ構造のオーバーヘッドにより、処理は数秒〜数十秒、ネットワーク経由の共有ファイルであれば数分単位で時間を食う。
解決策:トランザクションのスコープをコントロールする
これを解決するのがトランザクションの明示的管理だ。
処理の開始前に `BeginTrans` を宣言し、数千件の書き込みをすべてメモリ上のキャッシュ(ワークスペース)で完結させ、最後に `CommitTrans` で一気にストレージへ流し込む。これにより、ディスクI/Oの回数を劇的に削減し、データベースのパフォーマンスを理論値の限界まで引き上げることが可能になる。
—
2. 実務で耐えうる「自作BatchUpdate」のアーキテクチャ
単に `BeginTrans` で囲むだけでは、プロのコードとは言えない。実務の現場では以下の要件を満たす必要がある。
- 例外処理(Error Handling)の徹底: 途中でエラーが発生した場合、必ず `Rollback` し、データベースの整合性を担保する。
- 画面描画のロック: `Echo False` を併用し、余計な描画コストを削る。
- トランザクションの適切なスコープ: `CurrentWorkspace` を使用し、意図しないネストを防ぐ。
それでは、プロダクション品質のコードを提示する。
—
3. プロダクションコード:高速一括更新モジュール
以下のコードは、大量のマスターデータやトランザクションデータの数値を一括で更新(例:全レコードの単価を10%アップ)するシナリオを想定したサブプロシージャだ。
Option Compare Database
Option Explicit
Public Sub ExecuteBatchUpdate()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim lngCount As Long
Dim startTime As Double
startTime = Timer
‘ 画面描画と警告を停止し、処理速度を極限まで高める
Application.Echo False
DoCmd.SetWarnings False
‘ ワークスペースの参照を明示的に取得(トランザクション制御の要)
Dim ws As DAO.Workspace
Set ws = DBEngine.Workspaces(0)
Set db = CurrentDb
On Error GoTo ErrorHandler
‘ 更新対象のレコードセットを開く(パフォーマンスの良い TableType または Dynaset)
‘ ※大量処理では余計なロックを避けるため、dbOptimistic または dbDenyWrite 等を適切に選択
Set rs = db.OpenRecordset(“T_Sales_Detail”, dbOpenDynaset)
If rs.RecordCount = 0 Then
MsgBox “更新対象のレコードが存在しません。”, vbInformation, “情報”
GoTo Finally
End If
‘ ==========================================
‘ トランザクションの開始(ここから一括バッチモード)
‘ ==========================================
ws.BeginTrans
lngCount = 0
Do Until rs.EOF
‘ 編集モードへ移行
rs.Edit
‘ — ビジネスロジック(例:単価を1.1倍に切り上げ) —
‘ ※実際には引数で渡されたデータテーブルや配列から値をバインドする
If Not IsNull(rs!UnitPrice) Then
rs!UnitPrice = Round(rs!UnitPrice 1.1, 0)
End If
‘ 更新を確定(この時点ではメモリ上のトランザクションログに積まれるだけ)
rs.Update
lngCount = lngCount + 1
rs.MoveNext
Loop
‘ ==========================================
‘ コミット(一括書き込みの実行)
‘ ==========================================
ws.CommitTrans
MsgBox “一括更新が完了しました。” & vbCrLf & _
“処理件数: ” & Format(lngCount, “#,
0″) & ” 件” & vbCrLf & _
“処理時間: ” & Format(Timer – startTime, “0.00”) & ” 秒”, _
vbInformation, “高速バッチ処理完了”
Finally:
‘ クリーニング処理
On Error Resume Next
If Not rs Is Nothing Then rs.Close: Set rs = Nothing
Set db = Nothing
‘ 環境を元に戻す
Application.Echo True
DoCmd.SetWarnings True
Exit Sub
ErrorHandler:
‘ エラー時はロールバックして変更を完全に破棄
ws.Rollback
MsgBox “予期せぬエラーが発生したため、変更をすべてロールバックしました。” & vbCrLf & _
“Error 発生位置: ExecuteBatchUpdate” & vbCrLf & _
“Error Description: ” & Err.Description, _
vbCritical, “致命的なエラー”
Resume Finally
End Sub
—
4. チーフアーキテクトからの実践的な注意点・罠
この手法は劇的な効果を生むが、Access VBAの特性上、以下の「罠」に注意しなければシステム障害の原因となる。
① トランザクションの「重さ」とロック競合
トランザクションのスコープが長ければ長いほど、データベースファイル(.accdb)内のページロックやレコードロックが維持される時間が長くなる。
数万件程度であれば一括コミットで問題ないが、数十万件を超えるような規模の場合は、適度な件数(例:5,000件ごと)で一度 `CommitTrans` し、再度 `BeginTrans` を張る「チャンク(分割)処理」を設計に組み込むべきだ。これを行わないと、ファイルサイズが一時的に膨れ上がり、他のユーザーのアクセスを完全にブロック(フリーズ状態)させてしまう。
② エラーハンドリングにおける `ws.Rollback` の絶対性
トランザクション中にエラーが発生した場合、`ws.Rollback` を忘れると、ワークスペースが中途半端な状態を保持し続け、最悪の場合データベースファイルが破損する(修復が必要になる)。
必ず `On Error GoTo ErrorHandler` を経由させ、エラーハンドラ内で確実にロールバックが実行される構造を担保すること。
③ ネットワーク環境(共有フォルダ)での運用
バックエンドファイル(データ側)をファイルサーバーに置き、フロントエンドからリンクテーブル経由でこの処理を叩く場合、トランザクションのオーバーヘッドがネットワークの帯域やレイテンシに依存するようになる。
もしパフォーマンスが出ない場合は、ローカルのテンポラリDBにデータを一度インポートして一括処理を行い、最後にサーバーへ同期させるアーキテクチャの変更も視野に入れるべきだ。
—
総括
DAOのRecordset操作において、トランザクションの制御は「上級者への登竜門」ではなく、実務でまともなパフォーマンスを出すための「必須教養」である。
一件ずつ愚直に `Update` を呼ぶコードを書く時代は終わった。
データベースの挙動とトランザクションのライフサイクルを完全に掌握し、圧倒的な速さと堅牢性を兼ね備えた業務システムを構築してほしい。
