SQLite如何防止延迟事务造成的死锁?

时间:2019-04-24 13:47:53

标签: sqlite

根据documentation关于延期的交易:

  

默认事务行为被推迟。 (...)对数据库的第一次读取操作将创建SHARED锁,并且   第一次写操作将创建一个RESERVED锁。

还要根据锁上的documentation

  

任何数量的进程可以同时持有SHARED锁(...)   一次只能激活一个保留锁,尽管多个   共享锁可以与单个保留锁共存

这听起来像是具有任意读取器到写入器提升机制的多个读取器/单个写入器锁,这被认为是死锁危险:

  • A开始交易
  • B开始交易
  • A获取共享锁并读取内容
  • B获取SHARED锁并读取内容
  • A获取RESERVED锁并准备写一些东西。只要有其他SHARED锁,它就无法写入。
  • B希望写,因此尝试获取RESERVED锁。已经存在另一个RESERVED锁,因此它将一直释放直到释放,仍然保持SHARED锁。
  • 僵局。

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

  • 可能的作者会在获得RESERVED之前释放SHARED锁。这样会破坏读写之间的原子性。
  • B在尝试获取RESERVED锁时不会阻塞,但会出错。这意味着将需要重复所有读取操作,并使API的使用大大复杂化。

我想念什么吗? SQLite如何处理呢?为什么将这种看似危险的交易类型设为默认交易?

1 个答案:

答案 0 :(得分:0)

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

在给定的情况下,当B尝试获取RESERVED时,它将首先等待PRAGMA busy_timeout毫秒。然后它将报告Error: database is locked。该交易仍将处于活动状态,因此可以立即重试。

如果A之后尝试访问COMMIT(或者它用完了内存缓存),它将获得PENDING锁(防止其他SHARED锁),然后等待EXCLUSIVE。如果在PRAGMA busy_timeout毫秒后仍保留一些SHARED锁定,它将报告错误:数据库已锁定。该交易仍将处于活动状态,因此可以立即重试。

换句话说,正在使用的防止死锁机制是超时。但是,它确实要求API用户通过回滚并重试来进行合作。

作为准则:

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