什么时候使用 SQLite.swift 将记录提交到磁盘?

When are records committed to disk with SQLite.swift?

在使用 SQLite.swift 时,我很难看到 SQLite 数据库中反映的一些 INSERT。

我使用断点调试我的 iOS 应用程序,插入后我将应用程序容器(从设备)导出到主机硬盘。然后我检查 SQLite 数据库以查看更改。因此不反映 INSERT。

如果我在 table 上添加额外的代码,例如 SELECT 或重新启动应用程序,那么记录就在那里,因此看起来并没有整合到设备上的磁盘马上,但在其他时间。我正在使用单个 Connection 对象并且没有并发访问,所以我不希望出现线程问题。我还尝试在事务中使用 INSERT,但仍未反映在容器中。

何时将更改合并到磁盘以使其真正持久化?有没有办法强制冲洗?如果应用程序意外终止,会​​发生什么情况,数据是否会丢失,因为它实际上不在数据库中?

一般在C-API级别,只有在使用事务时才会缓存结果。在那个级别,sqlite3_reset() and/or sqlite3_finalize() 将关闭语句并写入。不过我猜你没有使用交易。

我在网上发现了几个 posts 建议将对数据库的活动查询保持打开状态可以防止缓存刷新到数据库。虽然没有看到您的代码,但实际上不可能知道这是否是正在发生的事情。

您可以显式 BEGIN 一个事务,并且 COMMIT 如果您愿意,它可以强制刷新。

要检查的另一件事是 PRAGMA SYNCHRONOUS 值 (docs)。它通常设置为 FULL (2) 但您可以尝试将其设置为 EXTRA (3).

更新:

我尝试使用 SQLite.swift 复制您所描述的内容,但写入始终是即时的,并且可以立即从不同的连接读取。这是我写的代码:

import UIKit
import SQLite

class ViewController: UIViewController {

    private static let REUSE_DB_CONN:Bool = true
    private static let DB_NAME = "SQLiteFlushTester.sqlite3"
    let pragma:String? = "PRAGMA foreign_keys = ON;"

    var connection:Connection?

    let fooTable = Table("FooTable")
    let id = Expression<Int64>("id")

    func dbDefaultPath(name: String = ViewController.DB_NAME) -> String {
        let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!

        let fmgr = FileManager.default
        if (!fmgr.fileExists(atPath: path)) {
            print("Directory does not exist at \(path)")
            do {
                try fmgr.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
            } catch {
                print("Could not create directory \(path)")
            }
        }

        return "\(path)/\(name)"
    }

    func dbConn() -> Connection? {
        if let connection = connection {
            print("Recycling connection")
            return connection
        }

        let dbPath = dbDefaultPath()
        do {
            let dbConn = try Connection(dbPath)
            if let pragma = pragma {
                try dbConn.execute(pragma)
            }

            if ( ViewController.REUSE_DB_CONN ) {
                connection = dbConn
            }
            return dbConn
        } catch let error {
            print("Error opening DB \(error)?")
            return nil
        }
    }

    func createTestTable(_ db:Connection) throws {
        try db.run(fooTable.create { t in
            t.column(id, primaryKey: true)
        })
    }

    func insertIdIntoFoo(_ db:Connection) {
        let randval:Int64 = Int64(arc4random_uniform(10000))

        do {
            let insert = fooTable.insert(id <- randval)
            let rowid = try db.run(insert)
            print("Inserted to row id \(rowid)")
        } catch let error {
            print("Error inserting value: \(error)")
            return
        }
    }

    @IBAction func doAnInsert(_ sender: Any) {
        guard let db = dbConn() else {return}
        insertIdIntoFoo(db)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        guard let db = dbConn() else { return }

        do {
            try createTestTable(db)
        } catch let error {
            print("Error creating table: \(error)")
            return
        }
    }
}

我从 sqlite3 命令行工具打开数据库,并在按下 ViewController 上触发 IBAction 的按钮时观察更新,该行反映在立即数据库。重用(或不重用)连接没有区别(参见 REUSE_DB_CONN 标志)。

我在访问数据库时遇到了同样的问题,我发现当我使用 for 或 while 语句等循环插入数据时,这种情况特别常见。

  1. 确保您通过序列化对数据库的访问来控制数据流。
  2. 确保数据库在执行插入过程的整个过程中都处于打开状态,否则会产生问题。如果您在每个循环周期都打开和关闭数据库,那将是一个问题。

祝你有愉快的一天!