为了写sqlite3数据库,进程必须先获取SHARED锁。当获取SHARED锁之后,进程需要进一步申请RESERVED锁。RESERVED锁表示该进程会在不远的将来执行写数据库操作。同一时刻只有一个进程能够获取RESERVED锁。但是其他进程此时还是可以获取SHARED锁来读取数据库中的内容
当写数据库的进程试图RESERVED锁未遂,意味着此时有另一个进程已经获取了RESERVERD锁。在这种情况下,写数据库将会失败,并返回sqlITE_BUSY错误。
获取RESERVERED锁之后,写数据库进程将先创建一个rollback journal。journal的头部被初始化成sqlite数据库文件的原始大小。同时该头部也为master journal保留了空间,尽管该master-journal初始值为空。
在对DB的任何一个page(数据是以page的形式组织的)执行更新之前,进程需要将该page的原始内容写到rollback journal中。被改变的page起初被保存在内存中,并没有写到disk上。原始的数据库还是处于未被修改的状态,换句话说,其它进程还是能继续读取原始的数据。
最后,写数据的进程准备真正更新数据库了。更新的时机在于memory cache满了需要换出,或者是进程已经准备好commit这次update。在更新发生之前,写数据库的进程必须确保没有其他进程正在读数据库,同时rollback journal的数据也已经安全保存到磁盘上,以确保在更新失败时,rollback能够正确进行。以下将是更新数据库的具体步骤:
1.确保所有的rollback journal都被写到disk上(而不是OS的FS中或者disk controller的cache中),以防止突然掉电重启后,数据依然可以恢复。
2.获取PENDING锁,再进一步获取该数据库文件上的EXCLUSIVE(排他)锁。如果此时该数据库文件上有SHARED锁(被其他进程所占用),写进程必须等带直到获取EXCLUSIVE(排它)锁。
如果由于memory cache满了导致的写数据库操作,写进程自己并不知道,也不会立即commit。相反,写进程可能会继续对内存中的page作更新操作。在后续的更新被写到数据库文件之前,rollback journal必须先被flush到disk上。同时也要注意,先前因为memory cache满而写数据库时获取的EXCLUSIVE(排他)锁,将会一直保持到所有的更新都被commit。换句话说,一旦memory cache满了,并且第一次成功写入数据库后,没有任何其他进程可以访问数据库。(排它锁一旦获取,就不会降级,直到所有的更新被提交给数据库)
当一个写进程准备commit更新时,它将执行一下操作:
4.获取该数据库文件上的排他锁,并确保所有memory中的更新都正确写到disk上。其逻辑由上述1-3步骤描述
5.将所有该数据库文件上的更新flush到disk上。(同步过程,数据真正写到磁盘表面才会返回)
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中记录的是各个DB的journal文件名称。(同样,此时要保证master-journal被正确flush到disk上)
6.将master-journal的名称写到相关的各个journal文件中(在创建rollback journal的时候,为master journal预留了空间)。同时flush各个journal文件到disk上(是指真正的磁盘表面)。
8.提交transaction同时,删除master-journal文件。同理如果在删除之前,出现掉电,crash等情况,参考上个session的第6步骤
10.释放相关数据库文件上的EXCLUSIVE锁和PENDING锁
写饥饿
在sqlite2中,如果有很多进程读数据库,很有可能每时每刻都有进程在读DB。也就是说每时每刻都有进程占据DB的SHARED锁,写进程将没有机会获取EXCLUSIVE锁,即导致所谓的写饥饿。
sqlite 3通过使用PENDING锁来解决写饥饿的问题。PENDING锁允许当前正在读DB的进程完成读操作,但是不允许新的读操作。因此,当一个进程准备写DB时,它将先申请PENDING锁,阻断后续的读操作。而当当前正在读的所有进程完成读取操作后,SHARED锁都被释放,从而写进程就可以获取EXCLUSIVE锁,进而可以对数据库进行写操作。
原文链接:https://www.f2er.com/sqlite/201268.html