我做了一些搜索,我知道为了从同一个应用程序中的不同线程更新一个SQLite数据库文件,线程必须共享相同的SQLiteConnection
对象。我已经在我的应用程序中采取措施来实现这一目标。
我的问题与交易有关。我需要每个线程在一个事务中执行其更新,并且我需要将每个事务与其他事务完全隔离。也就是说,在线程A中回滚或提交事务应该对线程B正在进行的工作没有影响。
我是否必须做一些特别的事情来实现这一点,或者在每个线程的操作开始时创建一个SQLiteTransaction
并将其分配给SQLiteCommand's Transaction
属性呢?
答案 0 :(得分:3)
在玩完这个之后,我发现我之前关于需要跨线程共享单个连接的结论是错误的。每个线程现在:
ConnectionManager
的特殊单例类创建SQLiteConnection
个实例,打开它们,并配置每个连接打开后必须设置的常用设置。ConnectionManager
类中的方法访问数据库之前创建新连接。SQLiteConnection
。 ConnectionManager
类允许所有需要连接到数据库的代码获取其连接,并以一致的方式设置所有数据库设置。我的代码需要设置的大多数属性实际上都在连接字符串中,但是有一个在那里无法指定,所以我需要另一种机制。这是ConnectionManager
的样子:
public class ConnectionManager {
public int BusyTimeout { get; set; }
public static ConnectionManager Instance {
get {
if ( iInstance == null ) {
lock ( instanceLock ) {
if ( iInstance == null )
iInstance = new ConnectionManager();
}
}
return iInstance;
}
}
private static ConnectionManager iInstance = null;
private static object instanceLock;
private ConnectionManager() {
BusyTimeout = Convert.ToInt32( TimeSpan.FromMinutes( 2 ).TotalMilliseconds );
}
static ConnectionManager() {
instanceLock = new object();
}
public SQLiteConnection CreateConnection( string connectionString ) {
SQLiteConnection connection = new SQLiteConnection( connectionString );
connection.Open();
using ( SQLiteCommand command = connection.CreateCommand() ) {
command.CommandText = string.Format( "PRAGMA busy_timeout={0}", BusyTimeout );
command.ExecuteNonQuery();
}
return connection;
}
}
要使用ConnectionManager
类,可以将实例变量或局部变量设置为单例实例的副本,如下所示:
_connectionManager = ConnectionManager.Instance;
要获取和使用数据库连接,每个线程都使用这样的代码:
using ( SQLiteConnection connetion = _connectionManager.CreateConnection( connectionString ) {
// Thread's database operation code here
}
事实证明,让它工作的真正技巧是将busy_timeout pragma设置为比默认值更长的值。 SQLite内部完全是线程安全的并且自己序列化请求,因此您的代码只需告诉SQLite等待任何当前正在执行的操作完成。我们的代码已经过结构化,因此数据库引擎中的任何故障都会导致在等待几秒后重试操作,因此这很有效。
默认的2分钟等待时间足以让99.99%的所有操作完成。实际上,如果事情需要超过2分钟才能完成,我们需要重新审视该区域,并以任何方式加快速度。
答案 1 :(得分:0)
我可能会弄错,但是对于多线程应用程序,我认为您一次只需要与数据库建立一个连接。我看不到您的代码如何阻止2个线程试图同时连接到同一数据库。如果有2个线程尝试同时获取连接,则当您尝试打开另一个线程使用的连接时,最后一个尝试获取连接的线程将获得SQLITE_BUSY响应。您想像这样锁定连接的创建:
public class ConnectionManager
{
public static int BusyTimeout { get; set; }
public static object instanceLock;
static ConnectionManager()
{
instanceLock = new object();
BusyTimeout = Convert.ToInt32(TimeSpan.FromMinutes(2).TotalMilliseconds);
}
public static SQLiteConnection CreateConnection(string connectionString)
{
SQLiteConnection connection = new SQLiteConnection(connectionString);
connection.Open();
using (SQLiteCommand command = connection.CreateCommand())
{
command.CommandText = string.Format("PRAGMA busy_timeout={0}", BusyTimeout);
command.ExecuteNonQuery();
}
return connection;
}
}
这是使用连接管理器的数据访问对象的示例。
class UserDAO
{
public void UpdateUser(User user)
{
int result = -1;
string connectionString = "Data Source=C:\\Counter\\Counter.sqlite";
lock (ConnectionManager.instanceLock)
{
using (SQLiteConnection conn = ConnectionManager.CreateConnection(connectionString))
{
using (SQLiteCommand cmd = new SQLiteCommand(conn))
{
cmd.CommandText = "UPDATE counter_user " +
"SET runnerfirstname=@runnerfirstname, " +
"runnerlastname=@runnerlastname, " +
"parentlastname=@parentlastname, " +
"parentfirstname=@parentfirstname, " +
"runnergrade=@runnergrade, " +
"email=@email, " +
"laps=@laps, " +
"vestnumber=@vestnumber, " +
"tagid=@tagid " +
"WHERE id=@id";
cmd.Prepare();
cmd.Parameters.AddWithValue("@runnerfirstname", user.RunnerFirstName);
cmd.Parameters.AddWithValue("@runnerlastname", user.RunnerLastName);
cmd.Parameters.AddWithValue("@parentlastname", user.ParentLastName);
cmd.Parameters.AddWithValue("@parentfirstname", user.ParentFirstName);
cmd.Parameters.AddWithValue("@runnergrade", user.RunnerGrade);
cmd.Parameters.AddWithValue("@email", user.Email);
cmd.Parameters.AddWithValue("@laps", user.Laps);
cmd.Parameters.AddWithValue("@vestnumber", user.VestNumber);
cmd.Parameters.AddWithValue("@tagid", user.TagId);
cmd.Parameters.AddWithValue("@id", user.Id);
try
{
result = cmd.ExecuteNonQuery();
}
catch (SQLiteException e)
{
Console.WriteLine("test");
}
}
conn.Close();
}
}
}