C# 数据库文件在SQLite提交期间被莫名其妙地锁定
我正在对SQLite数据库执行大量插入。我只用一根线。我对写操作进行批处理以提高性能,并在崩溃时提供一点安全性。基本上,我在内存中缓存了一堆数据,然后在我认为合适的时候,循环所有这些数据并执行插入。其代码如下所示:C# 数据库文件在SQLite提交期间被莫名其妙地锁定,c#,sqlite,C#,Sqlite,我正在对SQLite数据库执行大量插入。我只用一根线。我对写操作进行批处理以提高性能,并在崩溃时提供一点安全性。基本上,我在内存中缓存了一堆数据,然后在我认为合适的时候,循环所有这些数据并执行插入。其代码如下所示: public void Commit() { using (SQLiteConnection conn = new SQLiteConnection(this.connString)) { conn.Open()
public void Commit()
{
using (SQLiteConnection conn = new SQLiteConnection(this.connString))
{
conn.Open();
using (SQLiteTransaction trans = conn.BeginTransaction())
{
using (SQLiteCommand command = conn.CreateCommand())
{
command.CommandText = "INSERT OR IGNORE INTO [MY_TABLE] (col1, col2) VALUES (?,?)";
command.Parameters.Add(this.col1Param);
command.Parameters.Add(this.col2Param);
foreach (Data o in this.dataTemp)
{
this.col1Param.Value = o.Col1Prop;
this. col2Param.Value = o.Col2Prop;
command.ExecuteNonQuery();
}
}
this.TryHandleCommit(trans);
}
conn.Close();
}
}
现在,我采用以下技巧使事情最终成功:
private void TryHandleCommit(SQLiteTransaction trans)
{
try
{
trans.Commit();
}
catch (Exception e)
{
Console.WriteLine("Trying again...");
this.TryHandleCommit(trans);
}
}
我创建数据库的方式如下:
public DataBase(String path)
{
//build connection string
SQLiteConnectionStringBuilder connString = new SQLiteConnectionStringBuilder();
connString.DataSource = path;
connString.Version = 3;
connString.DefaultTimeout = 5;
connString.JournalMode = SQLiteJournalModeEnum.Persist;
connString.UseUTF16Encoding = true;
using (connection = new SQLiteConnection(connString.ToString()))
{
//check for existence of db
FileInfo f = new FileInfo(path);
if (!f.Exists) //build new blank db
{
SQLiteConnection.CreateFile(path);
connection.Open();
using (SQLiteTransaction trans = connection.BeginTransaction())
{
using (SQLiteCommand command = connection.CreateCommand())
{
command.CommandText = DataBase.CREATE_MATCHES;
command.ExecuteNonQuery();
command.CommandText = DataBase.CREATE_STRING_DATA;
command.ExecuteNonQuery();
//TODO add logging
}
trans.Commit();
}
connection.Close();
}
}
}
然后我导出连接字符串,并使用它在程序的不同部分获得新的连接
在看似随机的时间间隔内,尽管忽略或解决这个问题的速度太快,但我得到了未处理的SQLiteException:数据库文件被锁定。当我尝试提交事务时会发生这种情况。在此之前,似乎没有发生任何错误。这并不总是发生。有时整个事情都会顺利进行
- 在提交完成之前,不会对这些文件执行任何读取李>
- 我有最新的SQLite二进制文件李>
- 我正在为.NET2.0进行编译李>
- 我用的是VS2008
- 数据库是一个本地文件
- 所有这些活动都封装在一个线程/进程中
- 病毒防护已关闭(尽管我认为这仅在您通过网络连接时才相关?)
- 根据Scotsman的帖子,我实施了以下更改:
- 日志模式设置为持续
- 数据库文件通过
Windows调用存储在C:\Docs+Settings\ApplicationData中System.Windows.Forms.Application.AppData
- 没有内部例外
- 在两台不同的机器上见证(尽管硬件和软件非常相似)
- 一直在运行进程监视器-没有无关进程将自己附加到DB文件-问题肯定出在我的代码中
欢迎更多建议 您的数据库文件与应用程序在同一台机器上还是存储在服务器上 您应该在每个线程中创建一个新连接。我将简化连接的创建,在任何地方使用:connection=newsqliteconnection(connString.ToString()) 并在与应用程序相同的计算机上使用数据库文件,然后再次测试 为什么要使用两种不同的方式创建连接 需要注意的事项:
- 不要跨多个线程/进程使用连接
- 我见过这样的情况:病毒扫描程序会检测到文件的更改并尝试扫描它。它会在很短的时间间隔内锁定文件并造成严重破坏
此处讨论了日志模式选项:。你可以试试截断 在SQL Lite异常期间是否有堆栈跟踪
您指示您“以合理的间隔批处理我的提交”。间隔时间是多少?在运行程序时运行文件名并对其进行筛选,以排除是否有其他进程对其进行了任何操作,并查看您的程序对文件的具体操作。很长,但可能会给出一个线索。我总是在
using
子句中使用连接、事务和命令。在您的第一个代码清单中,您做到了,但在第三个代码清单(创建表)中,您没有做到。我建议您也这样做,因为(谁知道呢?)创建表的命令可能会以某种方式继续锁定文件。远射。。。但是值得一试吗?你有运行谷歌桌面搜索(或其他文件索引器)吗?如前所述,可以帮助您找到它
另外,数据库的文件名是什么?发件人:
请非常非常小心地命名数据库,尤其是扩展名
例如,如果您为所有数据库提供扩展名.sdb(SQLite数据库,很好的名称,嘿?我还是这么认为的,当我选择它的时候…),您会发现sdb扩展名已经与APPFIX包关联
现在,这里是最可爱的部分,APPFIX是一个Windows XP可以识别的可执行文件/包,它将(强调我的)将数据库添加到系统还原功能中
这意味着,请跟我在一起,每次您向数据库写入任何内容时,Windows XP系统都会认为一个血腥的可执行文件已更改,并将您的整个800兆数据库复制到系统还原目录
我推荐DB或DAT之类的产品
看起来您未能将该命令与已创建的事务相链接。 而不是:
using (SQLiteCommand command = conn.CreateCommand())
你应使用:
using (SQLiteCommand command = new SQLiteCommand("<INSERT statement here>", conn, trans))
另一件事是你不需要在内存中缓存任何东西。您可以依赖SQLite日志机制来存储不完整的事务状态。我们在使用TransactionScope类的嵌套事务时遇到了非常类似的问题。我们认为所有数据库操作都发生在同一个线程上…但是我们被事务机制捕获了…更具体地说是
public void Commit()
{
using (SQLiteConnection conn = new SQLiteConnection(this.connString))
{
conn.Open();
SQLiteTransaction trans = conn.BeginTransaction();
try
{
using (SQLiteCommand command = conn.CreateCommand())
{
command.Transaction = trans; // Now the command is linked to the transaction and don't try to create a new one (which is probably why your database gets locked)
command.CommandText = "INSERT OR IGNORE INTO [MY_TABLE] (col1, col2) VALUES (?,?)";
command.Parameters.Add(this.col1Param);
command.Parameters.Add(this.col2Param);
foreach (Data o in this.dataTemp)
{
this.col1Param.Value = o.Col1Prop;
this. col2Param.Value = o.Col2Prop;
command.ExecuteNonQuery();
}
}
trans.Commit();
}
catch (SQLiteException ex)
{
// You need to rollback in case something wrong happened in command.ExecuteNonQuery() ...
trans.Rollback();
throw;
}
}
}
using(TransactionScope scope = new TransactionScope(TransactionScopeOptions.RequiresNew))
{
...
scope.Complete()
}