zoukankan      html  css  js  c++  java
  • Sqlite学习笔记(四)&&SQLite-WAL原理(转)

    Sqlite学习笔记(三)&&WAL性能测试中列出了几种典型场景下WAL的性能数据,了解到WAL确实有性能优势,这篇文章将会详细分析WAL的原理,做到知其然,更要知其所以然。

    WAL是什么

          WAL(Write ahead logging)是一种日志模式,它是一种思想,普遍应用于关系型数据库。每个事务执行变更时,修改数据页,同时会产生日志,这样在事务提交后,不需要将修改的脏页刷盘,只需要将事务产生的日志落盘即可返回。WAL保证日志一定先于对应的脏页落盘,就是所谓的WAL。SQLITE在3.7版本以后引入WAL,它的WAL也基本采用这个原理,只不过SQLite实现比较简单,日志记录的是修改后的页,而不是所谓的修改日志。WAL模式下,SQlite中除了db文件,还包含了两个文件,.wal文件和.shm文件,前者是日志文件,后者是日志索引文件。

    日志模式

          SQLite中日志模式主要有DELETE和WAL两种,其他几种比如TRUNCATE,PERSIST,MEMORY基本原理都与DELETE模式相同,不作详细展开。DELETE模式下,日志中记录的变更前数据页内容;WAL模式下,日志中记录的是变更后的数据页内容。事务提交时,DELETE模式将日志刷盘,将DB文件刷盘,成功后,再将日志文件清理;WAL模式则是将日志文件刷盘,即可完成提交过程。那么WAL模式下,数据文件何时更新呢?这里引入了检查点概念,检查点的作用就是定期将日志中的新页覆盖DB文件中的老页,并通过参数wal_autocheckpoint来控制检查点时机,达到权衡读写的目的。

         DELETE模式下,写事务直接更新db-page,并将old-page写入日志,读事务则直接读db-page,因为db-page中保存了提交的所有事务的更新。事务提交后,直接将日志文件删除;若事务需要回滚,则将日志中old-page中的内容覆盖db-page,恢复原始内容。WAL模式下,写事务将更新写到日志文件中,不更新db-page,事务提交时,也不影响db-page,只是将日志持久化而已。若事务回滚,则不将日志写入文件即可。由于最新的数据在日志文件中,那么如何读取到最新的数据呢?WAL模式通过end-mark(事务提交位点)达到这一目的。具体而已,事务开始时,会首先扫描日志文件,获取最近一个end-mark,在读取数据时,首先会判断page是否则在wal日志文件中存在,因为同一个page,一定是wal文件中的比db文件中的要新。如果存在,则使用,否则,再从db文件中获取指定的page。从流程上来看,这个过程比较慢,因为极端情况下,每次读都需要扫描wal文件和db文件。为了提高性能,WAL模式中有一个wal-index文件,这个文件记录了页号和该页在WAL文件中的偏移,并且wal-index文件采用共享缓存实现,从文件名也可以看到,后缀是.shm,因此判断page是否在wal文件存在的操作实质是一次内存读。wal-index采用hash表存储,因此查询效率也非常高。

          与传统的DBMS不同,SQLite中记录的日志,实质是dirty-page,重做实质是对利用WAL中的日志页覆盖db-page,这种实现方式比较简单,同时也比较浪费空间,因为一个page是1k,即使只更新1byte,也会导致日志记录1k。

    WAL的优势与劣势

    1)  并发优势  

          SQLite为什么引入WAL,一定是WAL有很多好的特性。其中最主要的一点是WAL支持读写并发。在DELETE模式下,读写是互斥的。为什么WAL可以并发,而DELETE不行?我这里不打算详细展开WAL模式和DELETE模式的锁机制,后面有机会再单独写这一部分。从上面一节的分析可以知道,WAL模式下,写事务以append方式记录new-page,而读事务只会读取db-page和end-mark之前的wal日志,因此不会发生读写冲突的问题,读写可以并发。而DELETE模式下,写事务写的是db-page,读事务也是读db-page,所以读写不能并发。

    2) 写性能优势

    从前面的分析可知,WAL模式下,事务提交只需要写入日志文件即可,为了持久化,只需要一次fsync调用。而DELETE模式下,事务提交过程中,首先要确保日志落盘(保存old-page,用来rollback),这里需要一次fsync调用,然后再执行db文件刷盘,这里还需要一次fsync,并且修改的db-page可能是离散的,因此可能需要多余一次的fsync。此外,WAL写日志都是顺序写,相对于离散写又有很大的优势。因此DELETE模式下写性能会比WAL模式要差。测试结果也证明了这一点,这里可以参考测试报告。

    3) WAL劣势

          开启WAL后,每次读取page,都需要通过wal-index来确认page是否在WAL中,这个会产生一定的性能损耗。另外,会引入WAL文件,这个文件如果使用不当,可能会急剧膨胀,WAL文件变大后,意味着检索wal-index的代价也变高。而且由于SQLite一般用于端设备,空间也比较稀缺,因此要严格控制好WAL文件的大小。此外,WAL的索引文件采用共享内存实现,因此访问SQlite的进程不能跨机器。

    开启WAL模式

          通过命令pragma journal_mode=wal可以开启wal模式。前面我们提到开启WAL模式后,如果使用不当,可能导致WAL文件空间暴增,但我们有办法避免这种情况发生。这里主要介绍两个参数,wal_autocheckpoint和journal_size_limit。wal_autocheckpoint用来设置触发检查点的时机,默认是1000页,即当日志增长到1000页时,开始做检查点操作。这里要说明一点的是,SQLite中没有单独的检查点线程,如果设置1000,则触发写1000页的事务来进行检查点操作。因此这个事务的响应时间会比较长,而其它事务则不受影响。用来设置日志文件的大小,默认情况为-1,当这个参数设置时,若累计更新页大小超过journal_size_limit,也会导致检查点触发,用以重复利用日志文件,避免日志继续增长。

    相关参数

    1)  journal_mode(日志模式)

    默认是DELETE模式

    DELETE:原始数据页存放在日志文件中,事务提交时,将文件删除。

    TRUNCATE :与DELETE模式的区别是,清空日志文件,但不删除文件清空文件往往比删除文件要快。

    PERSIST:与DELETE和TRUNCATE模式区别是,既不删除文件,也不清空文件,而是将日志文件第一个页设置标记(置0),这个也是为了提高性能。 MEMORY :内存模式,修改不落盘,无法保证事务的原子性。

    OFF:不开启日志,这样没法保证事务的原子性。

    WAL :write ahead log,3.7.0引入,日志中记录修改页,提交时只需刷修改页。

    2)  journal_size_limit(日志文件大小)

    默认值为-1,表示没有限制,单位是字节。 DELETE模式下,当日志增长超过阀值时,则进行截断。 WAL模式下,当日志增长超过阀值时,日志文件不再会被截断,而是重复利用, 因为通常情况下重复写的性能要好于追加的性能,而且也省磁盘空间。 default_journal_size_limit,用于设置日志文件的默认大小。

    3)  wal_checkpoint(检查点模式)

    PASSIVE,默认自动检查点和主动检查点都是PASSIVE类型,将所有可以同步到db的数据都进行同步(不超过所有线程的end mark),不持有排它锁,因此不会影响其他读写事务。

    FULL,将wal与db文件完全同步,需要等待所有读写事务都结束,并且会堵塞新的读写事务

    RESTART,与FULL模式的区别是,下一个写线程从头开始写wal文件。 TRUNCATE,与FULL模式的区别是,将wal文件截断为0。

    4) wal_autocheckpoint(检查点触发时机)

    默认值为1000页,单位是页。当日志的增量到N页时,触发检查点操作,将wal_autocheckpoint设置为0或者-1,表示关闭检查点。

    5) synchronous(同步模式)

    默认设置是FULL

    0(OFF):事务提交时,不作sync操作,直接返回。

    1(NORMAL):事务提交时,不作sync操作

    2(FULL):每次事务提交,都强制刷日志(WAL),强制数据页(journal)

    6) cache_size 默认值2000,单位为页 修改db的缓存页数目,临时生效

    7) default_cache_size

    默认值2000,单位为页

    修改缓存页数目,永久生效若同时设置了cache_size和default_cache_size,以default_cache_size为准

    参考文档

    https://www.sqlite.org/wal.html

    http://www.cnblogs.com/cchust/p/4754619.html

  • 相关阅读:
    SQL Server, Timeout expired.all pooled connections were in use and max pool size was reached
    javascript 事件调用顺序
    Best Practices for Speeding Up Your Web Site
    C语言程序设计 使用VC6绿色版
    破解SQL Prompt 3.9的几步操作
    Master page Path (MasterPage 路径)
    几个小型数据库的比较
    CSS+DIV 完美实现垂直居中的方法
    由Response.Redirect引发的"Thread was being aborted. "异常的处理方法
    Adsutil.vbs 在脚本攻击中的妙用
  • 原文地址:https://www.cnblogs.com/softidea/p/4756035.html
Copyright © 2011-2022 走看看