编译自:http://www.aosabook.org/en/500L/dbdb-dog-bed-database.html
作者:Taavi Burns
翻译:鸿
如有翻译问题或建议,请公众号留言
插入和更新数据
将key值foo对应的value值bar插入到example.db中:
同样的进入main()方法:
__setitem__方法通过_assert_not_closed方法保证DB数据库是可以使用的,并且调用_tree方法更新key/value值。_tree.set()由LogicalBase提供:
set()方法首先检查数据是否上锁:
值得注意的是:
1.这儿的锁是由portalocker模块提供
2.如果数据库锁上了返回False,否则就是True
再回到_tree.set(),通过判断lock()的返回值,调用_refresh_tree_ref作为新的根节点,所以不会丢失其他进程可能正在进行的更新。这样的话,只要等待磁盘刷新树再用它将包含插入(或更新)的键/值的新树替换根节点。因为_insert()返回一个新的树,所以插入或更新二叉树不会改变任何节点,并且新树会与前一棵树共享不变的部分以节省内存和执行时间。
DBDB总是返回一个包装在一个NodeRef中的新节点,所以不用通过更新节点来指向新的子树,而是创建一个共享未改变的子树的新节点。 目前所做的只是通过移动树节点来控制磁盘数据。为了这些修改写入磁盘,我们需要对commit()进行显式调用。
LogicalBase里的self._tree_ref实际上是BinaryNodeRef(ValueRef的子类)类
_referent实际上是BinaryNode,调用的store_refs
这里是递归的调用store方法,将数据存储。回到store方法:
NodeRef的_referent确保已经递归结束,referent_to_string进行序列化:
到这里所有的节点都被写入磁盘了,回到commit()方法,commit_root_address解决所有剩下的事。
这里保证了文件句柄被成功刷新(操作系统可以将所有数据保存到稳定的存储器中)并给出根节点的地址。由于根节点地址同时拥有旧值或新值,其他进程可以从数据库中读取而不需要获得锁。外部进程可能会看到不同状态的二叉树树,但并不会混淆这两种树。所以commit具有原子性的。由于我们在写入根节点地址之前就将新数据写入磁盘并调用了fsync syscall2,因此未commit的数据是无法访问的。相反,一旦根节点地址被更新,它引用的所有数据也在磁盘上。所以,commit也具有持久性。NodeRefs如何存储数据:这是为了避免整个二叉树结构同一时间都保存在内存当中,当从磁盘读入逻辑节点时,其左右子节点的磁盘地址(及其值)也会被加载到内存中。一个NodeRef就是一个地址:
调用get()方法会不断遍历地址:
当对二叉树的更新没有commit时,它们会保存在内存中。 这些更新不会保存到磁盘中,因此更新的节点会包含具体的key和value,但是没有磁盘地址。在执行写入的过程可以看到没有commit的更新,并且在确认commit之前可以继续更新,因为NodeRef.get()会返回未commit的值(如果有的话);在通过API访问时,commit数据与未commit数据之间是没有区别的。 所有更新都是原子性的。并发更新则会被磁盘上的锁阻塞。锁是在第一次更新时获取并在commit后释放。
领取专属 10元无门槛券
私享最新 技术干货