SQLite 分表 - xiangwangfeng/xiangwangfeng.github.io GitHub Wiki
SQLite 分表
动机
以 IM APP 为例,常常产生几万,几十万条,甚至上百万条消息,单表状态下并不能很好的满足查询,拍个脑袋,将数据库按照联系人进行分表,等同于进行分区,能够有效加快后续针对单个联系人的查询速度。(其实并不完全对
)
理论分析
- 使用单表时,针对联系人的查询需要使用
联系人 + 时间戳
作为联合索引,而一旦分表后,搜索只需要使用时间戳即可,简化搜索。 SQLite
中所有的表信息都是通过B+ 树
进行存储,如果使用单表,在数据量大的情况下,会导致整个树深度过大,增加多余的 I/O 操作。而分表后,每个表在物理上被划分为多个区间,在空间上被内聚到邻近磁盘区间,数据内聚,减少 I/O 操作,加快查询速度。- 单表损坏不影响其他表信息
效果
分表后查询效果得到较大提升。
后续问题
分表后,当表数量超过千这个数量级后,对于内存和启动速度都会有较大影响。原因在于当数据库被打开后,整个 schema
都会被扫描并进行解析,最终形成一棵树结构并保存在内存中。当我们将单表拆分为多表时,对应的表信息和索引信息同样成比例增加,这就导致了数据库启动时解析时间的延长和内存占用的变大。
Whenever a database is opened, the entire schema is scanned and parsed and a parse tree for the schema is held in memory. That means that database connection startup time and initial memory usage is proportional to the size of the schema.
从 WeMobileDev
的数据来看,2000
张表大约需要 2-4 s
的解析时间。云信本身的测试数据而言, 10000
张表大约需要 5 s
左右。
结论
在能够合理控制表数量的前提下,分表是有效的。
一旦表数目超过千万级别后,分表带来的启动速度变慢将难以接受,启动时将扫描 sqlite_master
并解析,最终形成内存中的树结构。在其中由于 SQLite
默认的 rehash
实现中对桶数量有限制,而使得插入大量数据时效率从哈希表退化为链表插入效率,可以通过修改 SQLITE_MALLOC_SOFT_LIMIT
放大限制。另外一种改进做法则是在业务层进行规避,将不同联系人按照一定规则 hash
到有限数量(如上限 50)的数据库表中,仍使用 联系人 + 时间戳
作为联合索引。
或者仍推荐使用单表结构,但需要更加复杂的优化,如像 WeMobileDev
中 Android
的做法一样,通过将联系人 hash
成某个正整数,以减少索引长度,变现增加单个页内可容纳的信息量,减少页数目,减少搜索层级,加快搜索过程。