こんにちは!日々の業務改善やAccessのツール開発、お疲れ様です。マクロの自動記録を卒業して「VBAでレコードを自在に操作したい!」と思い始めた頃、誰もが一度は直面する“怪奇現象”があります。
- 「コード上で `.Update` を呼んだはずなのに、別のPCから見るとデータが変わっていない…」
- 「更新直後にPCの電源が落ちたりAccessが異常終了したら、直前のデータが消えていた…」
「ちゃんと `.Update` って書いたのに、嘘つき!」と叫びたくなりますよね。でも安心してください。あなたの書いたVBAが間違っているのではなく、Accessの裏側で動いている「キャッシュ(一時メモリ)の仕組み」が原因なのです。
今回は、Access VBAの二大巨頭であるDAOとADOにおける `.Update` メソッドの本当の振る舞いと、メモリ上のデータを確実に物理ファイル(ディスク)へ書き込む「Flush(フラッシュ)」の極意を、基礎からわかりやすく解説します。
ここをクリアすれば、データ欠損や遅延に怯えることのない、プロ級の堅牢なAccessシステムが作れるようになりますよ!
—
1. そもそも `.Update` は「ディスクに直接保存」していない?
まずは、コードが実行されてからハードディスクやSSDに書き込まれるまでの「データの旅」をイメージしてみましょう。
多くの初心者の方は、こう考えています。
> 読者のイメージ:
> VBAコード(`.Update`) ➔ 即座にファイル(.accdb)に物理保存!
実は、これは違います。現実のアーキテクチャは次のようになっています。
【VBAコード】
│ .Edit や !フィールド = 値
▼
┌─────────────────────────────────┐
│ ① レコードセットのエディットバッファ │ (VBAのメモリ領域)
└─────────────────────────────────┘
│ .Update 実行!
▼
┌─────────────────────────────────┐
│ ② データベースエンジンのキャッシュ │ (ACE/Jet や OLE DB のメモリ)
└─────────────────────────────────┘
│
│ ★ ここに「時間差(タイムラグ)」がある!
▼
┌─────────────────────────────────┐
│ ③ 物理ディスク(.accdb ファイル) │ (SSD/HDDの実体)
└─────────────────────────────────┘
`.Update` メソッドが実際に行っているのは、「①の編集バッファから、②のエンジン用メモリキャッシュへデータを移動させること」に過ぎません。
なぜそんな回りくどいことをするのでしょうか?理由はシンプルで、「ディスクへの書き込みは遅いから」です。Accessのエンジンは、パフォーマンスを稼ぐために「ある程度データがたまるまで、またはOSが手の空いたタイミング」を見計らって、②から③へとまとめて書き込み(遅延書き込み)を行っています。
通常、Accessはこの書き込みを数秒(デフォルトでは約5秒程度)以内に行いますが、マルチユーザー環境や電源断などのトラブル時には、この「数秒のズレ」が致命的なデータ欠損を引き起こすのです。
—
2. DAOのUpdate:Access直結だからこその「Flush手法」
まずはAccess開発の本命であるDAO (Data Access Objects) から見ていきましょう。DAOはAccessのデータベースエンジン(ACE/Jet)と直結しているため、キャッシュの制御が非常にダイレクトに行えます。
基本的な書き方のおさらい
DAOでは、既存のレコードを変更する際に必ず `.Edit` を呼び、最後に `.Update` を呼びます。
rs.Edit
rs!CustomerName = “株式会社エース”
rs.Update
物理ディスクへ即座に書き込ませる「2つの秘技」
「この売上データだけは絶対に今すぐディスクへ確定させたい!」という重要な局面では、エンジンに対して「キャッシュを今すぐディスクに吐き出せ(Flushしろ)!」と命令します。
技①:`dbForceOSFlush` オプションを使う
DAOの `Update` メソッドには、実は隠された引数があります。それが `dbForceOSFlush` です。
‘ キャッシュを飛び越えて、OSに即時ディスク書き込みを要求する
rs.Update dbForceOSFlush
これをつけるだけで、エンジンはOSに対して「バッファをフラッシュしてディスクにコミットしなさい」と強力に要請します。
技②:`DBEngine.Idle dbRefreshCache` で共有遅延を防ぐ
社内LANなどの共有フォルダにあるバックエンドファイル(`.accdb`)を複数人で更新していると、「Aさんが登録したのに、Bさんの画面にすぐ出てこない」という読み取り側のタイムラグが発生します。
そんなときは、以下の1行を実行します。
‘ 読み取り側のエンジンキャッシュを強制クリアし、ディスクの最新状態を再読込する
DBEngine.Idle dbRefreshCache
—
3. ADOのUpdate:柔軟性と「カーソル」の深い関係
次は、SQL Server連携や外部システム連携で大活躍するADO (ActiveX Data Objects) です。ADOはDAOよりも汎用的に作られているため、データの滞留場所が「カーソルの場所(`CursorLocation`)」によってガラリと変わります。
クライアントカーソル(`adUseClient`)の罠
ADOでよく使われる `adUseClient`(クライアント側カーソル)を使っている場合、データは端末側のローカルメモリに強烈にキャッシュされます。
rs.CursorLocation = adUseClient
rs.Open “SELECT FROM T_Sales”, cn, adOpenStatic, adLockBatchOptimistic
rs!TotalAmount = 50000
rs.Update ‘ ← 注意!これは「クライアント内メモリ」の確定に過ぎない!
‘ サーバー(ファイル)に書き込むには、このメソッドが必要!
rs.UpdateBatch
- 単なる `.Update`:PCのメモリ上のレコードセットを更新しただけ。
- `.UpdateBatch`:蓄積した変更を一括してデータソース(データベース本体)へ送信する。
「Updateしたのにデータベースが変わっていない!」と悩む初学者の9割は、この `UpdateBatch` の呼び忘れが原因です。
—
4. コピペで使える!現場仕様の安全な実装パターン
それでは、実際の現場でそのまま使える「安全設計の実践コード」を見てみましょう。エラーが発生した際にバッファが中途半端に残らないよう、エラーハンドリングもしっかり組み込んであります。
【パターン1】DAO:絶対に失わせない高信頼書き込み
Public Sub SafeUpdate_DAO()
On Error GoTo ErrorHandler
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
‘ レコードセットをテーブルタイプ、またはダイナセットで開く
Set rs = db.OpenRecordset(“T_Order”, dbOpenDynaset)
If Not rs.EOF Then
rs.Edit
rs!OrderStatus = “完了”
rs!UpdateDate = Now()
‘ ★極意:dbForceOSFlushを指定して、物理ディスクへの書き込みを強制する
rs.Update dbForceOSFlush
Debug.Print “DAO: 物理ディスクへの書き込み要求が完了しました。”
End If
CleanUp:
‘ オブジェクトの解放処理
If Not rs Is Nothing Then
‘ 編集中にエラーが起きた場合のロック残存を防ぐ
rs.Close
Set rs = Nothing
End If
Set db = Nothing
Exit Sub
ErrorHandler:
‘ 編集モードのままエラーになった場合、バッファを破棄する
On Error Resume Next
If Not rs Is Nothing Then rs.CancelUpdate
MsgBox “更新中にエラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub
【パターン2】ADO:トランザクションとUpdateで整合性を保つ
Public Sub SafeUpdate_ADO()
On Error GoTo ErrorHandler
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
‘ 現在のAccessプロジェクトへの接続を取得
Set cn = CurrentProject.Connection
Set rs = New ADODB.Recordset
‘ トランザクションを開始(すべて成功するか、すべて失敗するかの境界線)
cn.BeginTrans
‘ サーバーサイドカーソルで即時反映を目指す
rs.CursorLocation = adUseServer
rs.Open “SELECT FROM T_Order WHERE OrderID = 101”, cn, adOpenKeyset, adLockOptimistic
If Not rs.EOF Then
rs!OrderStatus = “発送済”
rs.Update ‘ サーバー側へ更新を通知
End If
‘ ★極意:トランザクションをコミットして、データベース全体で確定させる
cn.CommitTrans
Debug.Print “ADO: トランザクションが正常にコミットされました。”
CleanUp:
If rs.State = adStateOpen Then rs.Close
Set rs = Nothing
Set cn = Nothing
Exit Sub
ErrorHandler:
‘ エラーが起きたら変更をすべて取り消し、元の状態に戻す(ロールバック)
On Error Resume Next
If Not rs Is Nothing Then
If rs.EditMode <> adEditNone Then rs.CancelUpdate
End If
If Not cn Is Nothing Then cn.RollbackTrans
MsgBox “ADO更新エラーのため、ロールバックしました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub
—
5. 陥りやすい2大トラブルと回避策
トラブル①:共有フォルダで「幽霊データ」が見える
現象:Aさんがデータを消したはずなのに、Bさんの画面にはまだ残っていて、クリックすると「レコードは削除されました」とエラーが出る。
原因:BさんのAccessが、古いキャッシュメモリを見に行っているためです。
解決策:データを再検索(Requery)する直前に、先ほど紹介した `DBEngine.Idle dbRefreshCache` を実行してください。これで画面とディスクの同期が一瞬で取れます。
トラブル②:エラー発生時にレコードがロックされたままになる
現象:VBAが途中で止まった後、そのテーブルを手動で開こうとすると「他のユーザーによってロックされています」と怒られる。
原因:`.Edit` を呼んだ後、エラーで中断したためにエディットバッファが開いたまま(編集中状態)になっているためです。
解決策:エラー処理ルーチン(`ErrorHandler:`)の中に、必ず `rs.CancelUpdate` を記述しましょう。「エラーが起きたら編集を取り下げる」という後片付けをコードに仕込むのが、一流のエンジニアの作法です。
—
6. まとめ:データの流れを意識すれば、Accessはもっと頼もしくなる!
今回の重要ポイントを振り返りましょう。
1. `.Update` はディスク書き込みではない:まずは高速なメモリ(キャッシュ)に蓄えられ、その後に物理ファイルへ書き出される。
2. DAOで即座に書き込みたいなら:`rs.Update dbForceOSFlush` を活用する。
3. 共有ネットワークの遅延解消には:`DBEngine.Idle dbRefreshCache` でキャッシュを更新する。
4. ADOのバッチ更新には:`.Update` だけでなく `.UpdateBatch` を忘れない。
5. エラー時は:必ず `.CancelUpdate` でメモリを安全に解放する。
「メモリとディスクの間にある壁」をイメージできるようになれば、もう理不尽なデータ不整合に頭を抱えることはありません。ここをクリアできれば、Access VBAの基本データ操作はバッチリです!
自信を持って、安全で高速な業務アプリケーションを構築していってくださいね。応援しています!
