如何做好SQLite 使用质量检测,让事故消灭在摇篮里 (4)

SQLiteLint 会在应用启动后检测一次所有表的创建语句,发现未使用 without rowid 技巧且根据表信息判断适合使用 without rowid 优化的表,就报问题,建议使用 without rowid 优化。 这是 SQLiteLint 的另一个思路,就是发现是否可以应用上一些 SQLite 的高级特性。

without rowid 在某些情况下可以同时带来空间以及时间上将近一半的优化。简单说下原理,如:

img

对于这个含有 rowid 的表( rowid 是自动生成的),这时这里涉及到两次查询,一次在 name 的索引树上找到对应的 rowid ,一次是用这个 rowid 在数据树上查询到 mark 列。 而使用 without rowid 来建表:

img

数据树构建是以 name 为 key ,mark 为 data 的,并且是以普通 B-tree 的方式存储。这样对于刚刚同样的查询,就需要只有一次数据树的查询就得到了 mark 列,所以算法复杂度上已经省了一个 O(logn)。另外又少维护了一个 name 的索引树,插入消耗和空间上也有了节省。

当然 withou rowid 不是处处适用的,不然肯定是默认属性了。SQLiteLint 判断如果同时满足以下两个条件,就建议使用 without rowid :

表含有 non-integer or composite (multi-column) PRIMARY KEY

表每行数据大小不大,一个比较好的标准是行数据大小小于二十分之一的page size 。ps.默认 page size SQLite 版本3.12.0以后(对应 Android O 以上)是 4096 bytes ,以前是 1024 。而由于行数据大小业务相关,为了降低误报,SQLiteLint 使用更严格的判定标准:表不含有 BLOB 列且不含有非 PRIMARY KEY TEXT 列。

简单说下原因: 对于1,假如没有 PRIMARY KEY ,无法使用 without rowid 特性;假如有 INTEGER PRIMARY KEY ,前面也说过,这时也已经等同于 rowid 。 对于 2,小于 20 分之一 pagesize 是官方给出的建议。 这里说下我理解的原因。page 是 SQLite 一般的读写单位(实际上磁盘的读写 block 更关键,而磁盘的消耗更多在定位上,更多的page就有可能需要更多的定位)。without rowid 的表是以普通 B-Tree 存储的,而这时数据也存储在所有树结点上,那么假如数据比较大,一个 page 存储的结点变少,那么查找的过程就需要读更多的 page ,从而查找的消耗更大。当然这是相对 rowid 表 B*-Tree 的存储来说的,因为这时数据都在叶子结点,搜索路径上的结点只有 KEY ,那么一个page能存的结点就多了很多,查找磁盘消耗变小。这里注意的是,不要以纯内存的算法复杂度去考量这个问题。以上是推论不一定正确,欢迎指教。

引申一下,这也就是为什么 SQLite 的索引树以 B-Tree 组织,而 rowid 表树以 B-Tree 组织,因为索引树每个结点的存主要是索引列和 rowid ,往往没这么大,相对 B-Tree 优势就在于不用一直查找到叶子结点就能结束查找。与 without rowid 同样的限制,不建议用大 String 作为索引列,这当然也可以加入到 SQLiteLint 的检测。

小结

这里介绍了一个在开发、测试或者灰度阶段进行 SQLite 使用质量检测的工具,这个思路的好处是:

上线前发现问题

关注最佳实践

本文的较大篇幅其实是对 SQLite 最佳实践的讨论,因为 SQLiteLint 的思路就是对最佳实践的自动化检测。当然检查可以覆盖更广的范围,准确性也是挑战,这里还有很大的空间。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyjyws.html