こんにちは! Access VBAの世界へようこそ。
今回は、多くの開発者が頭を悩ませる「マルチユーザー環境でのテーブル定義変更に伴う排他制御の極意」についてお話しします。
「自分ひとりでテストしている時は完璧に動くのに、実際の現場(複数人が同時に使っている環境)で動かしたらエラーが出る…」
そんな経験はありませんか?
ここをクリアできれば、あなたも「マクロの記録」や「見よう見まねのコード」を卒業し、現場から絶賛されるプロのAccess開発者への道を大きく駆け上がることができます。
優しく、そして本質的な部分までしっかりと解説していきますので、リラックスしてついてきてくださいね!
—
1. なぜマルチユーザー環境でのテーブル変更は「事故」が起きやすいのか?
まず、Accessの心臓部である「データベースエンジン(ACE / Jet)」がどう動いているかを知る必要があります。
私たちがVBAで `TableDef` オブジェクトを使ってテーブルに新しいフィールドを追加したり、データ型を変更したりする時、Accessはテーブル全体を「排他ロック(Exclusive Lock)」しようとします。
ここで想像してみてください。
営業部のAさんが顧客マスタを開いて入力作業をしているまさにその瞬間、あなたがバックグラウンドでVBAを走らせて「このテーブルに新しい列を追加するぞ!」と命令したらどうなるでしょうか?
- Accessの心の中: 「ちょっと待って! Aさんがこのテーブルを触ってる最中だから、安全のために変更は許可できないよ!」
- 結果: `実行時エラー ‘3211’: データベース オブジェクト ‘XXX’ をロックできません。` という悲しいエラーが発生します。
これが、マルチユーザー環境でテーブル定義変更が嫌われる理由です。ここをスマートにいなすのがプロの技というわけです。
—
2. プロが実践する「3つの鉄則」
実務で安全にテーブル定義を変更するためには、以下の3つのステップをコードに組み込む必要があります。
1. エラーハンドリングの徹底(転んでもただでは起きない)
2. 接続ユーザ・リソースの競合検知とリトライ制御
3. トランザクションと排他制御のスコープ管理
これらをすべて盛り込んだ「実戦でそのまま使える最高峰のコード」を次章で公開します。
—
3. 【実践コード】安全にフィールドを追加する堅牢なプロシージャ
以下のコードは、マルチユーザー環境で他のユーザーがテーブルを開いていても、衝突を避けて安全にフィールドを追加(または存在確認をしてスキップ)するプロシージャです。
ご自身の開発環境の標準モジュールにコピーして、仕組みを味わってみてください。
Option Compare Database
Option Explicit
‘ =================================================================
‘ 担当:チーフアーキテクト
‘ 概要:マルチユーザー環境を考慮した安全なテーブル定義変更(フィールド追加)
‘ =================================================================
Public Sub SafeAddColumnToTable(ByVal targetTableName As String, ByVal targetFieldName As String)
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
Dim fldExists As Boolean
Dim retryCount As Integer
Const MAX_RETRIES As Integer = 3
Const RETRY_WAIT_SEC As Long = 2 ‘ リトライまでの待機秒数
‘ エラーハンドラーの設定
On Error GoTo ErrorHandler
‘ 現在のカレントデータベース参照を取得
Set db = CurrentDb()
‘ — 【ステップ1】排他制御を考慮したテーブルオブジェクトの取得 —
‘ 他のユーザーが排他ロックしている場合、ここでエラー3211等が発生する可能性があります。
retryCount = 0
TryOpenTableDef:
On Error GoTo LockErrorHandler
Set tdf = db.TableDefs(targetTableName)
On Error GoTo ErrorHandler ‘ エラー監視を通常に戻す
‘ — 【ステップ2】既存フィールドの重複チェック —
fldExists = False
For Each fld in tdf.Fields
If StrComp(fld.Name, targetFieldName, vbTextCompare) = 0 Then
fldExists = True
Exit For
End If
Next fld
‘ すでにフィールドが存在する場合は処理を安全に抜ける
If fldExists Then
MsgBox “指定されたフィールド ‘” & targetFieldName & “‘ は既に存在します。”, vbInformation, “スキップ”
GoTo CleanUp
End If
‘ — 【ステップ3】フィールドの動的追加 —
‘ ここでも他ユーザーの更新タイミングとぶつかる可能性があるため注意深く実行
Set fld = tdf.CreateField(targetFieldName, dbText, 255)
fld.AllowZeroLength = True
‘ テーブル定義へ追加実行
tdf.Fields.Append fld
tdf.Fields.Refresh
MsgBox “テーブル ‘” & targetTableName & “‘ へのフィールド追加が成功しました!”, vbInformation, “完了”
GoTo CleanUp
‘ —————————————————————–
‘ 排他制御・ロック競合専用のエラーハンドラー(リトライ機構付き)
‘ —————————————————————–
LockErrorHandler:
‘ エラー 3211: データベース オブジェクトをロックできません。
‘ エラー 3045: ファイルを開くことができません。(排他制御関連)
If Err.Number = 3211 Or Err.Number = 3045 Then
retryCount = retryCount + 1
If retryCount <= MAX_RETRIES Then
' ログ出力やデバッグプリント(実務ではここにDoEvents等を挟むと効果的)
Debug.Print "リソースが競合しています。(" & retryCount & "回目) " & RETRY_WAIT_SEC & "秒後に再試行します..."
' 少し待機(簡易的なウェイト処理)
Call SleepWithDoEvents(RETRY_WAIT_SEC)
Resume TryOpenTableDef ' もう一度トライ!
Else
MsgBox "現在、他のユーザーがこのテーブルを使用しているため変更できません。" & vbCrLf & _
しばらく時間を置いてから再度実行してください。", vbCritical, "排他エラー"
GoTo CleanUp
End If
Else
' 想定外のロック関連以外のエラーは通常のエラーハンドラーへ投げる
Resume ErrorHandler
End If
' -----------------------------------------------------------------
' 通常のエラーハンドラー
' -----------------------------------------------------------------
ErrorHandler:
MsgBox "予期せぬエラーが発生しました。" & vbCrLf & _
"エラー番号: " & Err.Number & vbCrLf & _
"エラー内容: " & Err.Description, vbCritical, "システムエラー"
CleanUp:
' オブジェクトの解放(メモリリーク防止のプロの作法)
Set fld = Nothing
Set tdf = Nothing
Set db = Nothing
End Sub
' -----------------------------------------------------------------
' 補助ルーチン:UIフリーズを防ぎながら待機する関数
' -----------------------------------------------------------------
Private Sub SleepWithDoEvents(ByVal seconds As Long)
Dim start As Single
start = Timer
Do While Timer < start + seconds
DoEvents ' 画面の固まり(フリーズ)を防ぐための重要テクニック
Loop
End Sub
---
4. コードの解説:ここがプロの技術!
上記のコードで、特に初学者の方に知ってほしい「プロのこだわりポイント」を解説します。
① リトライ機構(自律回復機能)の搭載
マルチユーザー環境では「今たまたま誰かが使っていた」という理由でエラーが起きます。人間であれば「あ、じゃあもう一回ボタン押そ」とやりますよね。それをプログラムに自動でやらせるのが `Retry` の思想です。数秒待ってから再トライすることで、運良くロックが外れたタイミングを狙うことができます。
② `DoEvents` を伴う安全な待機
VBAでただ止まるだけの処理を書くと、Access全体が固まってしまい、ユーザーが「フリーズした!」と勘違いして強制終了させてしまいます。`SleepWithDoEvents` を挟むことで、バックグラウンドで待機しつつもOSへの応答性を保つことができます。
③ 厳格なオブジェクトの開放(CleanUp)
DAOオブジェクト (`Database`, `TableDef`, `Field`) は、使い終わったら明示的に `Set xxx = Nothing` でメモリから解放するのが鉄則です。これをサボると、Accessの動作が徐々に重くなり、メモリリークの原因になります。
—
最後に:ここをクリアすれば、Access VBAの基本はバッチリですよ!
お疲れ様でした!
今回は、マルチユーザー環境という「現場のリアルな壁」を乗り越えるための排他制御とエラーハンドリングの極意をお伝えしました。
テーブル定義の変更は、Accessデータベースの構造そのものを書き換えるデリケートな操作です。だからこそ、ただ動くだけのコードではなく、「他の人が使っていても優しくハンドリングできるコード」を書けるかどうかが、初級者からプロフェッショナルへの分かれ道になります。
この知見をあなたの引き出しに加えれば、現場でどんなトラブルが起きても冷静に対処できるようになりますよ。自信を持って、次の開発に挑んでくださいね!
