ASP.NET状缓存Cache的应用-提高数据库读取速度

简介: 原文:ASP.NET状缓存Cache的应用-提高数据库读取速度一、 Cache概述       既然缓存中的数据其实是来自数据库的,那么缓存中的数据如何和数据库进行同步呢?一般来说,缓存中应该存放改动不大或者对数据的实时性没有太多要求的数据。
原文: ASP.NET状缓存Cache的应用-提高数据库读取速度

一、 Cache概述
  
    既然缓存中的数据其实是来自数据库的,那么缓存中的数据如何和数据库进行同步呢?一般来说,缓存中应该存放改动不大或者对数据的实时性没有太多要求的数据。这样,我们只需要定期更新缓存就可以了。相反,如果缓存的更新频率过快的话,使用缓存的意义就不是很大了,因此更新缓存的时候需要一次性从数据库中读取大量的数据,过于频繁地更新缓存反而加重了数据库的负担。
那么ASP.NET中的Cache又提供了哪些缓存的过期策略呢?
· 永不过期。和Application一样,缓存永不过期。
· 绝对时间过期。缓存在某一时间过期,比如5分钟后。
· 变化时间过期(平滑过期)。缓存在某一时间内未访问则超时过期,这个和Session有点类似,比如我们可以设定缓存5分钟没有人访问则过期。
· 依赖过期。缓存依赖于数据库中的数据或者文件中的内容。一旦数据库中某些表的数据发生变动或者文件内容发生变动,则缓存自动过期。
缓存过期后我们就要更新缓存了,ASP.NET提供了两种更新策略。
· 被动更新。缓存过期以后手动进行更新。
· 主动更新。缓存过期以后在回调方法中更新。


二、 Cache性能与过期策略


首先,在页面上添加两个按钮,并双击按钮实现Click事件处理方法。


程序代码 程序代码
<asp:Button ID="btn_GetDataFromCache" runat="server" OnClick="btn_GetData_Click"
Text="从缓存中读取数据" />
<asp:Button ID="btn_GetDataFromDb" runat="server" OnClick="btn_GetDataFromDb_Click"
Text="从数据库中读取数据" />


第一个按钮实现从缓存读取数据。

注意:本例需要using以下命名空间。

程序代码 程序代码
using System.Diagnostics; // 用于精确测定时间间隔
using System.Web.Caching; // 用于缓存的策略
using System.IO;   // 用于文件操作

protected void btn_GetData_Click(object sender, EventArgs e)
{
    InsertRecord();
    Stopwatch sw=new Stopwatch();
    sw.Start();
    if (Cache["Data"]==null)
    {
        Response.Write("缓存无效<br/>");
    }
    else
    {
        DataSet ds = Cache["Data"] as DataSet;
        Response.Write(string.Format("查询结果:{0}<br/>", ds.Tables[0].Rows[0][0]));
        Response.Write(string.Format("耗费时间:{0}<br/>", sw.ElapsedTicks));
    }
}
在这里有几点需要说明。
· 一开始的InsertRecord()方法是我们自己创建的,用来向数据库插入一条记录。这样,我们就能看出来数据是否是从缓存中读取的了。
InsertRecord()方法如下:

程序代码 程序代码
private void InsertRecord()
{
    using (SqlConnection conn = new SqlConnection(@"server=(local)\SQLEXPRESS;
    database=Forum;Trusted_Connection=True"))
    {
        conn.Open();
        using (SqlCommand cmd = new SqlCommand("Insert into CacheTest (Test) values
       ('Test')", conn))
        {
            cmd.ExecuteNonQuery();
        }
    }
}

· 如果缓存存在则输出查询结果和查询耗费的时间,如果缓存不存在则输出“缓存无效”。
· Stopwatch类用于精确测定逝去的时间,ElapsedTicks属性返回了间隔的计数器刻度,所谓计数器刻度就是系统的计数器走过了多少次。当然,Stopwatch还有ElapsedMilliseconds能返回间隔的总毫秒数。之所以使用ElapsedTicks,因为它是一个更小的时间单位。
第二个按钮直接从数据库读取数据。

程序代码 程序代码
protected void btn_GetDataFromDb_Click(object sender, EventArgs e)
{
    InsertRecord();
    Stopwatch sw = new Stopwatch();
    sw.Start();
    DataSet ds = GetData();
    Response.Write(string.Format("查询结果:{0}<br/>", ds.Tables[0].Rows[0][0]));
    Response.Write(string.Format("耗费时间:{0}<br/>", sw.ElapsedTicks));
}
在这里,我们把读取数据的操作使用一个GetData()方法进行了封装,方法实现如下:

程序代码 程序代码
private DataSet GetData()
{
    DataSet ds = new DataSet();
    using (SqlConnection conn = new SqlConnection(@"server=(local)\SQLEXPRESS;
    database=Forum;Trusted_Connection=True"))
    {
        SqlDataAdapter da = new SqlDataAdapter("select count(*) from CacheTest", conn);
        da.Fill(ds);            
    }
    return ds;
}

为了能体现出缓存的效率,我们在Forum数据库中又新建立了一个CacheTest数据表,表结构很简单,如图4-1所示。

图4-1  CacheTest表结构
我们在表中插入了10万条以上的记录,使得表的大小达到了100MB左右。
运行程序,单击“从数据库中读取数据”按钮,如图4-2所示,

图4-2 从数据库读取数据需要花费大量的时间

我们可以看到,这个操作耗费了相当多的时间。
因为我们直接从数据库读取count(*),所以每次单击按钮查询结果显示的数字都会+1。现在你单击“从缓存中读取数据”肯定是显示“缓存无效”,因为我们还没有添加任何缓存。
然后,我们在页面上添加三个按钮并双击按钮创建事件处理方法,三个按钮使用不同的过期策略添加缓存。

程序代码 程序代码
<asp:Button ID="btn_InsertNoExpirationCache" runat="server" Text="插入永不过期缓存"
OnClick="btn_InsertNoExpirationCache_Click" />
<asp:Button ID="btn_InsertAbsoluteExpirationCache" runat="server" Text="插入绝对时间
过期缓存" OnClick="btn_InsertAbsoluteExpirationCache_Click" />
<asp:Button ID="btn_InsertSlidingExpirationCache" runat="server" Text="插入变化时间
过期缓存" OnClick="btn_InsertSlidingExpirationCache_Click" />

三个按钮的Click事件处理方法如下:

程序代码 程序代码
protected void btn_InsertNoExpirationCache_Click(object sender, EventArgs e)
{
    DataSet ds = GetData();
    Cache.Insert("Data", ds);
}
protected void btn_InsertAbsoluteExpirationCache_Click(object sender, EventArgs e)
{
    DataSet ds = GetData();
    Cache.Insert("Data", ds,null, DateTime.Now.AddSeconds(10), TimeSpan.Zero);
}
protected void btn_InsertSlidingExpirationCache_Click(object sender, EventArgs e)
{
    DataSet ds = GetData();
    Cache.Insert("Data", ds, null, DateTime.MaxValue, TimeSpan.FromSeconds(10));
}
我们来分析一下这三种过期策略。
· 永不过期。直接赋值缓存的Key和Value即可
· 绝对时间过期。DateTime.Now.AddSeconds(10)表示缓存在10秒后过期,TimeSpan.Zero表示不使用平滑过期策略。
· 变化时间过期(平滑过期)。DateTime.MaxValue表示不使用绝对时间过期策略,TimeSpan.FromSeconds(10)表示缓存连续10秒没有访问就过期。
在这里,我们都使用了Insert()方法来添加缓存。其实,Cache还有一个Add()方法也能向缓存中添加项。不同之处在于Add()方法只能添加缓存中没有的项,如果添加缓存中已有的项将失败(但不会抛出异常),而Insert()方法能覆盖原来的项。
注意:和Application不同,这里不需要使用在插入缓存的时候进行锁操作,Cache会自己处理     并发。
现在,我们就可以打开页面对这三种过期策略进行测试了。
1.单击“从缓存中读取数据”按钮,提示“缓存无效”。
2.单击“从数据库中读取数据”按钮,查询结果显示现在记录总数为100646。
3.单击“插入永不过期缓存”按钮,然后连续单击“从缓存中读取数据”按钮,可以发现,无论过去多久,缓存始终没有过期,而且观察记录查询结果可以发现值始终没有发生变化。不同的是,从缓存中读取数据的效率比从数据库中读取数据提高了几个数量级,如图4-3所示,你可以和图4-2进行比较。

图4-3  从缓存中读取数据所花费的时间
4.单击“插入绝对时间过期缓存”,然后连续单击“从缓存中读取数据”按钮,大约10秒过期后,页面提示“缓存无效”,说明缓存过期了。
5.单击“插入变化时间过期缓存”,然后连续单击“从缓存中读取数据”按钮,缓存始终不过期,如果我们等待10秒后再去单击按钮,页面提示“缓存无效”,说明缓存过期了。
我们再来看一下依赖过期策略。所谓依赖过期就是缓存的依赖项(比如一个文件)的内容改变之后缓存也就失效了。由于篇幅关系,这里只介绍文件依赖。我们在页面上再加两个按钮并双击按钮添加Click事件处理方法。

程序代码 程序代码
<asp:Button ID="btn_ModifyFile" runat="server" Text="修改文件" OnClick="btn_ModifyFile_
Click" />
<asp:Button ID="btn_AddFileDependencyCache" runat="server" Text="插入文件依赖缓存"
OnClick="btn_AddFileDependencyCache_Click" />


在本例中,我们将使缓存依赖一个txt文本文件。因此,首先在项目中添加一个test.txt文本文件。单击“修改文件”按钮实现文件的修改。

程序代码 程序代码
protected void btn_ModifyFile_Click(object sender, EventArgs e)
{
    FileStream fs = new FileStream(Server.MapPath("test.txt"), FileMode.Append,
    FileAccess.Write);
    StreamWriter sw = new StreamWriter(fs);
    sw.WriteLine(DateTime.Now.ToString());
    sw.Close();
    fs.Close();
}


我们通过在文件的最后写入当前的时间来修改文件。插入文件依赖缓存按钮的事件处理方法如下:

程序代码 程序代码
protected void btn_AddFileDependencyCache_Click(object sender, EventArgs e)
{
    CacheDependency cd = new CacheDependency(Server.MapPath("test.txt"));
    DataSet ds = GetData();
    Cache.Insert("Data", ds, cd);
}
添加文件依赖缓存同样简单,通过CacheDependency关联了一个文件依赖。
现在就可以打开页面进行测试了。
1.单击“从缓存中读取数据”按钮,提示“缓存无效”。
2.单击“从数据库中读取数据”按钮,查询结果显示现在记录总数为100710。
3.单击“插入文件依赖缓存”按钮,然后连续单击“从缓存中读取数据”按钮,可以发现,无论过去多久,缓存始终没有过期,而且观察记录查询结果可以发现值始终没有发生变化。
4.单击“修改文件”按钮,然后单击“从缓存中读取数据”按钮,提示“缓存无效”。由于文件已经修改了,依赖这个文件的缓存立刻失效了。


三、  Cache的更新策略
最后,我们来讨论缓存的更新策略。在Web程序中我们通常会使用被动更新。所谓被动更新,就是在调用数据的时候判断缓存是否为空,如果为空则先更新缓存然后再从缓存中读取数据,如果不为空则直接从缓存中读取数据。可以把“从缓存中读取数据”按钮的Click事件处理方法修改成如下,实现被动更新。


程序代码 程序代码
protected void btn_GetData_Click(object sender, EventArgs e)
{
    InsertRecord();
    DataSet ds = new DataSet();
    Stopwatch sw = new Stopwatch();
    sw.Start();
    if (Cache["Data"] == null)
    {
        ds = GetData();
        Cache.Insert("Data", ds, null, DateTime.Now.AddSeconds(10), TimeSpan.Zero);
    }
    else
    {
        ds = Cache["Data"] as DataSet;
    }
    Response.Write(string.Format("查询结果:{0}<br/>", ds.Tables[0].Rows[0][0]));  
    Response.Write(string.Format("耗费时间:{0}<br/>", sw.ElapsedTicks));
}

我们可以看出,如果没有人访问数据缓存是不会更新的,只有缓存被访问的时候发现缓存无效才会去更新。这样很明显的一个缺点就是,如果缓存过期了更新操作将花费很长时间,这个时候的查询也需要花费很多时间。我们可以利用缓存的回调功能让缓存过期后自动续建实现自动更新的目的。

程序代码 程序代码
protected void btn_InsertActiveUpdateCache_Click(object sender, EventArgs e)
{
    DataSet ds = GetData();
    Cache.Insert("Data", ds, null, DateTime.Now.AddSeconds(10), TimeSpan.Zero,
    CacheItemPriority.Default, CacheRemovedCallback);
}

最后一个参数表明缓存被移除以后自动调用CacheRemovedCallback()方法,方法实现如下。

程序代码 程序代码
private void CacheRemovedCallback(String key, object value, CacheItemRemovedReason
removedReason)
{
    DataSet ds = GetData();
    Cache.Insert(key, ds, null, DateTime.Now.AddSeconds(10), TimeSpan.Zero, CacheItemPriority.
    Default, CacheRemovedCallback);
}


在回调方法中,我们再次插入一个支持回调的缓存。这样,缓存被移除以后又能自动更新了。说了这么多创建缓存的方法,读者可能会问怎么手动移除缓存呢?比如我们要移除Key="Data"的缓存只需要:
Cache.Remove("Data");

你可能会马上想到用Cache.RemoveAll()方法移除所有缓存,可是Cache没有提供这样的方法,我们只能通过遍历来实现移除所有缓存。

程序代码 程序代码
IDictionaryEnumerator CacheEnum = HttpRuntime.Cache.GetEnumerator();
while (CacheEnum.MoveNext())
{
    Cache.Remove(CacheEnum.Key.ToString());
}



四、  Cache总结
同样,我们以第一节中的几个问题结束对Cache的讨论。
· 存储的物理位置。服务器内存。
· 存储的类型限制。任意类型。
· 状态使用的范围。当前请求上下文,所有用户共用一份。
· 存储的大小限制。任意大小。
· 生命周期。有多种过期策略控制缓存的销毁。
· 安全与性能。数据总是存储在服务端,安全性比较高,但不易存储过多数据。
· 优缺点与注意事项。检索数据速度快,过期策略丰富。注意别把对实时性要求很高的数据放到Cache中,不断更新Cache会对数据库造成压力。

目录
相关文章
|
3天前
|
人工智能 容灾 关系型数据库
【AI应用启航workshop】构建高可用数据库、拥抱AI智能问数
12月25日(周三)14:00-16:30参与线上闭门会,阿里云诚邀您一同开启AI应用实践之旅!
|
1月前
|
架构师 数据库
大厂面试高频:数据库乐观锁的实现原理、以及应用场景
数据库乐观锁是必知必会的技术栈,也是大厂面试高频,十分重要,本文解析数据库乐观锁。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试高频:数据库乐观锁的实现原理、以及应用场景
|
1月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
43 5
|
1月前
|
缓存 NoSQL 数据库
运用云数据库 Tair 构建缓存为应用提速,完成任务得苹果音响、充电套装等好礼!
本活动将带大家了解云数据库 Tair(兼容 Redis),通过体验构建缓存以提速应用,完成任务,即可领取罗马仕安卓充电套装,限量1000个,先到先得。邀请好友共同参与活动,还可赢取苹果 HomePod mini、小米蓝牙耳机等精美好礼!
|
1月前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
55 3
|
1月前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
66 2
|
2月前
|
存储 缓存 数据库
缓存技术有哪些应用场景呢
【10月更文挑战第19天】缓存技术有哪些应用场景呢
|
2月前
|
XML 存储 数据库
XML在数据库中有哪些应用?
【10月更文挑战第17天】XML在数据库中有哪些应用?
53 2
|
3月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
51 7