コンテンツにスキップ

インデックスの使用

インデックスは、D1が一般的な(人気のある)クエリに対してインデックスされた列のクエリパフォーマンスを向上させることを可能にします。これは、クエリを実行する際にデータベースがスキャンしなければならないデータ(行数)を減らすことによって実現されます。

インデックスはいつ役立つか?

インデックスは以下のような場合に役立ちます:

  • 条件式で定期的に使用される列の読み取りパフォーマンスを向上させたいとき - 例えば、WHERE email_address = ?WHERE user_id = 'a793b483-df87-43a8-a057-e5286d3537c5'などです。メールアドレス、ユーザー名、ユーザーID、または日付は、典型的なWebアプリケーションやサービスでインデックスを作成するのに適した列の選択肢です。
  • 列または列の一部に一意性制約を強制するため - 例えば、CREATE UNIQUE INDEXを使用してメールアドレスやユーザーIDに対して。
  • 複数の列を一緒にクエリする場合 - (customer_id, transaction_date)

インデックスは、参照されるテーブルおよび列が挿入、更新、または削除されると自動的に更新されます。参照されるテーブルに書き込んだ後にインデックスを手動で更新する必要はありません。

インデックスの作成

D1テーブルにインデックスを作成するには、CREATE INDEX SQLコマンドを使用し、インデックスを作成するテーブルと列を指定します。

例えば、以下のordersテーブルがあるとします。customer_idにインデックスを作成したい場合があります。ほとんどのクエリがそのテーブルに対してcustomer_idでフィルタリングされており、インデックスを作成することでパフォーマンスが向上します。

CREATE TABLE IF NOT EXISTS orders (
order_id INTEGER PRIMARY KEY,
customer_id STRING NOT NULL, -- 例えば、一意のID aba0e360-1e04-41b3-91a0-1f2263e1e0fb
order_date STRING NOT NULL,
status INTEGER NOT NULL,
last_updated_date STRING NOT NULL
)

customer_id列にインデックスを作成するには、以下のステートメントをデータベースに対して実行します:

CREATE INDEX IF NOT EXISTS idx_orders_customer_id ON orders(customer_id)

customer_id列を参照するクエリは、今後インデックスの恩恵を受けることができます:

-- インデックスを使用:インデックスされた列がクエリで参照されています。
SELECT * FROM orders WHERE customer_id = ?
-- インデックスを使用しない:customer_idがクエリに含まれていません。
SELECT * FROM orders WHERE order_date = '2023-05-01'

より複雑なケースでは、D1がインデックスを使用したかどうかをクエリを分析することで確認できます。

インデックスの一覧表示

データベース内のインデックスとSQL定義を、sqlite_schemaシステムテーブルをクエリすることで一覧表示します:

SELECT name, type, sql FROM sqlite_schema WHERE type IN ('index');

これにより、以下のような出力が返されます:

┌──────────────────────────────────┬───────┬────────────────────────────────────────┐
│ name │ type │ sql │
├──────────────────────────────────┼───────┼────────────────────────────────────────┤
│ idx_users_id │ index │ CREATE INDEX idx_users_id ON users(id) │
└──────────────────────────────────┴───────┴────────────────────────────────────────┘

このテーブルや既存のインデックスを変更することはできません。インデックスを変更するには、最初に削除し、新しいインデックスを作成する必要があります。

インデックスのテスト

クエリがインデックスを使用したかどうかを確認するには、クエリの前にEXPLAIN QUERY PLANを追加します。これにより、次のステートメントのクエリプランが出力され、どのインデックスが使用されたか(もしあれば)が示されます。

例えば、usersテーブルにemail_address TEXT列があり、CREATE UNIQUE INDEX idx_email_address ON users(email_address)というインデックスを作成したと仮定します。email_addressに対する条件を持つクエリは、インデックスを使用するはずです。

EXPLAIN QUERY PLAN SELECT * FROM users WHERE email_address = 'foo@example.com';
QUERY PLAN
`--SEARCH users USING INDEX idx_email_address (email_address=?)

クエリプランナーからのUSING INDEX <INDEX_NAME>出力を確認し、インデックスが使用されたことを確認します。

これはインデックスの一般的な使用ケースでもあります。ユーザーのメールアドレスに基づいてユーザーを見つけることは、ログイン(認証)システムにとって非常に一般的なクエリタイプです。

複数列インデックス

複数列インデックス(複数の列を指定するインデックス)の場合、クエリはすべての列を指定するか、左側の列がすべてクエリに含まれている場合にのみインデックスを使用します。

CREATE INDEX idx_customer_id_transaction_date ON transactions(customer_id, transaction_date)というインデックスがある場合、以下の表はインデックスが使用される場合(または使用されない場合)を示しています:

クエリインデックス使用?
SELECT * FROM transactions WHERE customer_id = '1234' AND transaction_date = '2023-03-25'はい:インデックス内の両方の列を指定しています。
SELECT * FROM transactions WHERE transaction_date = '2023-03-28'いいえ:transaction_dateのみを指定しており、インデックスの他の左側の列を含んでいません。
SELECT * FROM transactions WHERE customer_id = '56789'はい:インデックスの左側の列であるcustomer_idを指定しています。

注意:

  • もし3つの列に対してインデックスを作成した場合 — customer_idtransaction_date、およびshipping_statuscustomer_idtransaction_dateの両方を使用するクエリはインデックスを使用します。なぜなら、すべての「左側」の列を含んでいるからです。
  • 同じインデックスで、transaction_dateshipping_statusのみを使用するクエリはインデックスを使用しません。なぜなら、クエリにcustomer_id(左側の列)を使用していないからです。

部分インデックス

部分インデックスは、テーブル内の行のサブセットに対するインデックスです。部分インデックスは、インデックスを作成する際にWHERE句を使用することで定義されます。部分インデックスは、NULLの値を持つ行や、特定の値を持つ行をクエリから除外するのに役立ちます。

  • 部分インデックスの具体例は、order_status INTEGER列を持つテーブルに対して、6がアプリケーションコード内で「注文完了」を表す場合です。
  • これにより、まだ履行されていない、出荷されていない、または進行中の注文に対するクエリが可能になり、これらはおそらく最も一般的なユーザー(注文状況を確認しているユーザー)となります。
  • 部分インデックスは、インデックスが時間とともに無限に成長するのを防ぎます。インデックスは、すべての完了した注文の行を保持する必要がなく、完了した注文は進行中の注文よりもはるかに少ない回数クエリされる可能性があります。

完了した注文をインデックスから除外する部分インデックスは、以下のようになります:

CREATE INDEX idx_order_status_not_complete ON orders(order_status) WHERE order_status != 6

部分インデックスは、読み取り時(インデックス内の行が少ない)および書き込み時(インデックスへの書き込みが少ない)にフルインデックスよりも高速である可能性があります。また、部分インデックスを複数列インデックスと組み合わせることもできます。

インデックスの削除

インデックスを削除するには、DROP INDEXを使用します。削除されたインデックスは復元できません。

考慮事項

インデックスを作成する際の考慮事項は以下の通りです:

  • インデックスは常に無料のパフォーマンス向上をもたらすわけではありません。最もクエリされる列を反映する列にのみインデックスを作成するべきです。インデックス自体も維持する必要があります。インデックスされた列に書き込むと、データベースはテーブルとインデックスの両方に書き込む必要があります。インデックスのパフォーマンス向上と読み取る行数の削減は、ほとんどの場合、この追加の書き込みを相殺します。
  • 他のテーブルを参照するインデックスや非決定的関数を使用するインデックスを作成することはできません。なぜなら、インデックスが安定しないからです。
  • インデックスは更新できません。インデックスから列を追加または削除するには、インデックスを削除し、新しい列を持つ新しいインデックスを作成する必要があります。
  • インデックスはデータベースが必要とする全体のストレージに寄与します:インデックスは実質的にテーブル自体です。