sqlite - SQLite 如何通过延迟事务防止死锁?

标签 sqlite

根据documentation关于延期交易:

The default transaction behavior is deferred. (...) The first read operation against a database creates a SHARED lock and the first write operation creates a RESERVED lock.

同时根据documentation关于锁:

Any number of processes can hold SHARED locks at the same time (...) Only a single RESERVED lock may be active at one time, though multiple SHARED locks can coexist with a single RESERVED lock

这听起来像是具有任意读取器到写入器升级机制的多个读取器/单个写入器锁,已知这会导致死锁危险:

  • A 开始交易
  • B 开始交易
  • A 获取 SHARED 锁并读取内容
  • B 获取共享锁并读取内容
  • A 获取 RESERVED 锁并准备写入内容。只要有其他共享锁,它就无法写入,因此会阻塞。
  • B 希望写入,因此尝试获取 RESERVED 锁。已经有另一个 RESERVED 锁,因此它会阻塞,直到它被释放,但仍持有 SHARED 锁。
  • 陷入僵局。

那么 SQLite 如何解决这个问题呢?我想到了两种可能的解决方案,但它们似乎都打破了交易的整个理念:

  • 潜在的写入者在获取 RESERVED 之前释放 SHARED 锁。这会破坏读取和写入之间的原子性。
  • B 在尝试获取 RESERVED 锁时不会阻塞,但会出错。这意味着所有读取都需要重复,并使 API 使用变得非常复杂。

我错过了什么吗? SQLite 如何处理这个问题?为什么这种看似危险的交易类型会成为默认交易类型?

最佳答案

通过简单的尝试和错误,我发现他们走了错误路线。

在给定的场景中,当 B 尝试获取 RESERVED 时,它将首先等待 PRAGMA busy_timeout 毫秒。然后会报错误:数据库已锁定。该事务仍将处于事件状态,因此可以立即重试。

如果A随后尝试COMMIT(或者如果它耗尽了内存缓存),它将获取PENDING锁(防止额外的SHARED锁),然后等待EXCLUSIVE。如果在PRAGMA busy_timeout毫秒后仍有一些共享锁存在,则会报告错误:数据库已锁定。该事务仍将处于事件状态,因此可以立即重试。

也就是说,使用的死锁预防机制是超时。不过,它确实需要 API 用户配合,回滚并重试。

作为指导:

  • 当您只想阅读时,只需使用 BEGIN TRANSACTION (或显式使用 BEGIN DEFERRED TRANSACTION)。写入可能会失败,迫使您回滚并再次重试整个事务。
  • 当您希望在某个时刻进行写入时,请使用BEGIN IMMEDIATE TRANSACTION。这将阻止所有其他作家和所有其他直接的潜在作家。
  • BEGIN EXCLUSIVE TRANSACTION 将立即阻塞,直到释放所有其他锁。我不知道为什么有人会想要这个。可能是为一些数据到达后需要尽快写入磁盘做好准备? 编辑:这似乎是防止开始事务后任意点超时的唯一方法。

关于sqlite - SQLite 如何通过延迟事务防止死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55831645/

相关文章:

sql - 如何选取每组的前N行?

java - 防止sqlite中IGNORE INTO语句后id自动递增

sqlite - 如何有两个GORM session 到同一个内存数据库?

c++ - sqlite CREATE INDEX exec 返回错误

java - 如何删除 sqlite 中的行/行(java,不是 Android java)

python - 用作上下文管理器的 sqlite3 连接的事务不是原子的

仅具有跳过(偏移)的 SQLite(不限制)

android - 在表格中输入多行的最简单方法

iphone - 如何打印存储在 iphone 上的所有 sqlite 数据库

c++ - 十六进制值的 SQLITE CHECK 约束