SQLite3 写数据库时的锁机制

来源于SQLite官方网站上的文档:http://www.sqlite.org/lockingv3.html

 

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锁,进而可以对数据库进行写操作。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值