我正在为SQLite数据库执行大量INSERTS.我只使用一个线程.我批量写入以提高性能,并在发生崩溃时保持一定的安全性.基本上我在内存中缓存了一堆数据然后在我认为合适时,我遍历所有数据并执行INSERTS.代码如下所示:
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二进制文件.
我正在编译.NET 2.0.
我正在使用VS 2008.
db是本地文件.
所有这些活动都封装在一个线程/进程中.
病毒防护已关闭(虽然我认为只有通过网络连接才有意义).
根据苏格兰人的帖子,我实施了以下更改:
日记帐模式设置为持久
通过System.Windows.Forms.Application.AppData
Windows调用存储在C:\ Docs + Settings\ApplicationData中的DB文件
没有内在的例外
见证了两台不同的机器(尽管硬件和软件非常相似)
一直在运行进程监视器 - 没有多余的进程将自己附加到数据库文件 - 问题肯定在我的代码中......
有没有人知道这里发生了什么?
我知道我只是丢掉了一大堆代码,但我一直在努力解决这个问题.我要感谢任何使这个问题结束的人!
布赖恩
更新:
感谢您的建议到目前为止!我已经实现了许多建议的更改.我觉得我们越来越接近答案......但是......
上面的代码在技术上有效,但它是不确定的!除了永久中立旋转之外,我们无法保证做任何事情.在实践中,它似乎在第1次和第10次迭代之间的某处工作.如果我以合理的间隔对我的提交进行批处理,那么损害将会减轻,但我真的不想让这种状态下的东西......
更多建议欢迎!
看起来您无法将命令与您创建的事务相关联.代替:
using (SQLiteCommand command = conn.CreateCommand())
你应该使用:
using (SQLiteCommand command = new SQLiteCommand("", conn, trans))
或者您可以在构建后设置其Transaction属性.
我们在这里 - 你对故障的处理是不正确的:
该命令的ExecuteNonQuery方法也可能失败,您并没有真正受到保护.您应该将代码更改为:
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; } } }
另一件事是你不需要在内存中缓存任何东西.您可以依赖SQLite日记机制来存储未完成的事务状态.
运行Sysinternals Process Monitor并在运行程序时过滤文件名,以排除任何其他进程对其执行任何操作并查看程序对文件执行的操作.远射,但可能会给出一个线索.
在TransactionScope类中使用嵌套事务时,我们遇到了非常相似的问题。我们以为所有数据库操作都在同一线程上发生...但是我们被事务处理机制所吸引...更具体地说是环境事务。
基本上,上链有一个事务,借助ado的神奇之处,该连接会自动加入。结果是,即使我们认为我们在单个线程上写入数据库,写入也没有真正发生直到提交了最高的交易。在这一“不确定”的时刻,数据库被写入,导致数据库被锁定在我们的控制范围之外。
解决方案是通过确保使用以下方法来确保sqlite数据库不直接参与环境事务:
using(TransactionScope scope = new TransactionScope(TransactionScopeOptions.RequiresNew)) { ... scope.Complete() }