【入門編】【上級】DAO.Recordsetの「BatchUpdate」を自作する:トランザクション管理による一括更新の高速化 – Access VBA解析バイブル

スポンサーリンク

こんにちは! Access VBAの海原を航海する皆さん、チーフアーキテクトの私です。

日々の業務システム開発で、こんな絶望感を味わったことはありませんか?
「数千件のデータをループで一件ずつ更新していたら、画面が固まってコーヒーを3杯飲んでも終わらない……」

もしあなたが、レコードを1行処理するたびに `Rst.Edit` と `Rst.Update` をフツーに繰り返しているなら、それはAccess(Jet/ACEエンジン)に対して「おい、毎回ディスクに書きに行ってくれよ!」と無駄なパシリを何千回も頼んでいるようなものです。そりゃあ遅くなります。

今回は、Accessが持つ本来のポテンシャルを極限まで引き出し、数千・数万件のデータを一瞬で飲み込む「トランザクション制御によるバッチ更新の自作」という奥義を伝授しましょう。ここをクリアすれば、あなたの書くコードは「ただ動くマクロ」から「プロのエンジニアが描く高速システム」へと劇的に進化します。

なぜ「一件ずつ更新」は遅いのか?(Accessの裏側を知る)

まず、敵を知ることから始めましょう。

通常、DAOのRecordsetを使ってデータを更新する場合、コードはこう書きますよね。

‘ 【アンチパターンの例】一件ずつディスクI/Oが発生する
Do Until Rst.EOF
Rst.Edit
Rst!Status = “完了”
Rst.Update ‘ ←ここで毎回ディスク書き込みが発生!
Rst.MoveNext
Loop

データベースエンジンにとって一番重い処理、それは「物理ディスクへの書き込み(I/O)」です。上記のコードでは、レコードが1,000件あれば、ディスクへの書き込み命令が1,000回発生します。これではOSもハードディスク(またはSSD)も悲鳴を上げてしまいます。

解決策:トランザクションで「まとめて」コミットする

ここで登場するのがトランザクション(Transaction)です。
トランザクションとは、一連の処理を「すべて成功させるか、すべてなかったことにする(ロールバック)」の単位として束ねる仕組みです。

実は、トランザクションの傘下に入ると、Accessは賢く立ち回ります。変更データをメモリ上で巧みに管理し、最後の `CommitTrans` が呼ばれるまで物理的なディスク書き込みを極限まで遅延(バッファリング)させてくれるのです。

つまり、「トランザクションの開始 + ループ内での高速更新 + 一括コミット」を組み合わせることで、擬似的な「BatchUpdate」を自作できるというわけです。

実践!超高速バッチ更新コードの全貌

それでは、実際の現場でそのままコピペして使える、堅牢かつ爆速なプロシージャを公開します。今回は「未処理の受注データ(数千件)」を一括で「処理済み」に書き換えるシナリオを想定しています。

‘ ==============================================================================
‘ 処理名 : BulkUpdateOrders
‘ 概要 : トランザクション制御により、大量レコードを一括高速更新する
‘ 著者 : チーフアーキテクト
‘ ==============================================================================
Public Sub BulkUpdateOrders()
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim startTime As Double
Dim updateCount As Long

‘ 処理時間計測用
startTime = Timer

‘ CurrentDbをオブジェクト変数に格納(※DAOの基本:毎回CurrentDbを叩かない!)
Set db = CurrentDb

‘ 1. 対象レコードを読み取り専用ではなく、更新可能なモードで開く
‘ 速度を優先するため、必要最低限のフィールドだけを絞り込むのがプロの技
Set rst = db.OpenRecordset(“SELECT OrderID, Status, UpdatedDate FROM T_Orders WHERE Status = ‘未処理'”, dbOpenDynaset)

‘ 対象データがなければ即終了
If rst.EOF Then
MsgBox “更新対象のデータはありません。”, vbInformation, “お知らせ”
GoTo CleanUp
nisEnd If

‘ 2. 【最重要】トランザクションの開始
‘ この宣言により、Jet/ACEエンジンは書き込みをメモリ上でキャッシュします
db.BeginTrans
On Error GoTo ErrorHandler ‘ エラー時のロールバックを必ずセット

updateCount = 0

‘ 3. 高速ループ処理
Do Until rst.EOF
rst.Edit
rst!Status = “処理済み”
rst!UpdatedDate = Now()
rst.Update

updateCount = updateCount + 1
rst.MoveNext
Loop

‘ 4. トランザクションの確定(Commit)
‘ ここで初めてメモリ上の変更がデータベースに一気に書き込まれます
db.CommitTrans

‘ 処理成功のログ
MsgBox “バッチ更新が完了しました!” & vbCrLf & _
“処理件数: ” & updateCount & ” 件” & vbCrLf & _
“所要時間: ” & Format(Timer – Timer, “0.00”) & ” 秒”, _
vbInformation, “爆速完了”

CleanUp:
‘ 5. オブジェクトの解放(メモリリークの防止)
If Not rst Is Nothing Then
rst.Close
Set rst = Nothing
End If
Set db = Nothing
Exit Sub

ErrorHandler:
‘ 6. 予期せぬエラーが発生した場合は「なかったこと」にする
db.Rollback
MsgBox “エラーが発生したため、更新をすべて取り消しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“内容: ” & Err.Description, vbCritical, “致命的なエラー”
Resume CleanUp
End Sub

コードに宿る「プロのこだわり」ポイント解説

上記のコードには、ただ動くだけではない、Accessエンジニアとしての「知見」が随所にちりばめられています。初心者から一歩抜け出すために、重要なポイントを解説しておきますね。

1. `CurrentDb` は変数に受ける

よくある初心者のミスが、ループの条件やあちこちで何回も `CurrentDb.OpenRecordset…` と書くことです。`CurrentDb` を呼び出すたびに、Accessは内部でデータベースへの接続オブジェクトを新しく生成し、破棄するという重い処理を行っています。最初に `Set db = CurrentDb` と変数に入れて使い回すだけで、パフォーマンスが目に見えて向上します。

2. エラーハンドリングと `Rollback` のセット

トランザクションを使うときの絶対の鉄則がこれです。
`BeginTrans` を使ったら、必ず `On Error GoTo` を用意し、エラー時には `Rollback`(変更の破棄)を行ってください。これをサボると、途中でエラーが起きたときにデータベースの中途半端なデータだけが残り、データ破損(ゴミデータの残留)の原因になります。

3. オブジェクトの確実な解放

処理が終わったら `rst.Close` し、変数に `Nothing` を代入する。これを怠ると、Accessのメモリ内にゴミが残り続け、アプリケーション全体の動作が重くなる原因(メモリリーク)になります。プロは後片付けまで美しく行います。

さらに上の高みへ:SQLの「一括更新(UPDATE文)」という選択肢

ここまで「DAOのRecordsetによるバッチ更新」を解説してきましたが、実はさらに速い究極の方法があります。それは、VBAからSQLの `UPDATE` ステートメントを直接実行することです。

Public Sub BulkUpdateBySQL()
Dim sql As String

‘ 条件に合うレコードを一発で書き換えるSQL
sql = “UPDATE T_Orders SET Status = ‘処理済み’, UpdatedDate = Now() WHERE Status = ‘未処理’;”

‘ トランザクションすら不要なほどの爆速
CurrentDb.Execute sql, dbFailOnError

MsgBox “SQLによる一括更新が完了しました!”
End Sub

「なんだよ、最初からSQLを教えろよ!」と思いましたか?(笑)
確かに、単純な値の置き換えだけであれば、SQL(`CurrentDb.Execute`)の方が圧倒的に速く、コードもシンプルです。

しかし、実際の業務アプリケーションでは、「更新する直前に複雑な計算を行いたい」「VBAのカスタム関数や外部の値を判断材料に組み込みたい」といった、レコードごとにロジックを挟む必要のあるケースが多々あります。そういった場面で、今回紹介した「トランザクション制御によるRecordsetのループ処理(自作BatchUpdate)」が最強の武器になるのです。

まとめ

いかがでしたでしょうか?
ここをクリアすれば、あなたはもう「Accessの速度に悩まされる初心者」ではありません。

1. 一件ずとの更新はディスクI/Oの嵐になり遅い
2. `BeginTrans` と `CommitTrans` で囲むことで、書き込みをまとめられる
3. エラー時には必ず `Rollback` でデータを守る
4. シンプルな一括更新ならSQL(`Execute`)も検討する

この仕組みをマスターすれば、数万件規模のデータ処理も余裕でこなせるようになります。ぜひ、あなたの開発するシステムに組み込んで、その圧倒的なスピードを体感してみてください。

それでは、次の開発現場でお会いしましょう!バッチリ決めていきましょう!

タイトルとURLをコピーしました