大数跨境

使用哈希键和索引在Node.js中加快SQLite查找速度

使用哈希键和索引在Node.js中加快SQLite查找速度 索引目录
2025-11-27
1
导读:关注「索引目录」公众号,获取更多干货。有一个显而易见的瓶颈——我们搜索行的方式。

关注「索引目录」公众号,获取更多干货。

有一个显而易见的瓶颈——我们搜索行的方式

事实证明,只需重新思考查找操作的设计方式,就能大幅提升性能WHERE。无需重写业务逻辑,也无需更换数据库,只需改变搜索键的形式即可。

是啊,它的速度提升得如此之快,简直令人难以置信。

真正的痛苦

许多应用程序都有类似这样的查询:

SELECT * FROM table
WHERE a = ? AND b = ? AND c = ? AND d = ?;

你一开始只有一个条件,然后又添加了两个条件,突然间,每次查找都需要比较多个字符串或文本字段。

小规模使用时没问题。但一旦读取流量增加或并行运行查询,性能就会急剧下降,因为 SQLite 必须:

  • 在关键比较中权衡多个值
  • 比较可变长度文本
  • 深入探索 B 树的更深层节点。
  • 存储更大的键 → 每页键数更少 → 更多 B 树层级和比较次数。

所以我尝试了一种不同的方法:将多个标识字段打包到一个哈希列中,并对该哈希列进行索引。

这并非什么高深莫测的科学——但却是真正意义上的变革。

诀窍在于:将标识字段哈希到一个整数中

我没有按(cluster, name)字符串搜索,而是构建了一个类似 URL 的字符串并对其进行哈希处理:

export function hashUrlToKey(url: string): string {
  const hash = crypto.createHash('sha256').update(url).digest();
  return hash.readBigInt64BE(0).toString(); // 8 bytes → BIGINT
}

现在,不再是:

WHERE cluster=? AND name=?

我们确实这样做:

WHERE url_hash = ?

在该列上创建索引/主键(最好如此WITHOUT ROWID),然后——SQLite 每一步只比较一个 8 字节的整数,而不是多个文本值。

基准测试:对约 52k 行的表进行 50,000 次查找

我们运行了两次完全相同的基准测试——一次使用哈希 BIGINT 键查找,一次使用复合文本查找。其他条件均未改变。

哈希查找 — url_hash(BIGINT,不含 ROWID)

╔════════════════════════════════════════════════════════════════════╗
║                      SVG Icon URL Lookup Benchmark                ║
╚════════════════════════════════════════════════════════════════════╝

📋 Configuration:
   Icon rows:           51,898
   URL entries:         51,898
   Total queries:       50,000 (fixed)
   Worker configs:      [1, 2]
   CPU pinning:         Workers on CPU [0, 1]
   Hash algorithm:      SHA256 (first 8 bytes as INTEGER)
   Table structure:     WITHOUT ROWID (clustered by url_hash)

⚡ Running benchmarks...

   2p × 1w (2 workers, 25000 queries each) ... ✓ 0.670s
   2p × 2w (4 workers, 12500 queries each) ... ✓ 0.824s

══════════════════════════════════════════════════════════════════════════════════════════════════════════════
📈 RESULTS - SVG ICON URL LOOKUP PERFORMANCE
══════════════════════════════════════════════════════════════════════════════════════════════════════════════

┌──────┬────────────┬──────────┬──────────┬──────────────┬──────────────┬─────────────┬──────────────┐
│ Rank │ Config     │ Proc×Wrk │ Queries  │ Duration (s) │ QPS          │ Avg Query   │ vs Slowest   │
├──────┼────────────┼──────────┼──────────┼──────────────┼──────────────┼─────────────┼──────────────┤
│    1 │ 2p-1w      │      2×1 │   50,000 │        0.670 │       74,680 │     13.39µs │        1.23x │
│    2 │ 2p-2w      │      2×2 │   50,000 │        0.824 │       60,680 │     16.48µs │        1.00x │
└──────┴────────────┴──────────┴──────────┴──────────────┴──────────────┴─────────────┴──────────────┘

📌 Summary:
   🎯 Workload:        50,000 URL lookups
   ⚡ Best config:     2p-1w → 0.670s
   🚀 Peak QPS:        74,680.4 queries/second
   ⏱️ Avg lookup:      13.39µs per URL
   ✅ Performance validated

复合字符串查找 — cluster + name(文本 + 文本)

╔════════════════════════════════════════════════════════════════════╗
║                      SVG Icon URL Lookup Benchmark                ║
╚════════════════════════════════════════════════════════════════════╝

📋 Configuration:
   Icon rows:           51,898
   URL entries:         51,898
   Total queries:       50,000 (fixed)
   Worker configs:      [1, 2]
   CPU pinning:         Workers on CPU [0, 1]
   Lookup strategy:     Cluster + name equality lookup
   Table structure:     WITHOUT ROWID (cluster + name columns)

⚡ Running benchmarks...

   2p × 1w (2 workers, 25000 queries each) ... ✓ 1.012s
   2p × 2w (4 workers, 12500 queries each) ... ✓ 1.112s

══════════════════════════════════════════════════════════════════════════════════════════════════════════════
📈 RESULTS - SVG ICON URL LOOKUP PERFORMANCE
══════════════════════════════════════════════════════════════════════════════════════════════════════════════

┌──────┬────────────┬──────────┬──────────┬──────────────┬──────────────┬─────────────┬──────────────┐
│ Rank │ Config     │ Proc×Wrk │ Queries  │ Duration (s) │ QPS          │ Avg Query   │ vs Slowest   │
├──────┼────────────┼──────────┼──────────┼──────────────┼──────────────┼─────────────┼──────────────┤
│    1 │ 2p-1w      │      2×1 │   50,000 │        1.012 │       49,391 │     20.25µs │        1.10x │
│    2 │ 2p-2w      │      2×2 │   50,000 │        1.112 │       44,975 │     22.23µs │        1.00x │
└──────┴────────────┴──────────┴──────────┴──────────────┴──────────────┴─────────────┴──────────────┘

📌 Summary:
   🎯 Workload:        50,000 URL lookups
   ⚡ Best config:     2p-1w → 1.012s
   🚀 Peak QPS:        49,391.36 queries/second
   ⏱️ Avg lookup:      20.25µs per URL

TL;DR 比较

同一台机器,
同一张表,
相同的查询次数,
相同的工作线程,
相同的 WAL 模式

唯一区别:
url_hash = 一个 8 字节整数;
cluster+name = 两个文本比较

结果:
实际应用中,BIGINT 查找速度提升了约 1.5 倍。

为什么哈希算法有帮助

SQLite索引是B树。搜索成本是O(n^2) ,但每一步O(log N)比较的内容至关重要。


复合文本键
哈希大整数密钥
每行多次比较
一项比较
可变长度
固定 8 字节
每页按键数量更少
每页更多按键
更多页面阅读量
阅读量较少
较慢的CPU对比
单指令比较


这不是魔法——只是降低你已经使用的结构中每个步骤的成本。

如果你的应用已经有一些类似于身份字段的条件(例如 user_id + device_id + type、cluster + name + version 等),就不要再单独处理它们了。把它们合并到一个哈希表中并建立索引。

并行负载也很重要。

当运行 2 个进程 × 多个工作进程猛烈攻击数据库时(就像昨天的帖子中那样),这种改进就更加明显了。

使用多列文本查找时,工作进程会更频繁地争用 CPU 资源;而使用哈希整数查找时,它们则能轻松应对。

这就是两者的区别:

  • CPU正忙于执行有用的二叉树遍历
  • CPU 浪费周期比较字符串。

真正有价值的收获(而非空谈理论)

这不仅仅关乎图标或网址路径。

如果您有 3-4 个始终同时出现的 WHERE 等式条件,请将它们哈希到一个合成键中,并对该列建立索引。

它:

  • 简化每次查找
  • 缩小索引页
  • 提高缓存密度
  • 显著提高读取并发速度

而且你不需要重新设计整个数据库。
你只需要添加列和一个索引。

最坏的情况是,一切照旧。
最好的情况是,你可以像我刚才那样获得免费的性能提升。

尝试以下情况:

✔ 查找基于相等性,而非范围
✔ WHERE 子句中多个字段始终同时出现
✔ 您正在进行大量读取操作或并发操作
✔ 您的键是长字符串或 UUID

结语

人们总是喜欢争论 SQLite 和 Postgres 哪个更好。但实际上,在 SQLite 中,只需添加一个存储哈希值的列,
就能获得50% 以上的性能提升。

关注「索引目录」公众号,获取更多干货。


【声明】内容源于网络
0
0
索引目录
索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
内容 444
粉丝 0
索引目录 索引目录是一家专注于医疗、技术开发、物联网应用等领域的创新型公司。我们致力于为客户提供高质量的服务和解决方案,推动技术与行业发展。
总阅读12
粉丝0
内容444