Skip to content

SQLite 分表

阿毛 edited this page Apr 30, 2018 · 1 revision

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)的数据库表中,仍使用 联系人 + 时间戳 作为联合索引。

或者仍推荐使用单表结构,但需要更加复杂的优化,如像 WeMobileDevAndroid的做法一样,通过将联系人 hash 成某个正整数,以减少索引长度,变现增加单个页内可容纳的信息量,减少页数目,减少搜索层级,加快搜索过程。

参考

Maximum Number Of Tables In A Schema

微信ANDROID客户端-会话速度提升70%的背后