Cloudflare D1
SQL
Performance
Índices
Otimização

D1 中的慢查询:如何诊断和优化

如果没有适当的索引,100 行的查询在开发中看起来很快,但在 100,000 行的生产中却变得很慢,而且成本高昂,因为 D1 对读取的行收费,而不是对返回的行收费。

D1 中的慢查询:如何诊断和优化

SQLite 因其简单的数据库而享有盛誉,“仅此而已”。当它在桌面和移动应用程序中用作嵌入式数据库时,这种声誉是当之无愧的——数据集很小,查询规划器的工作很容易。在 D1 中,这种上下文发生了变化:包含数十万行的表、每个用户请求多个查询以及针对银行在查询执行期间读取的每一行而不是返回到应用程序的每一行收费的定价模型。生产中优化不佳的查询不仅速度慢,而且成本高,而且成本随着数据量的增加而线性增加。

EXPLAIN QUERY PLAN:优化前诊断

在创建任何索引或重写任何查询之前,请针对有问题的查询运行0。在 D1 上,您可以通过 1 远程执行此操作,或在本地使用 2 指向文件 3 执行此操作。

输出是查询规划器将执行的操作的列表。 “SCAN TABLE orders”是警告信号:它意味着数据库将扫描表中的所有行。 “SEARCH Orders USING INDEX idx_orders_user_id (user_id=?)”就是你想看到的:数据库使用索引直接查找相关行。 “SEARCH order USING INDEX idx_orders_user_date (user_id=?AND date>?)”表示复合索引正在用于相等过滤器和范围过滤器。

最常见的错误是在生产中出现问题后创建索引。 EXPLAIN QUERY PLAN 应该是开发过程的一部分 - 在第一次部署之前针对涉及超过几千行的表的每个查询运行。不必要的索引的成本是存储空间。生产中没有索引的查询成本以金钱来衡量。

在 D1 中生成表扫描的模式

四种重复出现的情况会导致全表扫描。第一个是最明显的:WHERE 中使用的列上缺少索引。在 5 上没有索引的 4 读取表中的所有行以查找用户的行。修正很简单:6。

第二种情况是WHERE和ORDER BY的组合没有被复合索引覆盖。 7可以使用8上的索引进行过滤,但随后它需要在内存中对结果进行排序 - 这个操作称为文件排序。 9 上的复合索引消除了文件排序,因为数据已在每个11 内按10 排序。

第三个是带有初始通配符的 LIKE。 12 不能使用任何索引:字符串开头的通配符会阻止数据库使用索引顺序来丢弃行。对于两侧通配符文本搜索,请使用 FTS5 虚拟表:13。带有 14 的 FTS5 查询已建立索引且可扩展。

第四是类型强制。 SQLite 使用类型亲和性 — 定义为 TEXT 的列可以存储整数,并且与 TEXT 列的 15° 比较可能不使用索引,具体取决于值的输入方式。在 SQLite 中,保持模式、插入值和查询之间的类型一致性比在严格类型的数据库中更重要。

D1中的N+1以及db.batch()的作用

N+1 问题在 D1 中有一个额外的维度:每个查询都是一个子请求,并且每个 Worker 调用的子请求限制为 1000 个。搜索 50 个请求,然后对每个请求中的项目执行 SELECT 操作的端点分别执行 51 个查询 — 51 个子请求,代价是 51 次往返数据库,每一次都会增加网络延迟到总响应时间。

16 通过将多个查询分组为一个子请求来解决这个问题。批处理中的所有查询都在数据库的单次往返中执行。结果是一个数组,每个查询包含一个元素,其顺序与发送的顺序相同。对于订单和商品模式,批处理包含:订单查询和涵盖所有订单 ID 的 17 查询。总共两个子请求,无论返回多少个请求。

支持 D1 的 ORM(例如具有本机集成的 Drizzle ORM)具有急切加载选项,可以使用 JOIN 或批处理(而不是 N+1)自动构建查询。然而,大多数 ORM 的默认行为会生成 N+1,除非您显式配置预先加载。在投入生产之前检查开发环境中使用18生成的 SQL 是识别这些模式的最直接方法。

没有索引的实际成本:数字示例

拥有20万订单的D1银行。在 20 中没有索引的查询 19 执行全表扫描:读取 20 万行,返回 20 行。按每百万次读取 0.001 美元计算,此查询的每次执行成本为 0.0002 美元。

该端点每天执行 100,000 次(对于通过轮询或操作仪表板更新的面板来说很常见),仅此查询的成本为每天 20 美元,每月 600 美元。复合索引21 完全改变了计划:数据库使用该索引仅读取22 的记录,并且已按23 排序。假设有 5000 个待处理请求,该查询读取 5000 行,返回 20 行,每次执行成本为 0.000005 美元。如果每天运行 10 万美元,则成本降至每天 0.50 美元,每月 15 美元。

对于 200K 行,索引本身大约占用 5-10MB。按每月 0.75 美元/GB 计算,该存储每月的成本不到 0.01 美元。 600 美元/月和 15 美元/月的阅读成本之间的差异,对于存储投资的一小部分来说,是一种永远不应该推迟到“流量增长之后”的优化——因为当流量增长时,成本就已经产生了。

另请阅读

  • [生产中的 D1:性能、限制以及无法单独扩展的因素24
  • 【应用中的电池消耗:如何优化移动性能25
  • [软件性能:开始优化的基本步骤26
  • [SQL数据库优化:索引、分区和调优27
  • 【移动性能优化:完整指南28
  • 【移动端性能优化-初学者实例29