この記事で分かること
- インデックスが正常に動作しない原因となるNGな検索条件
- クエリの記述を工夫して検索パフォーマンスを最大化する手法
- 複合インデックスを適切に機能させるためのテーブル設計のルール
Accessの処理遅延を引き起こすインデックスと検索条件のミスマッチ
データの件数が数百件程度であれば、クエリの書き方が効率的でなくても瞬時に結果が返ってきます。しかし、レコード数が数万、数十万件と増えていくにつれて、不適切な検索条件はシステムの致命的な遅延(ボトルネック)へと発展します。
データベースに設定する「インデックス」は、書籍の索引のような役割を果たします。索引があれば、該当するページを直接開くことができますが、もし索引の探し方と本文の検索方法が一致していなければ、結局は最初から最後までページを1枚ずつめくって探すしかありません。この「全件探索」の状態をデータベース用語で「フルテーブルスキャン(またはフルスキャン)」と呼びます。
「動作が重いからインデックスを追加する」という対処療法的なアプローチでは、このミスマッチを解消できません。むしろ、不要なインデックスを増やすことでデータの追加や更新処理が余計に遅くなる二次被害を招く恐れがあります。まずは、現在のクエリがインデックスを正しく活用できる状態になっているかを検証することが重要です。
インデックスを無効化する代表的な検索条件
インデックスを設定していても、SQLやクエリのデザインビューで以下のような検索条件を指定している場合、Accessはインデックスを無視してフルテーブルスキャンを実行します。
1. 部分一致・後方一致のワイルドカード検索
もっとも頻出する原因が、Like演算子を用いた曖昧検索の使い方です。たとえば、以下のような条件指定です。
Like "*株式会社*" (中間一致)
Like "*東京都" (後方一致)
インデックスは「前方からの文字の並び順」を整理して格納しているため、先頭にワイルドカード(*)がある場合はインデックスを利用した並び替えやスキップができません。インデックスを有効にするためには、必ず先頭の文字が確定している「前方一致(例: Like "株式会社*")」である必要があります。
2. 検索フィールドに対する関数の使用
抽出条件の左辺(対象フィールド)に対して関数を適用すると、Accessはすべてのレコードに対してその関数を実行した上で比較処理を行います。そのため、インデックスが一切機能しなくなります。
【NG例】
Where Year([売上日]) = 2023
Where IsNull([出荷日]) = True
上記のような条件指定は、インデックスが設定された「売上日」列であっても無効化されます。これを回避するための正しいクエリの記述方法については、後述のセクションで詳しく解説します。
3. 異なるデータ型同士の比較(暗黙の型変換)
テーブルのフィールド定義と、検索値のデータ型が異なっている場合も注意が必要です。たとえば、テキスト型として定義されている「社員コード」に対して、数値として条件を与えた場合です。
【NG例】
Where [社員コード] = 10520 (テキスト型フィールドに数値を指定)
この場合、Accessの内部エンジンは比較を行うためにすべてのレコードの値を自動的に変換(暗黙の型変換)しようとします。その結果、インデックスの構造が無視され、スキャンが発生して処理が著しく遅延します。
検索効率を劇的に改善するクエリの書き換えとテーブル設計
無効化されていたインデックスを有効化し、処理を劇的に高速化するための具体的な書き換えテクニックと、インデックスを最大限に活かすテーブル設計について解説します。
関数の排除と範囲指定(Between)への置き換え
前述の「売上日」から特定の年(2023年)を抽出する処理を高速化するには、フィールドに関数を噛ませるのではなく、日付そのものを範囲指定で比較する記述へと書き換えます。
【高速化される記述例】
Where [売上日] Between #2023/01/01# And #2023/12/31#
この記述であれば、「売上日」に張られたインデックスがフルに活用され、対象期間のデータだけをピンポイントで瞬時に取り出すことが可能になります。
複合インデックスにおける「左側優先」のルール
複数のフィールドを組み合わせて検索条件に指定する場合、単一列のインデックスではなく、複数列をまとめた「複合インデックス」が有効です。ただし、この設計には厳密な順序ルール(左側優先の法則)が存在します。
たとえば、「部署コード」と「役職コード」の2つの列に対して複合インデックスを作成する場合、並び順を「部署コード > 役職コード」とします。このとき、クエリの検索条件として以下のような挙動を示します。
| 検索条件のパターン | インデックスの適用状況 | 動作の詳細 |
|---|---|---|
| 「部署コード」のみで検索 | 有効 | インデックスの第1キー(左側)と一致するため高速に動作します。 |
| 「部署コード」と「役職コード」の両方で検索 | 有効 | すべてのキー条件が合致するため、最も高速な探索が行われます。 |
| 「役職コード」のみで検索 | 無効 | 第1キー(左側)が抜けているため、インデックスは一切使われません。 |
このように、複合インデックスを設定する際は、「必ず検索条件に指定される優先度の高い列」をインデックスの定義順で最も左側(1番目)に配置するように設計してください。
実務でのトラブル解決事例:30万件の売上明細クエリの改善
ここからは、実際に多くの業務現場で発生しがちなトラブル事例と、それを解決するための具体的な手順を紹介します。
トラブルの状況
約30万件のレコードを保持する「売上明細テーブル」から、特定の店舗かつ特定の月に発生した売上データを集計するクエリを実行したところ、処理が完了するまでに毎回20秒以上の砂時計状態(応答なし)が発生していました。
既存の設計を確認したところ、「店舗コード」と「売上日」にはそれぞれ単一のインデックスが設定されていましたが、抽出クエリは以下のように実装されていました。
SELECT * FROM 売上明細
WHERE [店舗コード] = 'T001' AND Format([売上日], 'yyyy/mm') = '2023/10';
解決へのステップ
この問題を解消するため、以下のステップに沿って改修を行いました。
- データのバックアップ:改修作業に入る前に、必ずAccessファイル(accdb)のコピーを別フォルダに保存します。
- クエリの書き換え:
Format関数による日付の文字列化を止め、範囲指定に変更します。SELECT * FROM 売上明細 WHERE [店舗コード] = 'T001' AND [売上日] Between #2023/10/01# And #2023/10/31#; - 複合インデックスの新規作成:今回の検索パターンに合わせて、テーブルのデザインビューから「店舗コード」と「売上日」をこの順番で並べた複合インデックスを新規に定義します。
- 不要なインデックスの整理:過去に個別設定されていた「売上日」単体のインデックスは不要になったため削除し、テーブル書き込み時のオーバーヘッド(負荷)を削減します。
改善前後のパフォーマンス比較
改善を行った結果、クエリの実行速度は以下のように劇的に向上しました。
| 評価項目 | 改善前(関数処理 + 個別インデックス) | 改善後(範囲指定 + 複合インデックス) |
|---|---|---|
| クエリ処理速度 | 約22.5秒 | 0.2秒以下(即時表示) |
| CPU負荷率 | 一時的に100%近くまで急上昇 | ほぼ負荷なし(数%以下) |
| ファイルサイズ変化 | 肥大化しやすい(一時領域の消費) | 安定(ディスクI/Oの大幅な抑制) |
パフォーマンスを最適に維持するための運用の注意点
インデックスとクエリの設計を最適化した後も、Accessの特性に合わせた運用管理を怠ると、時間の経過とともに再び速度が低下することがあります。
1. データベースの最適化と修復の自動化・定期実行
Accessはデータの追加・更新・削除を繰り返すと、ファイル内部に不要な一時領域が残り、データベースファイル(.accdb)のサイズが不必要に肥大化していきます。これにより、インデックス構造の「断片化」が発生し、検索パフォーマンスが低下します。
これを防ぐために、定期的に「データベースの最適化と修復」機能を手動で実行するか、オプションから「閉じるときに最適化する」の設定を有効にしておくことを推奨します。
2. ネットワーク経由での利用における制約の理解
Accessファイルを社内の共有フォルダ(NASなど)に配置し、複数ユーザーで同時に利用している場合、ネットワークの通信速度が最大のボトルネックになります。
クエリで「フルテーブルスキャン」が発生すると、ネットワークを通じて数十万件の全データがクライアントPCに転送されることになり、ネットワーク帯域が圧迫され、他のユーザーの動作まで極端に重くなります。インデックスを正確に機能させることは、自PCの処理だけでなく、ネットワーク全体のトラフィック削減にも直結する極めて重要な取り組みです。
Q. インデックスを設定しすぎるとどのようなデメリットがありますか?
インデックスを増やすと、データの検索速度は向上しますが、レコードの新規追加(INSERT)、更新(UPDATE)、削除(DELETE)の処理速度が低下します。これは、データを書き換えるたびにAccessの内部でインデックスデータの再構築(並び替え処理)が実行されるためです。検索で実際に使われているキーのみに絞ってインデックスを構成することが重要です。
Q. SQL ServerやMySQLなどの外部データベース(リンクテーブル)でもインデックスの考え方は同じですか?
はい、基本的なインデックスの仕組みは共通しています。ただし、リンクテーブルを利用している場合、クエリの記述が不適切だとAccess側で全件データを一度ダウンロードしてからフィルタリングを行う「クライアントサイド処理」が発生し、パフォーマンスが壊滅的に低下します。リンク先(サーバー側)で処理を完結させる「パススルークエリ」の利用なども併せて検討してください。
Q. Null値が含まれるフィールドにインデックスを設定しても機能しますか?
Access(Jet/ACEエンジン)では、Null値に対してもインデックスを設定することができます。ただし、「Is Null」の条件で検索を行う際、インデックスプロパティの「Nullを無視(IgnoreNulls)」設定が「はい」になっていると、Nullレコードの検索にインデックスが使用されなくなります。Nullを多く含む列を検索キーにする場合は、この設定項目を確認してください。