DAOの極限:`dbConsistent` と `dbInconsistent` が握るマルチユーザー競合制御の深層
レガシーシステムの底流で未だに脈打つMicrosoft Access。その心臓部であるDAO(Data Access Objects)は、Jet/ACEデータベースエンジンと直結したネイティブなインターフェースとして、ADOやOLEDBすら凌駕する局所的な爆発力を秘めている。
りっぱなモダンアプリケーション全盛の今、なぜ今さらDAOなのか?
答えは単純だ。「Accessファイル(.accdb / .mdb)のローカルコンテキストにおいて、ミリ秒単位のトランザクション制御と排他制御を極限までチューニングできるのは、依然としてDAOのワークスペースとレコードセットだけだからだ」である。
今回は、マルチユーザー環境における最大の悪夢――「更新競合(Update Conflict)」を制御するための、DAOの隠された核心機能である `dbConsistent` と `dbInconsistent` の挙動の違いと、そのアーキテクチャの深層を解き明かす。
—
1. なぜ「更新競合」は起きるのか? Jetエンジンの楽観的同時実行制御
DAOのレコードセット(特に `dbOpenDynaset`)を介したデータ更新は、楽観的同時実行制御(Optimistic Concurrency Control)をベースにしている。
ユーザーAとユーザーBが同時に同一レコードを読み込んだとする。
1. ユーザーAが値を変更し、`Update` メソッドを実行。
2. ユーザーBも値を変更し、`Update` メソッドを実行。
この時、Jetエンジンは「本当にこのレコードは変更されていないか?」を検証する。もしユーザーBが更新を試みた時点で、ストレージ上のデータがユーザーBの読み込み時(または直前のキャッシュ時)から変化していれば、ランタイムエラー(通常は実行時エラー 3197 または 3260)が発火する。
ここで問われるのが、「何を基準に矛盾(Inconsistency)と判定するか」という定義だ。その判定基準のスイッチを握っているのが、`Update` メソッドの引数、あるいはレコードセットオープン時のオプションである `dbConsistent` と `dbInconsistent` である。
—
2. アーキテクチャの核心:`dbConsistent` vs `dbInconsistent`
ダイナセット(Dynaset)は、複数のテーブルを結合(JOIN)したクエリ結果に対しても構築できる。ここに落とし穴がある。結合クエリに対する更新は、しばしば「一側のテーブル(1対多の「1」側)」と「多側のテーブル(「多」側)」を同時に巻き込む。
`dbConsistent` (デフォルト・厳格な整合性)
- 挙動: 更新は、定義された結合条件の整合性が保たれる範囲に厳しく制限される。
- 詳細: 「多」側のレコードを更新する際、それが参照している「1」側のレコードが他のユーザーによって削除されている、あるいは結合キーが変更されて参照整合性が崩れる場合、更新は即座に拒絶される。データの論理的整合性を最優先するモード。
`dbInconsistent` (緩やかな整合性・強制更新)
- 挙動: 結合条件の整合性チェックを一部バイパスし、更新を強行する。
- 詳細: 例えば、多側の子レコードを更新する際、親側のレコードに微細な変更(他の列の変更など)が他ユーザーによって加えられていても、プライマリキーや結合キー自体が破壊されていない限り、更新を受け入れる。
> チーフアーキテクトの警告:
> `dbInconsistent` は諸刃の剣である。デッドロックや意図しない孤児レコード(Orphan Record)を生み出す温床となり得るため、業務ロジックの構造を完全に把握していない限り、安易に触れてはならない。しかし、バッチ処理や、マスターデータの緩やかな参照を持つトランザクション処理においては、競合エラーによる処理中断を防ぐための唯一の防壁となる。
—
3. 実装コード:競合制御をハンドリングする堅牢なVBAパターン
現場でそのまま使える、エラーハンドリングと明示的なオブジェクト解放を網羅したコードを示す。DAOのメモリリークを防ぐため、Workspace、Database、Recordsetは必ず逆順で破棄する鉄則を遵守している。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ ódulo名: basDaoConcurrencyControl
‘ 概要 : dbConsistent と dbInconsistent を用いたマルチユーザー更新制御
‘ =========================================================================
Public Sub ExecuteRobustUpdate()
Dim wrkDefault As DAO.Workspace
Dim dbCurrent As DAO.Database
Dim rstTarget As DAO.Recordset
Dim strSQL As String
Dim lngRetryCount As Long
Const MAX_RETRIES As Long = 3
‘ ワークスペースの取得(既定のワークスペース)
Set wrkDefault = DBEngine.Workspaces(0)
‘ データベースオブジェクトの参照
Set dbCurrent = CurrentDb()
‘ 1対多の結合クエリを想定したSQL(例: 注文と注文明細)
strSQL = “SELECT T_Orders.OrderID, T_Orders.OrderDate, T_OrderDetails.Quantity, T_OrderDetails.UnitPrice ” & _
“FROM T_Orders INNER JOIN T_OrderDetails ON T_Orders.OrderID = T_OrderDetails.OrderID ” & _
“WHERE T_Orders.OrderID = 10001;”
On Error GoTo ErrorHandler
‘ トランザクションの開始
wrkDefault.BeginTrans
‘ ダイナセットとしてオープン(dbInconsistentを指定して競合許容度を調整する場合)
‘ ※通常の業務アプリでは dbConsistent(省略時デフォルト)を推奨
Set rstTarget = dbCurrent.OpenRecordset(strSQL, dbOpenDynaset, dbSeeChanges)
If Not rstTarget.BOF And Not rstTarget.EOF Then
rstTarget.Edit
‘ データの書き換え
rstTarget!Quantity = rstTarget!Quantity + 10
‘ 【重要】Updateメソッドにおける競合制御の適用
‘ dbInconsistent を指定することで、他ユーザーによる親テーブルの非破壊的な変更を無視して強制書込を行う
rstTarget.Update dbInconsistent
End If
‘ トランザクションの確定
wrkDefault.CommitTrans
MsgBox “更新が正常に完了しました。”, vbInformation, “DAOアーキテクチャ”
CleanUp:
‘ ———————————————————————
‘ メモリ最適化とオブジェクトの明示的解放(リソースリークの完全阻止)
‘ ———————————————————————
On Error Resume Next
If Not rstTarget Is Nothing Then
rstTarget.Close
Set rstTarget = Nothing
End If
Set dbCurrent = Nothing
Set wrkDefault = Nothing
Exit Sub
ErrorHandler:
‘ ランタイムエラー 3197 (他のユーザーがデータを変更したため…) などの捕捉
If Err.Number = 3197 Or Err.Number = 3260 Then
lngRetryCount = lngRetryCount + 1
If lngRetryCount <= MAX_RETRIES Then
' 楽観的ロック競合時のリトライロジック
wrkDefault.Rollback
rstTarget.Requery ' キャッシュを破棄して最新データを再取得
App.Wait Now + TimeValue("00:00:01") ' 1秒待機して再トライ
Resume
End If
End If
' その他の致命的エラー
wrkDefault.Rollback
MsgBox "予期せぬエラーが発生しました: " & Err.Description & " (Error #" & Err.Number & ")", vbCritical, "致命的エラー"
Resume CleanUp
End Sub
---
4. プロフェッショナルの知見:APIとメモリ管理の裏側
大規模なAccessシステムにおいて、DAOを使う際に絶対に見落してはならないのが `dbSeeChanges` フラグとの組み合わせだ。
SQL Server等のODBCデータソースをリンクテーブルとしてDAOで操作する場合、他ユーザーが変更した内容は `dbSeeChanges` を付与していないと、ランタイムエラーとして即座に弾かれる。`dbConsistent` / `dbInconsistent` は、この `dbSeeChanges` と組み合わさることで初めて、真のC/S(クライアントサーバー)環境における強靭な排他制御を発揮する。
また、VBAのガベージコレクションは非常に気まぐれである。特に長時間のバッチ処理やループ内で `CurrentDb` を乱用すると、内部のJetエンジンセッションが肥大化し、メモリリークや「リソース不足」エラーを引き起こす。
常に一連のプロセスでは `Workspace` を明示し、オブジェクト変数を `Nothing` でクリアする作法を徹底してほしい。
結言
DAOはレガシーではない。それは「ローカルデータ処理における究極の低レイヤーインターフェース」である。
`dbConsistent` と `dbInconsistent` の挙動の差を正確に理解し、マルチユーザーの荒波の中でもデータの整合性と可用性をコントロールし切ること――それこそが、真に信頼されるシステムを支えるエンジニアの矜持である。
