C 如何检测sqlite3是否创建了数据库文件?
我正在编写一个程序,它使用sqlite3数据库文件来存储数据。如果我用C 如何检测sqlite3是否创建了数据库文件?,c,sqlite,race-condition,tocttou,C,Sqlite,Race Condition,Tocttou,我正在编写一个程序,它使用sqlite3数据库文件来存储数据。如果我用 sqlite3_open_v2(filename, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL) 如果数据库文件不存在,则创建该数据库文件。如何在打开数据库文件之前查明它是否存在?sqlite3 shell使用如下代码: /* Go ahead and open the database file if it already exists. If
sqlite3_open_v2(filename, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)
如果数据库文件不存在,则创建该数据库文件。如何在打开数据库文件之前查明它是否存在?sqlite3 shell使用如下代码:
/* Go ahead and open the database file if it already exists. If the
** file does not exist, delay opening it. This prevents empty database
** files from being created if a user mistypes the database name argument
** to the sqlite command-line tool.
*/
if( access(data.zDbFilename, 0)==0 ){
open_db(&data, 0);
}
但是,当数据库文件是在access
调用之后和open\u db
调用之前由另一个进程创建时(检查时间与使用时间),此代码具有竞争条件
另一个答案(我现在找不到)建议检查application\u id
和user\u version
字段。如果它们为零,则刚刚创建了一个数据库。我研究了这种方法,发现许多应用程序实际上并不费心在新创建的数据库中设置这些字段,所以这种方法充其量是模糊的,我认为它解决不了我的问题
是否有一种方法可以在打开数据库之前确定数据库是否存在,而不涉及这样的竞争条件?如果我只知道数据库文件是否由sqlite3初始化(如中所示,被截断的文件中填充了sqlite3头),这也是可以接受的
拥有这样一个例程的目的是能够确定是否需要在数据库中创建所需的所有表。我不想意外地覆盖其他应用程序放置在那里的另一个文件
示例代码
在下面的bash脚本中可以找到该问题的简化说明。它模拟两个应用程序。它们的工作方式相似:
test.db
test
,并在其中写入一行。第一个应用程序的值为1,第二个应用程序的值为2#!/bin/sh
# sleeps are inserted to make the race conditions easier to trigger
application() {
echo Checking if database exists...
[ ! -f test.db ]
status=$?
sleep 2
if (exit $status)
then
echo Database not found, making tables
sqlite3 test.db "CREATE TABLE IF NOT EXISTS test (a);"
sleep 2
echo Writing initial record into database
sqlite3 test.db "INSERT INTO test VALUES ($1);"
sleep 2
else
echo Database found, checking if it belongs to me
fi
echo Printing content of database
sqlite3 test.db "SELECT * FROM test;"
}
rm -f test.db
echo First test: app1 and app1 race
application 1 & (sleep 1 ; application 1)
rm -f test.db
echo Second test: app2 and app1 race
application 2 & (sleep 1 ; application 1)
我的目标是确保以下情况永远不会发生:
- application one的实例打开数据库文件,断定该文件未初始化,并对其进行初始化(通过创建表和写入初始记录),即使它已包含来自同一应用程序的不同实例或来自不同应用程序的数据
- 将属于不同应用程序的数据库文件写入
如果
应用程序
编程正确,则每次运行仅会在之前未初始化数据库的情况下初始化数据库。因此,您将看到的唯一输出是仅包含行1
的数据库或仅包含行2
的数据库。无法区分新创建的数据库和先前创建的空数据库
但是,空数据库是唯一存在此问题的数据库。
通过检查sqlite\u master
表是否为空,可以检测到任何其他数据库。在事务内执行此操作和创建表,则不存在争用条件
如果您对数据库的第一次写入(即文件实际创建时)设置了,则您知道任何具有其他ID的文件都不是您的。(注册的应用程序ID是唯一的。)我很难理解问题所在。为什么不打开文件并创建表呢。如果表存在,create语句肯定会失败?@user694733如果我从另一个应用程序打开一个数据库(碰巧)使用了我使用的相同表名,会怎么样?随后,我的应用程序神秘地失败了,或者数据库可能会因为我试图向其中写入无意义的内容而损坏。如果同时打开程序的两个实例,并尝试两次初始化相同的表,写入重复的初始化记录或更糟的情况会怎样?不久前我也遇到过同样的问题。最后,我开发了一个服务守护进程,它通过UDP套接字连接唯一地访问和管理数据库上的内容。我已经有一段时间没有使用SQLite了,但它应该为写操作使用独占锁定。您可以在create/init步骤中使用事务来防止出现2个实例的问题。对于其他应用程序问题,首先为什么要与其他无关应用程序共享db目录?但是,如果有理由这样做,并且它也使用SQLite,那么锁定也应该考虑到这一点。@user694733没有应用程序目录。数据库文件是在用户需要的地方创建的。将其视为一个数据库,而不是一个内部应用程序数据库。当然会使用锁定,但这种情况不一定是两个进程同时访问同一数据库的竞争条件。这是个问题。