加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

SQLite3 写数据库时的锁机制

发布时间:2020-12-12 20:16:43 所属栏目:百科 来源:网络整理
导读:SQLite3 写数据库 为了写 Sqlite3 数据库,进程必须先获取 SHARED 锁。当获取 SHARED 锁之后,进程需要进一步申请 RESERVED 锁。 RESERVED 锁表示该进程会在不远的将来执行写数据库操作。同一时刻只有一个进程能够获取 RESERVED 锁。但是其他进程此时还是可

SQLite3写数据库

为了写Sqlite3数据库,进程必须先获取SHARED锁。当获取SHARED锁之后,进程需要进一步申请RESERVED锁。RESERVED锁表示该进程会在不远的将来执行写数据库操作。同一时刻只有一个进程能够获取RESERVED锁。但是其他进程此时还是可以获取SHARED锁来读取数据库中的内容

当写数据库的进程试图RESERVED锁未遂,意味着此时有另一个进程已经获取了RESERVERD锁。在这种情况下,写数据库将会失败,并返回SQLITE_BUSY错误。

获取RESERVERED锁之后,写数据库进程将先创建一个rollback journaljournal的头部被初始化成Sqlite数据库文件的原始大小。同时该头部也为master journal保留了空间,尽管该master-journal初始值为空。

在对DB的任何一个page(数据是以page的形式组织的)执行更新之前,进程需要将该page的原始内容写到rollback journal中。被改变的page起初被保存在内存中,并没有写到disk上。原始的数据库还是处于未被修改的状态,换句话说,其它进程还是能继续读取原始的数据。

最后,写数据的进程准备真正更新数据库了。更新的时机在于memory cache满了需要换出,或者是进程已经准备好commit这次update。在更新发生之前,写数据库的进程必须确保没有其他进程正在读数据库,同时rollback journal的数据也已经安全保存到磁盘上,以确保在更新失败时,rollback能够正确进行。以下将是更新数据库的具体步骤:

1.确保所有的rollback journal都被写到disk上(而不是OSFS中或者disk controllercache中),以防止突然掉电重启后,数据依然可以恢复。

2.获取PENDING锁,再进一步获取该数据库文件上的EXCLUSIVE(排他)锁。如果此时该数据库文件上有SHARED锁(被其他进程所占用),写进程必须等带直到获取EXCLUSIVE(排它)锁。

3.Memory中所有修改过的page写到数据库文件中。

如果由于memory cache满了导致的写数据库操作,写进程自己并不知道,也不会立即commit。相反,写进程可能会继续对内存中的page作更新操作。在后续的更新被写到数据库文件之前,rollback journal必须先被flushdisk上。同时也要注意,先前因为memory cache满而写数据库时获取的EXCLUSIVE(排他)锁,将会一直保持到所有的更新都被commit。换句话说,一旦memory cache满了,并且第一次成功写入数据库后,没有任何其他进程可以访问数据库。(排它锁一旦获取,就不会降级,直到所有的更新被提交给数据库)

当一个写进程准备commit更新时,它将执行一下操作:

4.获取该数据库文件上的排他锁,并确保所有memory中的更新都正确写到disk上。其逻辑由上述1-3步骤描述

5.将所有该数据库文件上的更新flushdisk上。(同步过程,数据真正写到磁盘表面才会返回)

6.删除journal文件(如果PRAGMA journal mode被设置成TRUNCATE或者PERSIST时,则相应地Truncate journal文件或者清空journal文件的头部)。这些操作是在更新被提交时立即执行的。在删除journal文件之前,如果出现掉电或者crash的情况,当下一个进程打开数据库文件时,将会发现该数据库文件有一个hot journal文件残留,并会根据该journal文件回滚所有的更新。当journal文件被删除后,将不会存在所谓的hot journal文件,所有的更新即被持久化下来。

7.释放该数据库文件上的EXCLUSIVE锁和PENDING

一旦PENDING锁被释放,其他进程就可以读取该数据库文件中的内容。在当前(Sqlite3)的实现中,RESERVED锁也会一同被释放,虽然无伤大雅。将来的Sqlite可能会

提供"CHECH POINT" SQL命令,可以用来在一个transaction内,提交到当前位置的所有变化,同时保持RESERVED锁。这样后续的更新就能确保不会被其他的写进程抢占。

如果一个transaction涉及到多个DB文件,提交的逻辑将会更加复杂(1-3步骤是一样的,从第4步开始有所变化):

4.确保所有的数据库文件都获取到了排它锁以及正确的journal文件。

5.创建一个master-journal文件,该master-journal文件的名称为arbitray。(目前的实现是在"主数据库文件——main database file(怎么确定主数据文件,没有交代)"名称后加一个随机数后缀,知道该文件名没有被使用过为止)该master-journal中记录的是各个DBjournal文件名称。(同样,此时要保证master-journal被正确flushdisk上)

6.master-journal的名称写到相关的各个journal文件中(在创建rollback journal的时候,为master journal预留了空间)。同时flush各个journal文件到disk上(是指真正的磁盘表面)。

7.将数据库文件们的更新flush到磁盘表面。

8.提交transaction同时,删除master-journal文件。同理如果在删除之前,出现掉电,crash等情况,参考上个session的第6步骤

9.删除各个DB文件相关的journal文件

10.释放相关数据库文件上的EXCLUSIVE锁和PENDING

写饥饿

Sqlite2中,如果有很多进程读数据库,很有可能每时每刻都有进程在读DB。也就是说每时每刻都有进程占据DBSHARED锁,写进程将没有机会获取EXCLUSIVE锁,即导致所谓的写饥饿。

Sqlite 3通过使用PENDING锁来解决写饥饿的问题。PENDING锁允许当前正在读DB的进程完成读操作,但是不允许新的读操作。因此,当一个进程准备写DB时,它将先申请PENDING锁,阻断后续的读操作。而当当前正在读的所有进程完成读取操作后,SHARED锁都被释放,从而写进程就可以获取EXCLUSIVE锁,进而可以对数据库进行写操作。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读