こんにちは! 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`)も検討する
この仕組みをマスターすれば、数万件規模のデータ処理も余裕でこなせるようになります。ぜひ、あなたの開発するシステムに組み込んで、その圧倒的なスピードを体感してみてください。
それでは、次の開発現場でお会いしましょう!バッチリ決めていきましょう!
