跳转到内容
搜索文档

使用索引

最后更新 查看 MarkdownAgent 设置

索引使 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, -- for example, a unique 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 列的查询现在将受益于该索引:

-- Uses the index: the indexed column is referenced by the query.
SELECT * FROM orders WHERE customer_id = ?

-- Does not use the index: customer_id is not in the query.
SELECT * FROM orders WHERE order_date = '2023-05-01'

在更复杂的情况下,您可以通过直接分析查询来确认 D1 是否使用了索引。

运行 PRAGMA optimize

创建索引后,运行 PRAGMA optimize 命令以提升数据库性能。

PRAGMA optimize 对数据库中的每个表运行 ANALYZE 命令,收集表和索引的统计信息。这些统计信息使查询规划器在执行用户查询时生成最高效的查询计划。

有关更多信息,请参阅 PRAGMA optimize

列出索引

通过查询 sqlite_schema 系统表来列出数据库上的索引及其 SQL 定义:

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 = '[email protected]';
QUERY PLAN
`--SEARCH users USING INDEX idx_email_address (email_address=?)

查看查询规划器输出的 USING INDEX <INDEX_NAME>,确认已使用索引。

这也是索引的一个相当常见的用例。根据电子邮件地址查找用户通常是登录(身份验证)系统中非常常见的查询类型。

使用索引可以减少查询读取的行数。使用 meta 对象估算您的用量。请参阅"能否使用索引减少查询读取的行数?""如何估算我的(最终)账单?"

多列索引

对于多列索引(指定多个列的索引),仅当查询指定所有列,或指定列的子集且索引中左侧的所有列也包含在查询中时,查询才会使用该索引。

给定索引 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,即索引中最左侧的列。

说明:

  • 如果您创建包含三列的索引——customer_idtransaction_dateshipping_status——同时使用 customer_idtransaction_date 的查询将使用该索引,因为您包含了所有"左侧"列。
  • 使用同一索引时,仅使用 transaction_dateshipping_status 的查询不会使用该索引,因为您未在查询中使用 customer_id(最左侧列)。

部分索引

部分索引是对表中行子集建立的索引。部分索引通过创建索引时使用 WHERE 子句来定义。部分索引可用于省略某些行,例如值为 NULL 的行,或查询中经常出现特定值的行。

  • 部分索引的一个具体示例是包含 order_status INTEGER 列的表,其中 6 可能代表应用代码中的 "order complete"(订单已完成)。
  • 这允许查询尚未履行、已发货或进行中的订单,这些通常是用户最常见的查询场景(用户查看订单状态)。
  • 部分索引还防止索引随时间无限增长。索引无需为每个已完成订单保留一行,而已完成的订单被查询的频率通常远低于进行中的订单。

过滤已完成订单的部分索引如下所示:

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

部分索引在读取时(索引中行更少)和写入时(对索引的写入更少)都可能比完整索引更快。您还可以将部分索引与多列索引结合使用。

删除索引

使用 DROP INDEX 删除索引。已删除的索引无法恢复。

注意事项

创建索引时请注意以下事项:

  • 索引并非总是免费的性能提升。您应仅在反映最常查询的列上创建索引。索引本身需要维护。当您写入已索引的列时,数据库需要同时写入表和索引。在几乎所有情况下,索引带来的性能提升和读取行数减少都会抵消这一额外写入。
  • 您无法创建引用其他表或使用非确定性函数的索引,因为索引不会稳定。
  • 索引无法更新。要从索引中添加或删除列,请删除索引,然后创建新索引并定义新列。
  • 索引会增加数据库所需的总体存储空间:索引实际上也是一张表。

这篇文档对您有帮助吗?