Access VBA質問箱 IV

当質問箱は、有志のボランティア精神のおかげで成り立っています。
問題が解決したら、必ずお礼をしましょうね。
本サイトの基本方針をまとめました。こちら をご一読ください。

投稿種別の選択が必要です。ご注意ください。
迷惑投稿防止のため、URLの入力を制限しています。ご了承ください。


116 / 2272 ツリー ←次へ | 前へ→

【12965】Docmd.RunSQLだと問題ないのにDatabase.Exeuteだと反映にタイムラグが出る 亀マスター 16/3/9(水) 23:55 質問[未読]
【12967】Re:Docmd.RunSQLだと問題ないのにDatabase... かるびの 16/3/10(木) 10:47 回答[未読]
【12968】Re:Docmd.RunSQLだと問題ないのにDatabase... 亀マスター 16/3/11(金) 0:23 お礼[未読]
【12969】Re:Docmd.RunSQLだと問題ないのにDatabase... 亀マスター 16/3/14(月) 20:20 発言[未読]

【12965】Docmd.RunSQLだと問題ないのにDatabase....
質問  亀マスター  - 16/3/9(水) 23:55 -

引用なし
パスワード
   初めて質問させていただきます。

現在、社内システムをAccessで構築していますが、
INSERT INTOやDELETEのSQLをVBAから実行する際、
実行方法によってはデータベースへの反映に
時間がかかり、SQL実行直後にデータを拾おうと
すると更新前のデータになってしまうことがあり、
困っています。

具体的には、ある帳票フォームにレコードの削除・更新等のボタンを配置し、
そのクリックイベントとして以下のようなものを書いています。

−−−−−−−−−−−−−−−−−−−−−−−−−−
環境
Windows 7 Pro 32bit
Access 2013

※説明の簡略化のためエラートラップは省略しています
Dim strSQL As String
Dim WSP As Workspace
Dim DB as DAO.Database

strSQL = "削除クエリ、追加クエリ等"

'↓↓Execute使用パターン↓↓
Set WSP = Workspaces(0)
Set DB = CurrentDb

WSP.BeginTrans
DB.Execute strSQL
WSP.CommitTrans dbForceOSFlush

DBEngine.Idle dbRefreshCache

Set DB = Nothing
Set WSP = Nothing
'↑↑Execute使用パターン↑↑

'↓↓DoCmd.RunSQL使用パターン↓↓
DoCmd.RunSQL strSQL
'↑↑DoCmd.RunSQL使用パターン↑↑

Me.Requery
−−−−−−−−−−−−−−−−−−−−−−−−−−
実は元々DoCmd.RunSQLを使っており、その際は問題なかったのですが、
複数のSQLを連続して使う際にあまりよくないと聞いたもので、
Database.Executeを使うパターンに切り替えたのです。
しかし、そうすると、Me.Requeryをしたにも拘わらず、更新前の
ままになってしまいます。また、別のフォームで、
Me.RequeryのところにDLookupを使っているものがあるのですが、
そちらでも、更新前のデータしか得られません。

ただ、Me.RequeryやDLookupの前にMsgBoxやSleepで
5秒程度(2〜3秒ではうまくいくことといかないことがある)
待ってやると、うまく更新後のデータが拾えるようです。
このことから、キャッシュによるラグが生じていると予想
されるのですが、その対策として
WSP.CommitTrans dbForceOSFlush
DBEngine.Idle dbRefreshCache
を入れているのに、改善されません。

なお、更新対象のテーブルはサーバー上のAccdb(データ専用)に
リンクしているものと、ユーザー側のAccdb(プログラム用)に
システムデータ用に使っているものがありますが、どちらの
テーブルでも同じことが起こります。プログラム用のテーブル
相手でも起こることから、少なくともそちらはサーバーとの
接続速度の問題ではないと思うのですが…。

一応、Sleepで待ってやれば対応出来るのですが、それは
うっとうしいし、なんとか即時反映させたいのですが…
どなたか、お知恵を拝借出来ないでしょうか。

【12967】Re:Docmd.RunSQLだと問題ないのにDataba...
回答  かるびの  - 16/3/10(木) 10:47 -

引用なし
パスワード
   >実は元々DoCmd.RunSQLを使っており、その際は問題なかったのですが、
>複数のSQLを連続して使う際にあまりよくないと聞いたもので、
>Database.Executeを使うパターンに切り替えたのです。

 同期・非同期の問題ですね。

 RunSQL メソッドは非同期実行、Execute メソッドは同期実行と言われています。

 非同期実行というのは、例えば、RunSQL メソッドで追加クエリを実行した場合、
追加クエリが完了しないうちに、VBAにおける次行のコードが実行されてしまうものです。
 このため、次行実行時には、まだレコードが追加されていないということが起こり得ます。
 そうなると、あると思っていたレコードがテーブルにはないということになってしまうので、
思っていた結果にならないということになってしまいます。

 これに対し、同期実行であれは、追加クエリが完了しないうちは、
VBAにおける次行のコードが実行されません。
 そのため、上記のような問題は生じません。
 
 こうしたことから、追加クエリの実行は、Execute メソッドを使った方がいいと言われています。


>しかし、そうすると、Me.Requeryをしたにも拘わらず、更新前の
>ままになってしまいます。また、別のフォームで、
>Me.RequeryのところにDLookupを使っているものがあるのですが、
>そちらでも、更新前のデータしか得られません。

 これらの事象は、非同期実行よる典型例だと思います。
 事態が逆転してしまっていますね。実に妙なことだと思います。困惑するばかりです。


 その原因はわかりません。

 ただ、強いて言えば、トランザクション処理にあるのかなと思います。

 トランザクション処理は、トランザクション中は、追加クエリなどの結果をメモリにとどめておき、
CommitTrans により一気にファイルに書き込むものです。
 全く根拠はありませんが、ここが非同期実行になっているのかもしれません。
 だとすると、トランザクションをやめることが、一つの対策となるかもしれません。
 

>WSP.CommitTrans dbForceOSFlush
>
>DBEngine.Idle dbRefreshCache 

 dbForceOSFlush 定数とか、Idle メソッドとかがあるのは、全く知りませんでした。
 また、これらを使ったこともありません。
 なので、これらの効果や使用の是非については、私には何とも言えません。

【12968】Re:Docmd.RunSQLだと問題ないのにDataba...
お礼  亀マスター  - 16/3/11(金) 0:23 -

引用なし
パスワード
   かるびの さん 回答ありがとうございます。

>>しかし、そうすると、Me.Requeryをしたにも拘わらず、更新前の
>>ままになってしまいます。また、別のフォームで、
>>Me.RequeryのところにDLookupを使っているものがあるのですが、
>>そちらでも、更新前のデータしか得られません。
>
> これらの事象は、非同期実行よる典型例だと思います。
> 事態が逆転してしまっていますね。実に妙なことだと思います。困惑するばかりです。

そうなんですよね。
非同期実行を防ぐ意味もあってDatabase.Executeを使っているのに、どうして…。

> トランザクション処理は、トランザクション中は、追加クエリなどの結果をメモリにとどめておき、
>CommitTrans により一気にファイルに書き込むものです。
> 全く根拠はありませんが、ここが非同期実行になっているのかもしれません。
> だとすると、トランザクションをやめることが、一つの対策となるかもしれません。

クエリの複数連続実行をしている部分があるので、エラーが起こった場合の
ロールバック処理をするために、トランザクションは使いたいところなんですよね…。

>>WSP.CommitTrans dbForceOSFlush
>>
>>DBEngine.Idle dbRefreshCache 
>
> dbForceOSFlush 定数とか、Idle メソッドとかがあるのは、全く知りませんでした。
> また、これらを使ったこともありません。
> なので、これらの効果や使用の是非については、私には何とも言えません。

上記の原因が、メモリ上の命令をデータベースファイルに反映するのが遅れていたり
読み取りにいった時にキャッシュから読んでしまっている可能性を考え、
OSのキャッシュを即座にデータベースファイルへ反映させるためのオプションと、
AccessのReadキャッシュを即座に最新のものにするためのもの(というつもり)です。
ただ、これを付けても付けなくても、結果は変わってないのですが…。

概ね5秒ほど待ってから読みにいくと正常にデータを拾えることから、
Me.RequeryやDLookupの段階でReadキャッシュを読んでしまっている可能性が
高いと思っているのですが(キャッシュの更新間隔がデフォルト設定だと
5秒程度らしいので)、だったらDBEngine.Idle dbRefreshCacheで解決するはず
なのに…。

トランザクションははずしたくないので、どうしてもダメならSleepで5秒待機を
させることにします。ユーザー側からしたら待ち時間が目に付くかもしれませんが、
データ不整合によるエラーを起こすよりはマシなので。

引き続き、参考になることがあればお願いします。

【12969】Re:Docmd.RunSQLだと問題ないのにDataba...
発言  亀マスター  - 16/3/14(月) 20:20 -

引用なし
パスワード
   自己レスになりますが、部分的に対応方法ができました。

DLookupでデータを取得している部分で、以下のように
変更すると、更新後のデータを正常に取得出来ました。

Dim DB As DAO.Database
Dim RS as Recordset

'↓↓DLookupの代替↓↓
Set DB = CurrentDb
Set RS = DB.OpenRecordset("SELECT フィールド名 FROM テーブル名 WHERE WHERE句")
変数 = RS!フィールド名
RS.Close
'↑↑DLookupの代替↑↑

正直、これが良くてDLookupがダメな理由はよくわかりませんが、
CurrentDBあたりでキャッシュを無視してファイルのデータを読みに
いっているのかとか思ってます。他の環境でも同じようにうまくいく
保証はないのですが。

また、Me.Requeryがうまくいかないのは解決していません。
似たような発想でForm.RecordSourceをセットし直してみたり
したのですが、やっぱり古いデータのまま…。

116 / 2272 ツリー ←次へ | 前へ→
ページ:  ┃  記事番号:
1078188
(SS)C-BOARD v3.8 is Free