跑 SQLite 查询不及时使用 sqlite3_finalize() 的严重后果,望周知。

本事件已被我投稿至纯靠北工程师(「#純靠北工程師 7ib 」)。

教训是:用 SQLite 跑完「每一笔」查询之后一定要「立刻」用 sqlite3_finalize(StatementPointer) 释放运存,不然会产生连 Xcode Instruments 都抓不到的运存泄漏。

事情是这样:

为了因应 Apple 对 8GB 运存的病态执着, 我最近刚刚给威注音输入法的原厂词库格式由 JSON 升级至 SQLite 。

我是第一次用 SQLite ,看这篇学的(威注音是用 Swift 开发的): itisjoe.gitbooks.io/swiftgo/con... 现在 Swift 用 SQLite 超方便,直接 import SQLite3 就行。 ( Linux 或者旧版 macOS 还可以直接读 SQLite3 的 almagation 。) 只是,窃以为我犯的最大错误就是「没有针对相关的 SQLite API 详读 SQLite 官方的手册」。

我一开始以为让所有的 SQLite Statement 共用一个(作为静态变数的) nullable OpaquePointer 就可以了, 于是我一开始的设计就是让辞典模组在 deinit() 的时候才跑一次「 sqlite3_finalize()」。 因为原厂词库是只读的、在 App Bundle 内,所以我将 journal 塞在了运存内。

结果呢?施工完毕之后,单元测试啥的都成功通过了,刚刚开启输入法时的运存占用是 18MB 左右。 当时我感觉很开心,因为这几乎是直接消灭了输入法启动时花在原厂辞典上的载入时间。 但是,周一上午,我用这输入法没打六分钟的字,输入法碰到了我之前设定的保险丝而自尽了: 我早前做了这么个保险设计:只要运存占用超过 768MB ,输入法就自尽、自动重新启动来释放运存。

然后,我用 Xcode 14.2 的 Instruments 抓运存泄漏, 但只抓到一个与系统输入法选单有关的小微型泄漏(还是无法解决的那种)。 正好我周一那天公司休假,我花了一整个周一的时间排查自己做过的所有修改, 最后开始怀疑「 sqlite3_finalize()」是不是得在每次查询之后都执行一次。 于是我补上了这句试了试:

swift 复制代码
extension vChewingLM.LMInstantiator {
  fileprivate static func querySQL(strStmt sqlQuery: String, coreColumn column: CoreColumn, handler: (String) -> Void) {
    guard Self.ptrSQL != nil else { return }
    defer { sqlite3_finalize(Self.ptrStatement) } // 补上了这句。
    sqlite3_prepare_v2(Self.ptrSQL, sqlQuery, -1, &Self.ptrStatement, nil)
    while sqlite3_step(Self.ptrStatement) == SQLITE_ROW {
      guard let rawValue = sqlite3_column_text(Self.ptrStatement, column.id) else { continue }
      handler(String(cString: rawValue))
    }
  }
...
}

结果发现运存泄漏的问题失踪了。 这问题幸好发现得早,不然威注音 3.6.2 正式版带着这个 Bug 发出去的话会坑死很多人。

然后我就将这次经历投稿到靠北工程师,但没想到反应非常空前。 居然还有人说我积阴德。还有人在转发时提到这么个例子:

超市的系统在频繁的查询资料后都会有整个系统延迟的状况,不知道跟这有没关系... 虽然资料库跟我没啥屁关系,也先备份再说。

于是我决定将这个故事也放在掘金这边让各位 SQLite 用户共勉。

相关推荐
必胜刻40 分钟前
MySQL复盘总结
数据库·mysql·oracle
百***79461 小时前
MySQL数据库(SQL分类)
数据库·sql·mysql
weixin_307779131 小时前
Amazon VPC中Web应用无法连接数据库的安全组配置问题分析与修复
网络·数据库·安全·云计算·aws
q***42051 小时前
开启mysql的binlog日志
数据库·mysql
q***38512 小时前
Spring boot启动原理及相关组件
数据库·spring boot·后端
你不是我我2 小时前
【Java 开发日记】SQL 语句左连接右连接内连接如何使用,区别是什么?
java·javascript·数据库
JELEE.2 小时前
Django中如何重写save()方法
数据库·django
SuperSpinach2 小时前
mysql 报错Authentication method ‘caching_sha2_password‘ is not supported.
数据库·mysql
R.lin3 小时前
MySQL 性能优化最佳实践
数据库·mysql·性能优化
北i3 小时前
TiDB 关联子查询去关联优化实战案例与原理深度解析
java·数据库·sql·tidb