根据SQLite 的原子提交原则,任何时候,都只有一个排它的"写入锁",当一个事务发生了写入操作,其后任何其他线程/进程都将不能写入数据库的任何一部分.
测试表明,如果发生并发写入,线程将陷入停顿状态,竞争锁的双方都不能继续往下执行.这将导致严重的并发问题.
由上面的分析得知,尽管SQLite可以同时使用多个事务,但始终只用一个事务能够写入数据,所以,可以等同的认为,SQLite只用一个事务.
* 注:普通的查询操作也是默认带事务的.
申明一个全局(静态)的事务对象,在程序任何地方使用事务,都将使用该事务对象。如果同时有其他的更新操作,也将使用该事务对象,从而保证所有的事务操作 为一个事务对象,避免数据库被其他线程锁定。另外,由于没有使用分散的多个事务,有助于改善应用程序的整体性能。
* 注:本策略不适合高度并发的场合,如果想取得较高的并发性能,请尽量缩短事务使用的时间,或者使用其他RDBMS。
实现方式:
1,申明一个全局事务对象:
- /// <summary>
- /// 全局(静态)事务对象
- /// </summary>
- protected static SQLiteTransaction STTransaction = null;
2,申明一个事务计数器,任何时候有事务开始,计数器加一,事务结束,计数器减一,当事务计数器为0的时候,才可以真正关闭事务对象的数据库连接。
- /// <summary>
- /// 事务计数,只有计数为0方可在全局事务时候提交事务。
- /// </summary>
- protected static int STCount = 0;
3,为事务操作定义独立的打开和关闭数据库的方法:
- /// <summary>
- /// 打开全局事务连接
- /// </summary>
- protected void GOpen()
- {
- try
- {
- if (reader != null)
- {
- if (!reader.IsClosed)
- reader.Close();
- }
- if (STTransaction != null && STTransaction.Connection != null)
- {
- cn = STTransaction.Connection;
- }
- if (cn.State == ConnectionState.Closed)
- {
- //cn.DefaultTimeout = 100;
- cn.Open();
- }
- }
- catch (Exception ex)
- {
- this.TransactionErrorMessage = ex.Message;
- }
- }
- /// <summary>
- /// 关闭全局事务连接
- /// </summary>
- protected virtual void GClose()
- {
- try
- {
- if (reader != null)
- {
- if (!reader.IsClosed)
- reader.Close();
- }
- //没有事务,方可关闭连接
- if (cn.State == ConnectionState.Open && STCount <= 0)
- {
- cn.Close();
- STTransaction = null;
- }
- }
- catch { }
- }
- //注意程序中的事务计数器的使用。
4,提交和回滚事务的处理:
- /// <summary>
- /// 事务提交
- /// </summary>
- /// <returns></returns>
- public bool TranCommit()
- {
- try
- {
- //如果有全局事务正在执行,则计算当前事务计数,大于零,则不能提交该事务
- if (STTransaction != null)
- {
- STCount--;
- if (STCount > 0)
- return false;
- }
- tran.Commit(); // 事务提交
- bool success = true;
- return success;//提交成功
- }
- catch (Exception ex)
- {
- this.TransactionErrorMessage = ex.Message;
- bool success = false;
- return success;//提交失败
- }
- finally
- {
- this.GClose();
- }
- }
- /// <summary>
- /// 异常手动返回事务
- /// </summary>
- public void TranRollback()
- {
- if (STTransaction != null)
- {
- if (STCount > 0)
- STCount--;
- }
- tran.Rollback();
- this.GClose();
- }
- //注意程序中的全局事务对象和事务计数器的使用。
5,修改常规的数据更新操作方法。
这里说的数据更新操作是指 Insert ,Update,Delete 语句,不包括Select语句。所有相关的数据更新方法都必须调用事务连接对象的打开和关闭方法,如下面的方法:
- /// <summary>
- /// 更新数据
- /// </summary>
- /// <param name="sql"></param>
- /// <returns></returns>
- public virtual int Update(string sql)
- {
- this.GOpen();
- cmd = new SQLiteCommand(sql, cn);
- int i = 0;
- try
- {
- i = cmd.ExecuteNonQuery();
- }
- catch (Exception ex)
- {
- //错误处理。。。
- }
- finally
- {
- this.GClose();
- }
- return i;
- }
===========================================
根据本文的理论分析,我写了一个测试程序,在一个后台线程更新数据的时候,前台窗体可以自由的查询和更新数据的,不会发生数据库被锁定的情况。
欢迎大家提意见!