如何高效处理数十亿固定长度字符串的磁盘式去重?
针对固定长度字符串批量去重的高效存储方案优化
先优化现有SQLite方案(低成本快速见效)
如果不想替换现有方案,先把SQLite的性能潜力挖尽:
- 关闭自动事务提交:默认单条
INSERT就是一个事务,磁盘IO开销极大。改成每1000-10000条批量提交一次事务,能直接把写入速度提几倍。 - 调整核心参数:
- 开启WAL模式:
PRAGMA journal_mode = WAL;,比默认的DELETE模式写入性能更高,还支持并发读。 - 降低同步级别:
PRAGMA synchronous = NORMAL;,牺牲一点崩溃安全性换写入速度——反正你的数据可以重新生成,这点风险完全可控。 - 加大缓存:
PRAGMA cache_size = -2000;(代表分配2GB内存缓存),减少磁盘IO的触发频率。
- 开启WAL模式:
- 用批量插入代替单条:把
INSERT OR IGNORE改成批量语法:INSERT OR IGNORE INTO tbl VALUES ('str1'), ('str2'), ...;,一次插入几十上百条,减少SQL解析和磁盘IO次数。
专用磁盘级去重方案(比SQLite更高效)
1. 布隆过滤器+哈希分块文件组合
- 前置用布隆过滤器快速过滤:1-2亿条数据,误判率0.1%的话,只需要约2.3GB内存就能构建布隆过滤器,先把大概率已存在的字符串过滤掉,减少后续磁盘查询次数。布隆过滤器可以用磁盘持久化的实现,重启后不用重新构建。
- 哈希分块存储:把字符串哈希后分成1024个左右的文件,每个文件存储对应哈希分区的唯一字符串。判重时先哈希找到对应文件,再用二分查找(保持文件内字符串有序)判断是否存在,不存在就追加写入。这种方式没有数据库的事务、日志开销,IO效率比SQLite高很多。
2. LevelDB/RocksDB键值存储
- 这类写优化的键值存储天生适合你这种“只写、键唯一”的场景。把字符串作为Key,Value随便填个固定值(比如
1)。 - 判重时先调用
Get查询,不存在就调用Put写入;或者直接Put,重复Key会自动覆盖(但会有无效写入,建议先查再写)。 - LSM树结构的写入是追加式的,避免了SQLite B树的随机写入开销,性能比SQLite高一个量级,而且配置灵活,能根据硬件调整内存、压缩策略等参数。
3. 自定义哈希分块文件+内存缓存
- 针对固定长度字符串完全定制:
- 用xxHash(比MD5快几十倍)对字符串计算哈希,按哈希前几位分成1024个文件。
- 每个文件内部存储该分区的字符串哈希值(或原字符串),同时用LRU缓存每个文件的最近访问哈希集合。
- 判重时先查缓存,缓存没命中再打开对应文件查找,不存在就追加写入。
- 这种方式没有任何冗余开销,性能拉满,但需要自己写代码实现,适合极致性能需求的场景。
4. Berkeley DB哈希模式
- 老牌嵌入式键值存储,专门支持哈希模式,插入和查找性能都很高,比SQLite更轻量化,没有SQL解析的额外开销,配置简单,适合快速替换现有方案。
选择建议
- 低成本改法:先优化SQLite的参数和批量操作,快速提性能。
- 平衡性能和开发成本:选LevelDB/RocksDB,成熟稳定,代码改动小。
- 极致性能:自己实现布隆过滤器+哈希分块文件的组合。
内容的提问来源于stack exchange,提问作者Gordon Royle
相关产品推荐
相关产品推荐

