技术开发 频道

SQLite应用在本地缓存上需注意的几点

  构建缓存对象模型和缓存控制器

  每一块缓存对象,在数据库中会产生一个表,而表名称是有缓存控制器自动生成的,访问缓存的工作全部交由缓存控制器完成,通过缓存项的ID和ModuleKey来访问。

  在Sqlite中还需要一个系统表来维护每个缓存项和实际缓存存储表之间的对应关系,我们称之为配置表,它将在缓存控制器创建Sqlite缓存数据库文件时创建。

  配置表共有以下几个字段,分别和缓存对象模型CdlCacheItem类映射:

列名称说明
Id缓存的唯一数字编号
ModuleKey缓存模块名称,一个模块可以有多个缓存数据,ID可以区分。实际应用时,某个功能时会经常缓存数据的,所以通过ModuleKey就可以得到这个功能所有的缓存列表,然后选定其中的部分缓存来进行使用。
Comments缓存说明
TableName缓存数据存储的数据表名称
AddDate缓存时间戳

  创建数据库的方法如下:

static void CreateDB()
        {
            
//总共有ID、ModuleKey、Comments、AddDate这几列
            string sql = "CREATE TABLE SYSCDLTABLES(ID INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,MODULEKEY VARCHAR(200),COMMENTS VARCHAR(500),TABLENAME VARCHAR(100),ADDDATE DATETIME)";
            SQLiteDBHelper
.CreateDB(CACHEFILEPATH, sql);
        }

  每个缓存项(缓存对象模型)定义如下,和配置表对应:

/// <summary>
    /// 缓存项对象
    /// </summary>
    /// <Author>Tecky Lee</Author>
    /// <Date>2011-1-11 15:11</Date>
    public class CdlCacheItem
    {
        int m_id;

        
public int Id
        {
            get {
return m_id; }
            set { m_id
= value; }
        }
        
string m_moduleKey;

        
public string ModuleKey
        {
            get {
return m_moduleKey; }
            set { m_moduleKey
= value; }
        }
        
string m_comments;

        
public string Comments
        {
            get {
return m_comments; }
            set { m_comments
= value; }
        }
        
string m_tableName;

        
public string TableName
        {
            get {
return m_tableName; }
            set { m_tableName
= value; }
        }
        DateTime m_timestamp;

        
public DateTime Timestamp
        {
            get {
return m_timestamp; }
            set { m_timestamp
= value; }
        }
    }

  下面是控制器的接口定义:

public interface ICdlCacheController
    {
        void BeginLoadRow();
        void EndLoadRow();
        
System.Collections.Generic.IList<CdlCacheItem> GetCdlCacheItems(string moduleKey);
        CdlCacheItem GetCdlCacheItems(int id);
        void LoadRow(
System.Data.DataRow row, string tableName);
        void LoadRow(IEnumerable
<object> row, string tableName);
        
string LoadTable(System.Data.DataTable dt, string moduleKey, string comments);
        
System.Data.Common.DbDataReader QueryCdlTableReader(CdlCacheItem item);
        
System.Data.DataTable QueryCdlTables(CdlCacheItem item);
        
System.Data.DataTable QueryCdlTables(string sql);
        void RemoveAllTables();
        void RemoveCdlTables(
string moduleKey);
        void RemoveCdlTables(
System.Collections.Generic.IList<CdlCacheItem> items);
        void RemoveCdlTables(CdlCacheItem item);
        void RemoveCdlTables(int id);
    }

  上面的函数下面来做个说明:

  1、BeginLoadRow、LoadRow和EndLoadRow,三个函数组为了在我们查询主数据库时使用Reader方式读取数据时,可以一条条将数据同时存放在缓存中。

  2、RemoveAllTables和RemoveCdlTables是用来删除缓存项的。

  3、GetCdlCacheItems,通过moduleKey得到多个缓存项。比如用户想基于这几天内保存的某个功能的数据做一次快速分析,那么我们就可以通过这个函数得到缓存列表,由用户选择列表中的一个来继续。

  4、QueryCdlTableReader,得到某个缓存数据的Reader对象,这样可以一行行的分析,一次读出大数据量的数据到DataTable中,内存可能会溢出的。

  5、QueryCdlTables,将某个缓存项查询并装载到DataTable中。

  提高缓存数据写入效率

  Sqlite在保存数据的时候,比如一次保存一个亿条的数据,一条条插入效率非常低下,网上也有人对其进行讨论。

  效率低下的主要原因在于IO操作次数过于频繁,所以在LoadTable或者是使用BeginLoadRow·EndLoadRow的时候,使用了事务来减少数据提交的次数,结果保存的效率非常的高,我测试的结果是400万条数据查询,只需要几十秒钟,这点时间相对于重新查一次远程服务器那是可以忽略了。

  下面给出BeginLoadRow和EndLoadRow的具体代码(只有在EndRow的时候才会提交一次数据):

SQLiteConnection m_connection;
        SQLiteCommand m_command;
        DbTransaction m_transaction;
        
public void BeginLoadRow()
        {
            m_connection
= new SQLiteConnection("Data Source=" + CACHEFILEPATH);

            m_connection
.Open();
            m_transaction
= m_connection.BeginTransaction();
            m_command
= new SQLiteCommand(m_connection);
        }
        
public void EndLoadRow()
        {
            
try
            {
                
if (m_command != null)
                    m_command
.Dispose();

                
if (m_transaction != null)
                {
                    m_transaction
.Commit();
                }

                
if (m_connection != null)
                {
                    m_connection
.Close();
                    m_connection
.Dispose();
                }
            }
            
catch (System.Exception ex)
            {
                LogHandle
.Error(ex);
            }
        }

  LoadTable函数内部也是调用BeginLoadRow·EndLoadRow模式来完成的。

  数据库文件如何创建:

  Sqlite数据库文件如果不存在,在执行sql语句的时候,会自动根据ConnetionString中指定的位置创建数据库文件,默认创建的空数据库只有4K。

  其他有待讨论的问题:

  1、我是将所有的缓存做到一个数据库文件中了,实际应用根据业务的不同,可以一份缓存数据一个文件也是很好管理的,维护也方便,资源管理器中就可以拷贝删除等。

  2、当我们存储一亿条数据到Sqlite的时候,因为Sqlite没有压缩数据,结果数据库文件就可以会有好几个G(这也不一定,适合数据库字段的多少,字段类型有关的)。文件太大就消耗了磁盘空间,而且用户或者程序如果不及时清理的,可能会耗尽磁盘空间。

  这里就必须建立一个机制,检查sqlite的缓存并及时清理,或者设置缓存应用的上限,当达到上限后自动根据时间戳清理历史缓存。

0
相关文章