DAOの高速化を実現する43の技法
VB4に最適なDAOの意外と知られていない高度な技術を知る!
Microsoft Corporation. Michael Mee
文化オリエント株式会社 矢沢 久雄
はじめに
Visual Basic Ver.4.0(以下VB4と称す)では、Jetデータベースエンジンが搭載
され、VB4を使ったデータベースシステムの構築が容易にできるようになりました。し
かし、実際にJetを利用しているのは、まだまだほんの一部の人たちだけのようです。
DAOやデータコントロール、そしてRDOやRDCといった言葉は聞いたことがあって
も、具体的にそれらの特徴や機能を正しく理解して有効に使っている人は、まだまだ少な
いのが現状です。そればかりか、実際に使ってみる前からJetのデータアクセス機能を
「処理速度の遅いもの」だとか、「難しくて使いづらいもの」と決め付けているのではな
いでしょうか。
そこで、ここではVB4のデータベースアクセス機能のうちで特に有用な
DAOにテーマをしぼり、その概要を説明した上で、DAOの弱点と思われている処理速
度を改善する様々な高速化手法について解説します。
DAOの概要
まずDAOとは何かということからお話しましょう。DAOとは、Data Access Object
の略で、Jetデータベースエンジンの機能をカプセル化して提供するものです。プログ
ラミングにおいてDAOは、階層化されたオブジェクトクラスを集めたDLLの形式にな
っています。これらのオブジェクトクラスには、Database(データベース)オブジェクト、
TableDef(テーブル)オブジェクト、Field(フィールド)オブジェクト、およびIndex(
インデックス)オブジェクトなどがあります。
DAOもJetもその実態はDLL形式で
あり、クライアント/サーバ型のデータベースシステムにおいては、DAOもJetもそ
の実体を各クライアントマシンに置かねばなりません。このことは、この後でDAOの高
速化手法について述べる時に、基礎知識として知っておいてほしいことです。また、プロ
グラムにおいてDAOを使うには、DAOの提供するオブジェクトをオブジェクト型変数
として宣言して、そのプロパティとメソッドとでデータベースをアクセスします。
データベース接続におけるDAOの位置づけ
ODBCを使ったクライアント/サーバー型のデータベースシステムにおけるDAOの
位置づけとその役割を説明しましょう(図1参照)。VB4でデータベースアクセスを行
う場合に最高のパフォーマンスを得るには、ODBCのAPIライブラリを利用したり、
SQLサーバのAPIに直接リンクするVisual Basic SQLライブラリ(VBSQL)を利
用する方法もありますが、それらにおいてはコーディングが複雑なものとなりますので、
開発効率を重視するVBユーザーにはあまりお勧めできません。
また、図1において、
DAOと同じ位置にあるRDO(Remote Data Object)ですが、これはODBCのライブラ
リをカプセル化したもので、Jetを仲介させない分だけ、処理速度は高速となりますが、
やはりコーディングが若干複雑なものとなります。DAOは、ODBCのみならず多くの
形式のデータベースに対応したJetデータベースエンジンの機能をカプセル化したもの
です。一番上に位置しているData ControlおよびRDC(Remote Data Control)は、DAO
およびRDOの機能をさらにカプセル化し、VBのフォームに貼り付けられるOCXと
なっているものです。
図1においては、上にあるもの程コーディングが容易ですが、処理
速度は遅くなります。また逆に下にあるもの程コーディングが複雑ですが、処理速度は速
くなります。この中で、何を使うべきかを選択しなければなりません。選択肢は、システ
ムの規模や目的に応じても様々でしょうが、私は何といってもDAOをお勧めします。
(図1)データベース接続におけるDAOの位置づけ
[Data Control][RDC]
↓ ↓
[DAO] [RDO]
↓ ↓
[Jet] ↓
↓ ↓
[ ODBC ]
↓ ↓
[ SQL Serever ]
DAOの利点
DAOすなわちJetデータベースエンジンの最大の利点は、ODBCに限らず、Access形式や
ISAM形式といった様々なデータベース形式に対応しており、それらに
統一的なプログラムインターフェースを与えるということです。これは、RDOやRDC
には無い大きな利点です。また、DAOの持つこの汎用性により、他のアプリケーション
とデータを共有するようなアプリケーションを作成できます。
また、低レベル(すなわちAPIレベル)のファイルアクセスや検索のためのコードの記述は一切不要で、簡潔なコ
ードで目的の操作を実現できます。
また、サポートしている多くのデータベース形式のど
れに対しても、同じオブジェクト、プロパティ、およびメソッドを使った統一的なプログ
ラミングインターフェースが与えられ、データベース形式の変更に伴う移植も比較的容易
に行うことが出来ます。
また、DAOはJetをカプセル化したものだからといっても機
能の省略などは無く、Jetの提供するすべてのデータベース制御機能を使って、データ
ベースの作成、テーブル、フィールド、インデックスの定義、テーブル間のリレーション
の設定、データベース内の移動、およびクエリーなどが行えます。
DAOの弱点
DAOの弱点についていえば、その処理速度ということになります。ODBCやSQL
サーバのAPIライブラリを直接使った場合より処理速度が遅くなるのは当然です。
また、
Jetを使わずにODBCのAPIライブラリ自身をカプセル化したRDOより遅いのも
当然です。しかし、DAOをさらにカプセル化してOCXとして簡単に使えるようにした
データコントロールよりは速いわけですから、DAOが最も遅いというわけではありませ
ん。いくつかの方法がデータベースアプリケーションの構築において選択でき、その中で
なぜ私がDAOをお勧めするのかがお分かりでしょうか。
DAOには、すべてのデータベ
ース制御機能と多くのデータベースに対応した汎用性があります。これは、処理速度の若
干の遅さを補っても余りある機能です。
DAOの使い方
DAOの高速化手法を具体的に紹介する前に、DAOの様なプログラムオブジェクトの
使い方を簡単に説明しましょう。
VBのフォームに貼り付けて使うOCXなどのコントロ
ールでは、フォームにコントロールを貼ることによってオブジェクトのインスタンスが生
成されますが、DAOの様なプログラムオブジェクトでは、オブジェクト型の変数を宣言
することによってオブジェクトのインスタンスが生成されます。DAOオブジェクトのク
ラスは階層構造となっており、各オブジェクトはコレクションを持っています。これもこ
の後で述べる高速化手法を理解する上での基礎知識として知っていてほしいことです。下
位の階層にあるオブジェクトを参照するには、上位のコレクションからの階層をたどって
行くことになります。
また、データベースの定義や操作は、オブジェクトのプロパティと
メソッドとで実行されます。コード1〜3にDAOのオブジェクトを使ったコードの簡単
な例を示します。
(コード1)
'オブジェクト変数の宣言(インスタンスの生成)
Dim MyWs As Workspace
(コード2)
'コレクションの階層をたどってCustomerという名前のFieldsオブジェクトを参照する
a = DBEngine.Workspace(0).Database(0).TableDefs(0).Fileds("Customer")
(コード3)
'ACCOUNT.MDBという名前のデータベースを開くメソッドの実行
Set MyWs.OpenDatabase("ACCOUNT.MDB")
また、DAOの高速化手法を理解するためには、DAOのレコードのオープン形式(レコ
ードセットオブジェクトのタイプ)に関する知識も必要です。DAOのレコードのオープ
ン形式には3種類あり、目的に合わせて使い分けるようにします。これらのオープン形式
の特徴を表1に示します。
(表1)DAOのRecordsetオブジェクトの形式と特徴
-----------------+--------------------------------------------------------------
形式 |特徴
-----------------+--------------------------------------------------------------
テーブル |オープンしているデータベース内のローカルテーブルを参照する。ア
|タッチテーブル(他のデータベースのテーブル)を参照できない。イ
|ンデックスやSeekメソッドは、テーブル形式でのみ使用可能である。
-----------------+--------------------------------------------------------------
ダイナセット |ローカルテーブル、アタッチテーブル、およびクエリーの結果を参照
|する。複数のテーブルやアタッチテーブルのデータについて、並べ替
|え、抽出、更新、およびフィルタの実行が可能である。
-----------------+--------------------------------------------------------------
スナップショット |ダイナセット形式とほぼ同様であるが、スナップショット形式は、そ
|れを作成したときに存在したデータの静的なコピーであり、更新がで
|きない点でダイナセット形式とは異なる。
-----------------+--------------------------------------------------------------
DAOの高速化手法
では、これからDAOを高速化する手法をいくつか紹介したいと思います。
1. Find より Seek
レコードの検索を行うメソッドには、SeekとFindとがあります。Seekは、
テーブル(インデックス付き)のみで使え、Findはダイナセットとスナップショット
とで使えます。Seekは、Findに比べてオーバーヘッドが少なく処理速度が速くな
ります。ただし、Seekは、インデックス付きのフィールドのみで使えるので、無理に
Seekを使おうとしてインデックスを多用すると逆効果になります。また、ODBCで
は、クライアント/サーバーの制限で、テーブル形式のオープンはできないため、この手
法は使えません。
2. Dynaset,Snapshot より Table-Type Recordset
もしもテーブルの結合や複雑なクエリーを使わないのなら、ダイナセット、スナップシ
ョットよりテーブルの方が速くなります。ダイナセットやスナップショットのものは全て
テーブルタイプでサポートされていますが、2つの例外としてFilterとSortプ
ロパティとがあります。一般的なルールとして、もしもJoinやSortが必要なら、
テーブルより逆にダイナセットやスナップショットの方が高速になります。なお、テーブ
ルタイプでオープンしたときの問題点は、テーブルのアタッチができないということです。
もしもデータベース内のテーブルが小さいなら、テーブルタイプの効果は少ないので、ダ
イナセットやスナップショットの方がコーディングが楽な分だけ有利となります。
3. Databaseを直接オープンすることにより、直接アタッチ・テーブルをオープンする
サーバー上に実際のデータがあり、クライアントのフォームやレポートがそれにアタッ
チするといった場合は、アタッチを使っているためテーブルタイプでオープンできないの
で、ダイナセットまたはスナップショットを使うことになります。もしODBCを使えば
テーブルタイプが使えると思っても、それはアタッチより必ず遅くなるので行ってはいけ
ません。アタッチをせずODBCも使わない手法として、Conectプロパティを使っ
て、アタッチのアーキテクチャーでありながらも直接テーブルをオープンする方法があり
ます。これによって手法1、2の利点を活かすことができます。なお、この方法はテーブ
ルが小さい場合に逆効果の場合もあります。なぜなら、データベースをもう一つオープン
するためのオーバーヘッドがあるからです。
4. リモートデータには Dynaset より Snapshot(Memo形式に要注意)
ここでは、ダイナセットとスナップショットのどちらが速いかということではなく、そ
れぞれの特徴を説明するので、目的に応じて使い分けて欲しいと思います。Jetエンジ
ンは、リモートデータ用にダイナセットとスナップショットの2種類のレコードセットを
持っていますが、もしも欲しいデーターが500レコード以下で更新不要なら、スナップ
ショットを使った方が速くなります。これは、ダイナセットの場合Jetは全レコードの
主キーだけを読み込み、スナップショットの場合全レコードのフィールド自身を読み込む
からです。ただし、レコードセットが500以上で、メモ形式もしくはOLEオブジェク
トが含まれているならダイナセットの方が効果的です。また、ダイナセットの場合、Je
tはレコードのメモ形式もしくはOLEオブジェクトのフィールドを参照したときに、そ
のオブジェクトをメモリにロードします。レコードセットを作るとき、Jetは全てのレ
コードの全てのフィールドを読むのではなく、あくまでキーを設定するのみで、そのレコ
ードが必要となってから実体を持ってきます。これによってネットワークのトラフィック
を軽減できます。このようにキーのみを読み込む手法をブックマークと読んでいます。ス
ナップショットには、ブックマークという概念がなく、全てのデータをメモリ上に読み込
んでしまいます。従って、小さいテーブルの場合はスナップショットの方が速くなります
が、メモ形式やOLEオブジェクトのように実体の参照に時間がかかるものには注意が必
要です。なお、レコードが少なくフィールドも小さい場合には、ダイナセットとスナップ
ショットのどちらの場合も、SQL文で必要なフィールドのみを指定することができ、高
速な処理を行わせることもできます。
5. ODBC Snapshotに DB_FORWARDONLY を使用
ODBCでスナップショットをオープンするときは、両方向のスクロール可能がデフォ
ルトになりますが、その必要がないのであればDB_FORWARDONLY(前方向スクロールのみ)
フラグを指定した方が高速になります。なぜならば、スクロールバッファを省略できるか
らです。
6. ODBC DynasetのCacheStart、Cachefill、CacheSizeを使用
CasheStartプロパティは、キャッシュをスタートするレコードを決め、CasheFillメソッ
ドは、キャッシュ動作を実行し、CasheSizeプロパティはローカルのレコード数を5〜12
00レコードの範囲で決めるものです。CasheSizeの推奨値は100で、これを0にすると
キャッシュのためのメモリが開放されます。またキャッシュのためのメモリが不足してい
る場合は、ローカルのHDが使われます。これらを適切に使って、ダイナセットの一部ま
たは全部のデータをローカルメモリに置くことで高速化が行えます。このキャッシュを使
うときには、データベースと処理するレコードの歩調を合わせることが必要となります。
レコード1〜100をキャッシュ対象にしているときに、150番目のレコードを処理す
るなら、キャッシュ範囲を100〜200の様に変更します。キャッシュを使うか使わな
いかで2倍くらいの効果があります。特にバックスクロールにおける効果は大きなものと
なります。また、ReadOnlyやForwardOnlyを指定するよりキャッシュの方が効果が大きいこ
とが多くなります。特にメモリ形式、Long Binary Filed、OLEオブジェクトの参照が行
われる場合は、効果が大きくなります。キャッシュのサイズについてはケースバイケース
ですので、いろいろ試してみるとよいでしょう。
7. Dynamic SQL Text より QueriesをDBに保存(特に少メモリマシン)
DAOはSQL文を簡単に作ることができますが、この場合にコード中でSQL文を生
成するより、それらをDBに保存したストアドクエリを使った方が速くなります。ストア
ドクエリなら、クエリのコンパイルがかからないので、特にメモリの少ないマシンでは処
理が速くなります。SQL文は直接コードの中に書きますが、ストアドクエリはデータベ
ース中にあるのでコードのメンテナンスも楽になります。
8. Dynamic SQL Text より Parameterize Queries(特にODBC)
コードの中でSQL文を生成し、JetにSQL文を実行させるのがダイナミックSQ
Lです。データベースの中にSQLを保存しなくてもよいのですが、メモリの消費量が大
となります。JetがSQL文をチェックし、コンパイルし、そしてサーバー専用のクエ
リをJetからODBCに渡し、ODBCが実際の操作を行います。また処理結果もOD
BC→Jet→DAOの順で戻ってきます。メモリが少ないマシンの場合は、この代替え
手法として、パラメータ型クエリを事前に作っておく方法があります。これは、SQLス
テートメント中に実際の値ではなく、変数を置くものです。コード中で変数に実際の値を
設定し、その値をJetに伝えます。一度パラメータ型クエリーがコンパイルされると、
Jetもパラメータ型クエリーをODBCに渡し、ODBCはその結果を返します。そし
て、同じパラメータ型クエリーは、2回目以降には高速に処理されます。なお、データを
参照するだけなら、この手法よりもパススルークエリーの方が高速な場合が多いのですが、
それについては後述します。
9. Open Database を Exclusive 全ユーザーが Read-Only なら Read-Only
データベースをExclusiveとRead-Onlyに設定してオープンするとパフォーマンスが向上
します。なぜならば、ロック情報ファイル(.LDB)が生成されないからです。そしてロッ
クの制御も行われなくなり、ネットワーク上のトラフィックが小さくなり高速となります。
なお、この手法はローカルでは効果がありません。
10. ODBC SQL Statementsでは、なるべく Pass-Through を使用
JetにSQL文を渡すときパススルーを指定すると、ODBCを経由しなくなり、直
接サーバーに伝えられるようになります。SQL文のチェックとコンパイルは不要となり、
そのメリットとして、JetやVBが対応していないサーバー依存型のSQLコマンドが
使えることと、サーバーからの独自のメッセージやステータスの取得が行えるということ
があります。パススルーによる更新、削除、追加は、リモートテーブルにアタッチするよ
り高速です。特に、更新するレコードが多い場合には効果的です。ただし制限として、部
分的な処理ができないことと、複数テーブルにまたがったクエリーは使えないといったこ
とがあります。
11. DAO でループ処理するより、同等のSQL Statements
手法7では、一般的にはDAOコードよりストアドクエリーの方が高速であり、メンテ
ナンス性がよいことを話しました。しかし、場合によってはDAOコードの方が速い場合
もあります。DAOコードの方が速くなる条件として、(1)テーブル型でオープン可能
であること、(2)ODBCを使わないこと、(3)トランザクション処理を行うこと、
(4)Joinを使わない単純なシングルテーブルの様に大量のデータ更新を行わないこ
とがあります。
12. つねに DAO コードで Transactionsを束ねる
これは特に追加、更新、削除の場合には必須です。なぜなら、ディスクアクセスは、1
トランザクションにつき1回行われるからです。無駄なディスクアクセスが発生しないよ
うにしなければなりません。そのためにはデータを論理的なグループに分け、グループ毎
にトランザクションを分割してかけるようにすることが大切です。トランザクションはメ
モリ上で行われ、トランザクションのCommitTransで物理的なディスクアクセ
スが行われます。
13. Find より Bookmarks
ダイナセットの場合には、任意のレコードに戻るにはFindよりブックマークの方が
速くなります。元のポジションに戻るにはブックマークを使うのが最も速い方法です。な
ぜならブックマークは、メモリ上に記憶されているからです。なお、ブックマークのプロ
パティは1つだけだからといって、ブックマークを1つしか設定できないと考えがちです
が、ブックマークを別の変数に保存しておいて再利用すれば、いくつでもブックマークを
設定できます。ただし、テーブル形式のレコードセットの場合は、Seekメソッドがブ
ックマークと同等に高速になります。
14. MyRS!fieldname より Field Objects(ループ処理内)
これは直接コードを見てもらった方がよく分かるでしょう(コード4参照)。ループの
中でフィールドを参照するときに、"レコードセット!フィールド名"と指定すると、毎回
フィールドの検索が行われることになり、処理速度が低下します。これをループの外で、
Filedオブジェクト型の変数にあらかじめ格納しておくことで処理速度を向上させることが
できます。これは、ループの回数が多い場合に特に有効です。
(コード4)
Set rstTemp = dbCurrent.OpenRecordset("Customers", DB_OPEN_TABLE)
For i% = 1 To Count '遅い例
a = rstTemp![Customer ID]
Next
ShowTime "MyRS!フィールド名"
Dim fldCust As Field '速い例
Set fldCust = rstTemp![Customer ID]
For i% = 1 To Count
a = fldCust.Value
Next
15. 変更が少ないテーブルはローカルMDBに保存
これはネットワークを前提としたことです。あまり更新する必要のないテーブルはロー
カルマシンに置くようにします。例えば、リストボックスに表示する都道府県のデータな
どはローカルマシンに置き、サーバーからダウンロードしなくてもよいようにしておきま
す。このことを行ってよい条件としては、(1)テーブルは更新されないこと、(2)J
oinをかけるときはリモートのサーバー上にも同じデータベースを置いておくようにし、
もしこのデータベースが更新されることがあるなら、手動でそれが行えるようにしておく
こととがあります。時々テーブルが更新される場合は、同じデータベースをクライアント
とサーバーの両方に置いてアプリケーションの起動時に毎回更新するようにします。これ
によってアプリケーションの起動時間は遅くなりますが、その後の処理は高速になります。
16. ODBC Time-Outs の減少によってのDAO戻りの高速化
これは、ODBCのタイムアウト時間を出来る限り小さい値にして、DAOになるべく
速く制御を戻すようにするということです。ODBCからの応答待ちを短くすることで、
ユーザーには速いと思わせることができます。特にローカルネットワークにサーバーがあ
り、レスポンスが速いときには有効です。ODBCのタイムアウト時間をどの程度まで短
く設定できるかはケースバイケースなので実験してみることをお勧めします。
17. Tabledef と Field Property Collection Refsを多く参照するなら、キャッシュする
TabaledefオブジェクトとFieldオブジェクトからプロパティを取得するときに、パフォ
ーマンスを低下させるものがあります。特に「ユーザー定義のプロパティは大きくパフォー
マンスを低下させるので、それをループの中で参照しなければならない場合には、ループ
の外で変数に一時的に格納したものを使うとよいでしょう。この手法をプロパティのキャ
ッシュといいます。
18. ODBC Find の高速化:
データをローカルのインデックス化されているテーブルにダウンロードし、Seekを使用
これは一時的なインデックス化されたテーブルを作成し、Seekメソッドを使って検
索スピードをアップするということです。複数のテーブルをJoinし、クエリーをかけ
て検索するような場合は、このような一時的なテーブルを物理的に作ってしまって、それ
に検索をかけた方が高速になります。スナップショットを使った場合は、インデックスが
付かず、Seekが使えないため遅くなってします。またSQLのWhere文も低速で
す。ただし、この手法は、SQLサーバーから大きなテーブルを持ってくるような場合に
は効果が少ないものとなります。スナップショットに比べて、物理テーブルの生成は10
%程度遅くなりますが、検索は6倍くらい速くなります。注意事項として、不要となった
一時テーブルを削除すること(Jetには自動削除機能がありません)と、ネットワーク
上でユニークなテーブル名を付けなければならないということとがあります。
19. 繰り返し処理されるDynamic SQLの代わりに、臨時のQuery
これは、ストアドクエリーデータベース上にクエリーを保存したくないときや、アプリ
ケーションを実際に動作させなければクエリーが生成できないような場合に有効です。
ストアドクエリーとテンポラリー(すなわち臨時の)クエリーとの違いは、テンポラリーク
エリーは、unnamed(すなわち長さが0)ということです。そのためクリーンアッ
プが不要であり、またデータベースのコンテナに含まれないので、他のユーザーからは見
えないようになります。
20. Dynasetにレコードを追加するなら、DB_APPENDONLY を使用(特にODBC)
これは、ダイナセットにレコードの新規追加をする場合には、レコードセットのオープ
ン時にDB_APPENDONLYフラグを設定しておくと、特にODBCの場合高速になるということ
です。なぜならば、レコードセットにブックマークを追加する処理が省略されるからです。
21. バリアント型より実際のデータ型
一般的にはプログラミングにおいて、バリアント型は実行速度を低下させるので、出来
る限り使わないようにし、ネイティブのデータ型を使うべきですが、DAOでは内部的に
は全てバリアント型で処理しているので、このことは当てはまりません。DAOでバリア
ント型が使われているのは、バリアント型が唯一Nullを格納できるからです。データ
ベースのプログラミングにおいては、データ型よりもデータアクセスの手法に労力をかけ
るべきです。
22. 短いMemo型と長いText型
これは、Memo型よりもText型の方が処理速度が速いということです。Memo型は、データ
ベース中のメモデータ領域へのポインタのみを格納し、Text型は文字列そのものを格納し
ています。文字列を格納するフィールドの長さが確定しづらい場合には、Memo型を使って
しまいがちですが、Memo型は実際のメモの内容の文字列をデータベースから読み込んでく
るオーバーヘッドの分処理速度が遅くなってしまいます。
23. Snapshot:全フィールド選択より、必要なフィールドのみ選択
これは、スナップショットを使うときに、レコードの全てのフィールドを指定するより
必要なフィールドのみを指定した方が高速であるということです。なぜならば、指定され
たフィールドのみがローカルメモリにコピーされるので、効率がよくなるからです。Memo
型やOLE型をODBCで読み込む場合には特に効果が大きくなります。ただし、指定す
るフィールドが多い場合には、ダイナセットを使った方が効率的な場合もあります。
24. テーブル数が多い場合、 DAO Tabledef初期化時間を短縮するには、.MDBを分割
TabledefなどのDAOオブジェクトのコレクションを参照するときには、すべての基本
情報がメモリ上にコピーされます。特に100以上のテーブルを持つデータベースをアプ
リケーションで使う場合には、この時間は非常に長いものとなります。これを避けるため
には、実際のテーブルを論理的にいくつかのMDBに分割するという手法があります。そ
して、必要に応じてこれらの分割されたMDBをDAOでオープンするようにします。こ
れによって別々のMDBにそれぞれのTabledefなどが存在することになり、トータルで見
た場合の初期化量は同じではあっても、その初期化を行うタイミングをコントロールでき
るようになり、必要なときに必要なMDBの初期化が行えるようになります。
25. DBCSを使用しないなら、Like より Instr()を試してみる
Likeは、DBCS(2バイト文字、すなわち日本語など)には最適ですが、処理速度が遅く
なります。もしも2バイト文字を使わないのであるならば、Instr()、Left()、Right()を
使った方が高速となります。ただし、Instr()、Left()、Right()はVBやAccessに
特化されたものであり、ODBCではサポートされていない場合が多くあります。この場
合は、(信じられないかも知れませんが)すべてのデータがローカルにコピーされてから
検索が行われ、極めて処理速度が低下します。
26. Filter より Seek(または Query)
Filterプロパティは、とても遅いものです。検索を行うには、テーブルをオープンし、
インデックスを指定してSeekを使うのが良い方法です。もしも適切なインデックスがなく、
検索が複雑なものとなるのであるならば、新しいクエリーを作り、SQLのWhere文を使う
のが良い方法といえます。新しいクエリーを作る方が、既存のクエリーから別のクエリー
を作より高速となります。一般的には、もしもレコードが100を超えるなら、Filterプ
ロパティ使わないのが原則です。
27. Recordsets を再オープンするより、Queryを再実行
レコードセットを再オープンするよりは、Requeryメソッドを使ってクエリーを再実行し
た方が高速になります。これは、ダイナセットまたはスナップショットにあてはまること
です。特に最初のクエリーを実行してから、レコードが追加されたりするときにリフレッ
シュを行う場合に有効です。
28. Objectへの直接長い参照より、Dim Objects、Setで定義し、参照する
これは、目的のオブジェクトの参照をその都度その上位のオブジェクト階層を順番にた
どって指定するのではなく、一度オブジェクト型変数に格納してしまうということです。
一度オブジェクト型変数に格納してしまえば、それ以降の参照はすべてこのオブジェクト
型変数で行え高速な処理ができます。ただし、オブジェクトの参照が1回だけの場合は無
意味です。この様なテクニックは、処理速度を特に要求される部分(コード全体で最も多
く使われる10%ぐらいの部分)に使うようにするべきで、あまりコードのすみからすみ
までに気を使う必要はありません。
29. Text collection lookupを行うより、序数を使用
これもコードを直接見た方が分かりやすいでしょう(コード5参照)。
(コード5)
Set rstTemp = dbCurrent.OpenRecordset("Employees", DB_OPEN_TABLE)
For i% = 1 To Count
a = rstTemp![Notes] 'フィールド名を直接指定
Next
For i% = 1 To Count
a = rstTemp(14) '序数を使用
Next
フィールド名を直接指定するのではなく序数を使うことによって得られる効果は少ないも
のです。それによってコードの可読性が低下したり、データベースの構造を変更したとき
に発見しづらいバグの原因となる方が問題でしょう。
30. デフォルトコレクションを使用、余計なコレクション名指定は避ける
DAOオブジェクトのコレクションには、Tabledef、Querydef、Recordsetなどがあるわ
けですが、これらを1つ1つ指定するより、指定を省略したときに暗黙に指定されるデフ
ォルトのコレクションを使った方が僅かですが高速になります。ただし、コードは可読性
の悪いものとなりますので、この手法は繰り返し回数の多いループの中だけで使うように
するとよいでしょう。
31. Collection-Basedコードを使う、旧来のListTablesなどの置き換え
Access ver.1.x から 2.0 へのバージョンアップにおいてListメソッドなどがより短い
コードで記述できるようになり、実行速度もそれに伴って速くなりました。これは、VB
Aによりコレクションベースのコードが書けるようになったからです。従来のコードをど
の様な新しいコードに置き換えられるかについては、Access のヘルプに詳しい説明があり
ます。
32. Explicit Code よりクエリーの 'Posting'が速い
クエリーを使ってデータベース中のテーブルを更新する方法をポスティングクエリーと
いいます。Jetは先進的なクエリーを持っていますが、データベースの更新時には、そ
れらの機能が使われません。そこで、一般的な方法として、複数行のコードでデータベー
スを更新することを1つのクエリー文にする手法があり、これがポスティングクエリーで、
処理速度の速いものとなります。ポスティングクエリーは2つのテーブルから構成され、
1つはマスターデータを持ち、2つ目はそれと同じ構造でアップデート構造を持っていま
す。このアップデート情報には、変更データと追加データの情報が入っています。普通な
ら更新データをマスターデータに追加しますが、ポスティング・クエリーでは、1つのク
エリーでそれと同じ操作が行えるようになります。Jetには更新可能な外部Joinと
いう機能があり、更新テーブルとマスターテーブルにJoinをかけることができ、マス
ターに更新と追加とができます。
33. 現在のフィールド値更新は、MoveNext/Previousより Move 0
Access ver.2.0 からMove 0を使ってカレントフィールドのリフレッシュができるように
なりました。Move 0を使うと、Jetエンジンは物理レコードをディスクから再度取得し
ます。従って、現在のレコード位置が変わっていなくてもネット上の最新のデータを確実
に持ってくることができます。Move 0を使った手法は、従来のMoveNextとMovePreviousを
連続して使っていた方法に比べて高速です。またMoveNextでファイルの末尾以降を読み出
したときに発生するEOFエラーの処理ルーチンも不要となります。
34. Move 0 で Edit と AddNewの中止
これはあまり使われないかもしれませんが、EditメソッドやAddNewメソッド実行後に、
Move 0を使うと、EditやAddNewを中止することができます。これも従来のMoveNextと
MovePreviousメソッドを使った方法に比べて高速になり、EOFエラー処理ルーチンも不
要となるので有効です。
35. 新しいDynaset Recordsets をオープンするより、DynasetのClone
これもあまり使われないかもしれませんが、効率的で処理速度の速いレコードセットの
コピー(クローン)を作成するという手法です。これは、クエリーを実行することや、テ
ーブル型のレコードセットをオープンするより遥かに速くなります。一般的には、レコー
ドセットのクローンの作成時間のロスは、ほとんど考えなくてもよいレベルのものです。
もしも、レコードに対してEditをかけて、更新がまだ行われていない場合は、実際のディ
スク上からのデータを取得することができます。例えば、更新でエラーが発生した場合で
も、Cloneメソッドでオリジナルのデータをユーザに表示することができます。レコードの
一部をユーザに表示する場合に、表示についてはクローンを使い、更新は実際のレコード
を使うようにします。これは手法6で説明したキャッシュを使うとさらに効果的となりま
す。
36. モジュールを保存する前にコンパイル
これは、アプリケーションを配布する前には、モジュールとクエリーのコンパイルを行
うようにするということです。コンパイルを行わないと、アプリケーションの実行時に内
部的に毎回クエリーのコンパイルが発生するので時間のロスとなります。
37. ODBC の場合、OLE型とMemo型は、SnapshotsよりDynasets
これは手法4と手法23で述べたことと同様です。レコード上には参照情報のみが記録
されており、データの実体は別に保存されているようなOLE型やメモ型のレコードにお
いては、特にODBCの場合にスナップショットよりダイナセットを使った方が効率が良
くなります。
38. ODBCの場合、インデックス化されているフィールドに対し、Findを使用、他は新し
いRecordsetを生成する
Findメソッドは、レコードセットの中の1つのレコードを検索するときに有効です。一
般的にJetは、DAOからODBCデータベースへのレコード変換において、テーブル
を使っている場合にはSeekを使ってしまいますが、ODBCではテーブルが使えないわけ
ですからダイナセットに変えてFindを使います。しかし、これは大変処理速度の遅いもの
となります。これを効率よく行うには、新しいレコードセットを作成し、Where文を使って
その中に検索文字列を指定するようにします。これによって、データベースからは、それ
に該当するレコードセットだけが戻ってくるようになり、これはサーバ上ではシングルオ
ペレーションとなるため効率的です。場合によっては、ユーザインターフェースを変えな
ければならないこともありますが、この手法は大きなテーブルにおいては特に有効です。
どうしてもFindメソッドを使う必要があるならば、Findの内部処理をよく理解した上で最
適な方法で使わなければなりません。Findは、シングルフィールドでかつそれがサーバー
上でインデックス化されており、検索がイコール(=)またはLike文を使っている場合な
ら効果的といえます。Jetはこの場合に限って、サーバー上にクエリーをかけて、それ
に一致するレコードのみをローカルにダウンロードします。そしてその中で、1つ1つの
キーを検索して最終のアプリケーションに渡します。これは、Findが失敗(該当レコード
無し)の場合に非常にレスポンスの高いものとなります。Findメソッドは、スナップショ
ットには効率が良いものです。そして、レコード数が500以下ならば効果的です(ただ
しメモリやバッファの状態によります)。この場合Findは、実際にローカルメモリ上にダ
ウンロードされているスナップショットのデータを検索します。もちろんスナップショッ
トがダウンロードされていなければ、ダウンロード時間が追加されます。つまり、Findが
失敗する場合でも、アプリケーションはすべてのレコードを持ってこなければなりません。
Findが遅くなるケースとしては、(1)レコードセットがODBCのダイナセットの場合、
(2)レコード数が100を超える場合、(3)Find文が前回に定義した条件と一致して
いない場合(1つ1つのレコードが読み込まれてチェックがかかってしまうため)とがあ
ります。しかし、Findが遅いからといって、Basicコードのループで同じことをするともっ
と遅くなってしまうので注意して下さい。
39. 文字列型なら、Variant String Functions より "$" Functions
これはあまり効果的ではないのですが、バリアント型を相手側に合わせて文字列型に変
えると若干の効果があります。
40. Sort より 明示的にIndexを指定(またはQuery)
レコードセットのSortプロパティは大変遅いものです。効率的なのは、テーブルタイプ
のレコードセットでオープンし、インデックスを指定してデータを読み込むことです。も
しも、アタッチテーブル形式やODBCのためテーブルタイプでオープンできない場合は、
ダイナセットで新しいクエリーを作成し、OrderBy(SQL文)を使うとよいでしょう。
既存のレコードセットからクエリーを作るより、OrderByで新しく作った方が効率的です。一
般的なルールとして、100以上のレコードをソートするならSortプロパティを使わず、
ここで紹介した手法を使った方が良いでしょう。
41. 浮動小数点数より整数型
これはDAOに限ったことでなく、アプリケーション全体のコードで注意すれば、全体
のパフォーマンスが向上します。Accessのデフォルトは、Double(倍精度浮動小数点数型)
ですが、これはサイズ、処理速度とも最も効率の悪いものです。整数型で十分であるのな
らば、データタイプを整数型に変更することで、DAOにおいてもデータの取得や更新が
若干速くなります。
42. 書き込みは一人だけの場合、DB_DENYWRITE フラグを使用
もしもネットワーク上の他のユーザがデータベースに更新をかけないのであるならば、
DB_DENYWRITEフラグを設定することで処理速度が向上します。一人のユーザだけがデータ
ベースに更新をかける場合は、このフラグでデータベース全体にロックがかかり、更新で
きるのは一人のユーザだけとなります。これは、部分的なロックであるために更新が速く
なります。
43. Collectionsのリフレッシュは必要最低限に
Refreshメソッドは大変時間のかかる処理であるため、慎重に、本当に必要な場合にだけ
使うべきです。DAOは複数のオブジェクトの中で、いろいろなコレクションを所有し、
あるものは実際のデータベースに保存されたデータを元にしているので、ネット上ではメ
モリ上のデータが古くなる場合があります。そこでRefreshをかけて強制的な更新を物理的
に行うわけですが、これは大変時間のかかる処理ですので注意が必要です。
どの手法を使えばよいか?
さて、DAOの弱点である処理速度の遅さを改善するための手法を今までにいくつか紹
介してきましたが、これらを全て使えばよいというものでもありません。実際のシステム
の構成や目的に応じて効果の大きいものを選択しなければなりません。システムの構成に
よっても効果の大きなものと小さなものとがあります。システムの構成をODBCを使っ
た場合と使わない場合とに分けて、それらにおいて効果の大きい順にランク付けしたもの
を表2〜3に示します。これらの表においては、効果を1〜5の5段階に分けており、1
が最も効果の大きいもので、5が最も効果の小さいものであることを示しています。これ
らの表を比較すると、どちらの場合でも効果が大きいという手法は意外と少ないものであ
ることに気が付かれたことでしょう。データベースアクセスの最適化というのは、本当に
ケースバイケースのものなのです。
また、ここまでに紹介した手法以外にDAOの高速化
手法は無いというわけでは決してありません。技術の進歩に伴って、さらに新しい手法が
登場するはずです。また読者の皆様自身にも独自のアイディアがあることと思います。な
お、これらの手法に共通していた注意点としては、テーブルタイプ、ダイナセットタイプ、
およびスナップショットタイプの特徴をよく理解することと、データベースにおいて一番
時間のかかるデータの取得と更新に重点を置くということです。これらがDAOの高速化
の基本と言えます。
(表2)ODBCを使う場合に効果的な高速化手法
-----+-----+--------------------------------------------------------------------
効果 |番号 |手法
-----+-----+--------------------------------------------------------------------
1| 12|つねに DAO コードで Transactionsを束ねる
-----+-----+--------------------------------------------------------------------
1| 20|Dynasetにレコードを追加するなら、DB_APPENDONLY を使用(特にODBC)
-----+-----+--------------------------------------------------------------------
1| 40|Sort より 明示的にIndexを指定(またはQuery)
-----+-----+--------------------------------------------------------------------
1| 6|ODBC DynasetのCacheStart、Cachefill、CacheSizeを使用
-----+-----+--------------------------------------------------------------------
1| 38|ODBCの場合、インデックス化されているフィールドに対し、Findを使用、他
| |は新しいRecordsetを生成する
-----+-----+--------------------------------------------------------------------
1| 26|Filter より Seek(または Query)
-----+-----+--------------------------------------------------------------------
1| 10|ODBC SQL Statementsでは、なるべく Pass-Through を使用
-----+-----+--------------------------------------------------------------------
2| 43|Collectionsのリフレッシュは必要最低限に
-----+-----+--------------------------------------------------------------------
2| 27|Recordsets を再オープンするより、Queryを再実行
-----+-----+--------------------------------------------------------------------
2| 14|MyRS!fieldname より Field Objects(ループ処理内)
-----+-----+--------------------------------------------------------------------
2| 4|リモートデータには Dynaset より Snapshot(Memo形式に要注意)
-----+-----+--------------------------------------------------------------------
2| 13|Find より Bookmarks
-----+-----+--------------------------------------------------------------------
2| 23|Snapshot:全フィールド選択より、必要なフィールドのみ選択
-----+-----+--------------------------------------------------------------------
2| 7|Dynamic SQL Text より QueriesをDBに保存(特に少メモリマシン)
-----+-----+--------------------------------------------------------------------
2| 37|ODBC の場合、OLE型とMemo型は、SnapshotsよりDynasets
-----+-----+--------------------------------------------------------------------
2| 5|ODBC Snapshotに DB_FORWARDONLY を使用
-----+-----+--------------------------------------------------------------------
3| 15|変更が少ないテーブルはローカルMDBに保存
-----+-----+--------------------------------------------------------------------
3| 17|Tabledef と Field Property Collection Refsを多く参照するなら、キャッ
| |シュする
-----+-----+--------------------------------------------------------------------
3| 8|Dynamic SQL Text より Parameterize Queries(特にODBC)
-----+-----+--------------------------------------------------------------------
3| 11|DAO でループ処理するより、同等のSQL Statements
-----+-----+--------------------------------------------------------------------
3| 31|Collection-Basedコードを使う、旧来のListTablesなどの置き換え
-----+-----+--------------------------------------------------------------------
3| 41|浮動小数点数より整数型
-----+-----+--------------------------------------------------------------------
3| 36|モジュールを保存する前にコンパイル
-----+-----+--------------------------------------------------------------------
3| 35|新しいDynaset Recordsets をオープンするより、DynasetのClone
-----+-----+--------------------------------------------------------------------
3| 32|Explicit Code よりクエリーの 'Posting'が速い
-----+-----+--------------------------------------------------------------------
3| 22|短いMemo型と長いText型
-----+-----+--------------------------------------------------------------------
3| 28|Objectへの直接長い参照より、Dim Objects、Setで定義し、参照する
-----+-----+--------------------------------------------------------------------
4| 16|ODBC Time-Outs の減少によってのDAO戻りの高速化
-----+-----+--------------------------------------------------------------------
4| 34|Move 0 で Edit と AddNewの中止
-----+-----+--------------------------------------------------------------------
4| 18|ODBC Find の高速化:データをローカルのインデックス化されているテーブ
| |ルにダウンロードし、Seekを使用
-----+-----+--------------------------------------------------------------------
4| 19|繰り返し処理されるDynamic SQLの代わりに、臨時のQuery
-----+-----+--------------------------------------------------------------------
4| 33|現在のフィールド値更新は、MoveNext/Previousより Move 0
-----+-----+--------------------------------------------------------------------
5| 30|デフォルトコレクションを使用、余計なコレクション名指定は避ける
-----+-----+--------------------------------------------------------------------
5| 29|Text collection lookupを行うより、序数を使用
-----+-----+--------------------------------------------------------------------
5| 21|バリアント型より実際のデータ型
-----+-----+--------------------------------------------------------------------
5| 39|文字列型なら、Variant String Functions より "$" Functions
-----+-----+--------------------------------------------------------------------
(表3)ODBCを使わない場合に効果的な高速化手法
-----+-----+--------------------------------------------------------------------
効果 |番号 |手法
-----+-----+--------------------------------------------------------------------
1| 12|つねに DAO コードで Transactionsを束ねる
-----+-----+--------------------------------------------------------------------
1| 2|Dynaset,Snapshot より Table-Type Recordset
-----+-----+--------------------------------------------------------------------
1| 40|Sort より 明示的にIndexを指定(またはQuery)
-----+-----+--------------------------------------------------------------------
1| 20|Dynasetにレコードを追加するなら、DB_APPENDONLY を使用(特にODBC)
-----+-----+--------------------------------------------------------------------
1| 26|Filter より Seek(または Query)
-----+-----+--------------------------------------------------------------------
1| 10|ODBC SQL Statementsでは、なるべく Pass-Through を使用
-----+-----+--------------------------------------------------------------------
1| 1|Find より Seek
-----+-----+--------------------------------------------------------------------
2| 43|Collectionsのリフレッシュは必要最低限に
-----+-----+--------------------------------------------------------------------
2| 27|Recordsets を再オープンするより、Queryを再実行
-----+-----+--------------------------------------------------------------------
2| 14|MyRS!fieldname より Field Objects(ループ処理内)
-----+-----+--------------------------------------------------------------------
2| 13|Find より Bookmarks
-----+-----+--------------------------------------------------------------------
2| 23|Snapshot:全フィールド選択より、必要なフィールドのみ選択
-----+-----+--------------------------------------------------------------------
2| 7|Dynamic SQL Text より QueriesをDBに保存(特に少メモリマシン)
-----+-----+--------------------------------------------------------------------
2| 3|Databaseを直接オープンすることにより、直接アタッチ・テーブルをオープ
| |ンする
-----+-----+--------------------------------------------------------------------
2| 42|書き込みは一人だけの場合、DB_DENYWRITE フラグを使用
-----+-----+--------------------------------------------------------------------
3| 15|変更が少ないテーブルはローカルMDBに保存
-----+-----+--------------------------------------------------------------------
3| 17|Tabledef と Field Property Collection Refsを多く参照するなら、キャッ
| |シュする
-----+-----+--------------------------------------------------------------------
3| 8|Dynamic SQL Text より Parameterize Queries(特にODBC)
-----+-----+--------------------------------------------------------------------
3| 11|DAO でループ処理するより、同等のSQL Statements
-----+-----+--------------------------------------------------------------------
3| 31|Collection-Basedコードを使う、旧来のListTablesなどの置き換え
-----+-----+--------------------------------------------------------------------
3| 41|浮動小数点数より整数型
-----+-----+--------------------------------------------------------------------
3| 36|モジュールを保存する前にコンパイル
-----+-----+--------------------------------------------------------------------
3| 35|新しいDynaset Recordsets をオープンするより、DynasetのClone
-----+-----+--------------------------------------------------------------------
3| 22|短いMemo型と長いText型
-----+-----+--------------------------------------------------------------------
3| 32|Explicit Code よりクエリーの 'Posting'が速い
-----+-----+--------------------------------------------------------------------
3| 24|テーブル数が多い場合DAO Tabledef初期化時間を短縮するには、.MDBを分割
-----+-----+--------------------------------------------------------------------
3| 28|Objectへの直接長い参照より、Dim Objects、Setで定義し、参照する
-----+-----+--------------------------------------------------------------------
3| 25|DBCSを使用しないなら、Like より Instr()を試してみる
-----+-----+--------------------------------------------------------------------
4| 18|ODBC Find の高速化:データをローカルのインデックス化されているテーブ
| |ルにダウンロードし、Seekを使用
-----+-----+--------------------------------------------------------------------
4| 34|Move 0 で Edit と AddNewの中止
-----+-----+--------------------------------------------------------------------
4| 9|Open Database を Exclusive 全ユーザーが Read-Only なら Read-Only
-----+-----+--------------------------------------------------------------------
4| 19|繰り返し処理されるDynamic SQLの代わりに、臨時のQuery
-----+-----+--------------------------------------------------------------------
4| 33|現在のフィールド値更新は、MoveNext/Previousより Move 0
-----+-----+--------------------------------------------------------------------
5| 30|デフォルトコレクションを使用、余計なコレクション名指定は避ける
-----+-----+--------------------------------------------------------------------
5| 29|Text collection lookupを行うより、序数を使用
-----+-----+--------------------------------------------------------------------
5| 39|文字列型なら、Variant String Functions より "$" Functions
-----+-----+--------------------------------------------------------------------
5| 21|バリアント型より実際のデータ型
-----+-----+--------------------------------------------------------------------
おわりに
この記事を読んでいただいた皆様には、改めてVB4におけるDAOとは何かがお分か
りいただけたことでしょう。DAOイコールJetであること、そしてDAOこそがVB
4のデータアクセスにおける最適な選択であることとを知っていただけたことでしょう。
皆様が今後のアプリケーション開発にVB4とDAOを活用していただければと願ってお
ります。
さて、ここまでにDAOの高速化手法をいくつか紹介したわけですが、最後にと
っておきの手法を紹介しましょう。それは、「とにかくやって見てください」ということ
です。高速化の手法というものは、全てがいつでも効果的であるというわけではありませ
ん。ある状況では効果的な手法が、別の状況では逆効果ということもありますし、その逆
の場合もあります。従って、ここで紹介した手法は、「DAOを高速化することができる
可能性のある手法」といえます。また、皆さん独自の新しいアイディアもどんどん試して
みることをお勧めします。
コラム
この記事は、去る7月31日〜8月2日の3日間にわたりパシフィコ横浜国際会議場で開催さ
れたMicrosoft Tech*Ed 96で私が講演した「DAOを高速化する43の手法」の内容を基
に作成したものです。Tech*Ed(テック・エド)は、マイクロソフトが全世界で毎年開催
している最新技術の公開セミナーであり、日本で開催されるのは昨年に続いて、今回で2
回目となります。今回のTech*Ed 96は募集人員1700名のところ、当日受け付けも含めて約
1800名もの参加者を得ることができ、大成功を収めることができました。セッションの多
くは、ActiveX、Exchange、そしてWindows NT 4.0に関するものが多く、DAOをテーマと
した私の講演には、ほとんど人が集まらないのではないかと心配していました。ところが
いざ蓋を開けてみると500名程度の会場(Tech*Ed 96では、複数の会場で同時に複数のセッ
ションが行われる)はほぼ満員状態で、これほどまでにDAOに興味を持った人が多いも
のだったのかとびっくりしてしまいました。
講演の始めに「皆様の中で、すでにDAOを
使った経験のある方は手を挙げて下さい」と聞いたところ、20%程度の人たちが手を挙
げてくれました。やはりDAOはまだまだ普及していないと感じました。しかし、興味を
持っている人たちが多いことは分かりました。さて、Tech*Ed 96の最終日のすべての講演
終了後には、スタッフパーティーが開催されました。居並ぶ米国マイクロソフトの講演者
と並んで、私も記念写真を撮ってもらったり、記念品(外国人向けに浮世絵のTシャツで
した)をもらったりして、とても楽しい懇親の場を持つことができました。現在そのTシ
ャツを着てこの記事を書いています。では、最後になりましたが、講演資料を提供してい
ただいた米国マイクロソフト社のMichael Mee氏、お世話になった米国マイクロソフト社の
皆様、ならびにマイクロソフト株式会社の皆様、多くのスタッフの皆様、そして何よりご
参加いただいた皆様に心より感謝申し上げます。そして、読者の皆様とは、次回のTech*Ed
でお会いする日を楽しみにしています。
サンプルプログラムについて
この記事で紹介しているDAOの高速化手法には、すべてサンプルプログラムが用意さ
れており、それらはVisual Basic Magazine Vol.9 付録のCD−ROMに収録されています。
収録されているファイ
ルの内容は以下の通りです。サンプルグラムの実行方法については、fast43.docを参照し
て下さい。
fast43.doc ・・・・・ ドキュメントファイル(Word95形式)
fast42.mdb ・・・・・ Access95 MDBファイル
nwind.mdb ・・・・・ Access95 MDBファイル
なお、文化オリエント社のNiftyにおけるステーション(GO SBOC)には、Tech*Ed 96
で使用したサンプルプログラムのみならず、パワーポイント資料もアップロードされてい
ますので、ぜひ活用していただきたいと思います。
オンラインマガジン
int21 ホームページ |
PCDN ホームページ
Copyright (c) 1996 int21 Corporation All Rights Reserved.
For questions or comments, please send mail to: pcdn@int21.co.jp