最近调查一个问题,用sqlite3 记录一条item,显示成功后机器直接断电重启发现数据没有变化。
明明操作已经显示success了,为什么数据没有写进去呢?
对数据库基本是小白,一直是做底层开发的,上面的兄弟说sqlite操作数据库,fsync或者fdatasync后,数据没有写到磁盘导致了这个问题。 好吧,调查fsync fdatasync,系统调用最后都是一个fsync,datasync做参数0或者1,更新的元数据时候做了判断区分,这兄弟两可都是货真价实的同步调用啊。代码上分析直接写入了这个fd的file map data和inode信息,真正写入后才返回啊。
问题来了,数据库fdatasync内容莫非真的没有写进去,只能看sqlite数据库怎么调用的。发现里面只是加读写锁机制。啊哟我勒个去,莫非是锁干的坏事,写没成功,debug一把,调用的顺序就是这么的正常,毫无破绽。加了各种log,打开了内核的block dump log看哪些文件写了。发现很奇怪,数据库文件确实已经被写了,那为啥写完断电重启后数据文件又被还原了呢。
strace跟踪了一下,发现在数据库更新时,会去创建一个 -journal的日志文件,写完后会unlink掉这个日志文件。去查了一个官方文档,这-journal文件就是为了对应 crash和 突然power off的情况的。这下知道原因了,原来都是这个 -journal 文件搞的鬼。 unlink后 在kernel 调度IO刷新操作之前掉电,那么这个-journal文件其实没有被真正删除,还是存在于磁盘上的。
下次操作数据库时,sqlite检查到有 -journal文件,认为上次是异常处理,从-journal文件中将更新的内容还原回来了。还是原来的数据,还是熟习的配方。 这个数据库就是这么设计的,没有问题,数据库需要满足ACID (Atomicity,Consistency,Isolation,Durability)特性,用日志来防止上次的异常操作。sqlite叫做hot journal
原因清楚了,为什么会出现这样的情况呢,为什么不像数据库写一样,写了数据后用fdatasync来保证数据写了磁盘上。猜测了一下,unlink文件没有类似 fsync和fdatasync 一样同步的操作可以确保文件从磁盘上删除的。删除动作,只能操作sync去更新整个文件系统,估计这样做的话性能就要降低了。