SQLite做为本地缓存的应用需要注意的地方

原文:SQLite做为本地缓存的应用需要注意的地方

今天看到了园友陆敏计的一篇文章<<C#数据本地存储方案之SQLite>>, 写到了SQLite的诸多优点,尤其适应于本地数据缓存和应用程序。

转自陆兄的内容,来夸夸Sqlite:

SQLite官方网站: http://www.sqlite. org/ 时第一眼看到关于SQLite的特性。

1. ACID事务

2. 零配置 – 无需安装和管理配置

3. 储存在单一磁盘文件中的一个完整的数据库

4. 数据库文件可以在不同字节顺序的机器间自由的共享

5. 支持数据库大小至2TB

6. 足够小, 大致3万行C代码, 250K

7. 比一些流行的数据库在大部分普通数据库操作要快

8. 简单, 轻松的API

9. 包含TCL绑定, 同时通过Wrapper支持其他语言的绑定

10. 良好注释的源代码, 并且有着90%以上的测试覆盖率

11. 独立: 没有额外依赖

12. Source完全的Open, 你可以用于任何用途, 包括出售它

13. 支持多种开发语言,C, PHP, Perl, Java, ASP .NET,Python

正好前一段时间我做了这方面的应用,我就结合陆兄的这篇文章,谈谈我在Sqlite本地缓存业务数据时的经验,给大家借鉴一下。我开发时比较仓促,很多地方请大家多提意见。

解决的问题

首先介绍我用Sqlite解决的实际问题是什么?

问题1:某个功能的数据需要连接一个远程数据库查询速度很慢,查一次数据不容易,希望能够重复利用之前查过的数据集。

问题2:非常大的数据量比如几千万甚至几亿条数据,一次性读取到DataTable中,会内存溢出的,所以在第一次分析时就是通过Reader的方式,分析完一条后并不在内存中保存,但是紧接着用户的第二次分析、第三次分析还是要用到的第一次分析的数据,如果我们重新查询一次远程服务器,效率可想而知啊。

结合上面的2个问题,为了解决效率问题和数据重复利用度,减少数据库服务器的压力,我才用Sqlite缓存数据(当然这不是唯一也不是最好的解决方案) 。

优化SQLiteHelper

陆兄的SQLiteHelper类我增加了几个有用的方法:

第一个方法是GetSchema,得到某个表的表结构。

        /// <summary>
        /// 查询数据库中的所有数据类型信息
        /// </summary>
        /// <returns></returns>
        public DataTable GetSchema()
        {
            using (SQLiteConnection connection = new SQLiteConnection(connectionString))
            {
                connection.Open();
                DataTable data = connection.GetSchema("TABLES");
                connection.Close();
                //foreach (DataColumn column in data.Columns)
                //{
                //    Console.WriteLine(column.ColumnName);
                //}
                return data;
            }
        }

第二个方法是IsTableExist,判断SQLite数据库重某个表是否存在 。

        /// <summary>
        /// 判断SQLite数据库表是否存在
        /// </summary>
        /// <param name="dbPath">要创建的SQLite数据库文件路径</param>
        public bool IsTableExist(string tableName)
        {
            using (SQLiteConnection connection = new SQLiteConnection(connectionString))
            {
                connection.Open();
                using (SQLiteCommand command = new SQLiteCommand(connection))
                {

                    command.CommandText = "SELECT COUNT(*) FROM sqlite_master where type=‘table‘ and name=‘" + tableName + "‘";
                    int iaaa = Convert.ToInt32(command.ExecuteScalar());
                    if (Convert.ToInt32(command.ExecuteScalar()) == 0)
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
            }
        }

第三个方法是Query,执行查询语句,返回DataSet

        /// <summary>
        /// 执行查询语句,返回DataSet
        /// </summary>
        /// <param name="SQLString">查询语句</param>
        /// <returns>DataSet</returns>
        public DataSet Query(string SQLString)
        {
            using (SQLiteConnection connection = new SQLiteConnection(connectionString))
            {
                DataSet ds = new DataSet();
                try
                {
                    connection.Open();
                    SQLiteDataAdapter command = new SQLiteDataAdapter(SQLString, connection);
                    command.Fill(ds, "ds");
                }
                catch (System.Data.SQLite.SQLiteException ex)
                {
                    throw new Exception(ex.Message);
                }
                return ds;
            }
        }

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

每一块缓存对象,在数据库中会产生一个表,而表名称是有缓存控制器自动生成的,访问缓存的工作全部交由缓存控制器完成,通过缓存项的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的缓存并及时清理,或者设置缓存应用的上限,当达到上限后自动根据时间戳清理历史缓存。

原文地址:https://www.cnblogs.com/lonelyxmas/p/10236260.html

时间: 2024-10-26 08:07:08

SQLite做为本地缓存的应用需要注意的地方的相关文章

Android -- ImageLoader本地缓存

传送门 <Android -- ImageLoader简析>  http://www.cnblogs.com/yydcdut/p/4008097.html 本地缓存 在缓存文件时对文件名称的修改提供了两种方式,每一种方式对应了一个Java类 1)  HashCodeFileNameGenerator,该类负责获取文件名称的hashcode然后转换成字符串. 2)  Md5FileNameGenerator,该类把源文件的名称同过md5加密后保存. 两个类都继承了FileNameGenerato

Java8简单的本地缓存实现

译文出处: 踏雁寻花   原文出处:lukaseder 这里我将会给大家演示用ConcurrentHashMap类和lambda表达式实现一个本地缓存.因为Map有一个新的方法,在key为Null的时候自动计算一个新的value值.非常适合实现cache.来看下代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void main(String[] args) {     for (int i = 0; i < 10; i++)   

HTML5权威指南--Web Storage,本地数据库,本地缓存API,Web Sockets API,Geolocation API(简要学习笔记二)

1.Web Storage HTML5除了Canvas元素之外,还有一个非常重要的功能那就是客户端本地保存数据的Web Storage功能. 以前都是用cookies保存用户名等简单信息. 但是cookie有下面几个问题: a:大小:cookies的大小被限制在4KB b:带宽:cookies随HTTP事务一起被发送,因此会浪费一部分发送的cookies时使用的带宽. c:复杂性:要正确的操纵cookies是很困难的. Web Storage分为两种: <1>sessionStorage 将数

故障排除 Mybatis ORA-01000 和 本地缓存问题

※异常信息 环境 MyBatis Oracle11.2c Terasoluna BatchCaused by: org.springframework.jdbc.UncategorizedSQLException: ### Error updating database. Cause: java.sql.SQLException: ORA-00604: 再帰SQLレベル1でエラーが発生しました.ORA-01000: 最大オープン?カーソル数を超えました.ORA-00604: 再帰SQLレベル1で

本地缓存方式

iOS本地缓存数据方式有五种: 1.直接写文件方式:可以存储的对象有NSString.NSArray.NSDictionary.NSData.NSNumber,数据全部存放在一个属性列表文件(*.plist文件)中. 2.NSUserDefaults(偏好设置),用来存储应用设置信息,文件放在perference目录下. 3.归档操作(NSkeyedArchiver),不同于前面两种,它可以把自定义对象存放在文件中. 4.coreData:coreData是苹果官方iOS5之后推出的综合型数据库

Android ImageLoader 本地缓存

本地缓存 在缓存文件时对文件名称的修改提供了两种方式,每一种方式对应了一个Java类 1)  HashCodeFileNameGenerator,该类负责获取文件名称的hashcode然后转换成字符串. 2)  Md5FileNameGenerator,该类把源文件的名称同过md5加密后保存. 两个类都继承了FileNameGenerator接口 在DefaultConfigurationFactory类中提供了一个工厂方法createFileNameGenerator,该方法返回了一个默认的F

iOS 获取本地缓存文件大小及清除

由于项目需求中要求计算出应用内的缓存文件的大小及清除工作,做了一个小小的模块提供给大家分享,随便说句买苹果还是选个16g以上的那样妈妈就不会担心你的内存不用够用了哦! // 清除本地缓存文件 + (void)clearCacheFile { NSString *cachPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSArray *fi

iOS五种本地缓存数据方式

iOS五种本地缓存数据方式 iOS本地缓存数据方式有五种:前言 1.直接写文件方式:可以存储的对象有NSString.NSArray.NSDictionary.NSData.NSNumber,数据全部存放在一个属性列表文件(*.plist文件)中. 2.NSUserDefaults(偏好设置),用来存储应用设置信息,文件放在perference目录下. 3.归档操作(NSkeyedArchiver),不同于前面两种,它可以把自定义对象存放在文件中. 4.coreData:coreData是苹果官

本地缓存localstorage使用

最近做项目遇到一个问题,即从“个人中心”点击进入“修改支付宝”,需要自动获取用户手机号怎么做? 修改支付宝的api不提供用户手机号数据,但是发现个人中心提供,于是想通过localstorage在个人中心页面获取到手机号然后保存在本地缓存,进入修改支付宝页面后再获取出来. 经简单查询用法和实验,代码如下: var phoneNumber = data.data.memberInfo.mobile; //将后台数据保存在一个变量中 if( window.locastorage ){ localsto