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会对数据库造成压力。

目录
相关文章
|
5月前
|
存储 人工智能 NoSQL
AI大模型应用实践 八:如何通过RAG数据库实现大模型的私有化定制与优化
RAG技术通过融合外部知识库与大模型,实现知识动态更新与私有化定制,解决大模型知识固化、幻觉及数据安全难题。本文详解RAG原理、数据库选型(向量库、图库、知识图谱、混合架构)及应用场景,助力企业高效构建安全、可解释的智能系统。
|
8月前
|
存储 关系型数据库 数据库
附部署代码|云数据库RDS 全托管 Supabase服务:小白轻松搞定开发AI应用
本文通过一个 Agentic RAG 应用的完整构建流程,展示了如何借助 RDS Supabase 快速搭建具备知识处理与智能决策能力的 AI 应用,展示从数据准备到应用部署的全流程,相较于传统开发模式效率大幅提升。
附部署代码|云数据库RDS 全托管 Supabase服务:小白轻松搞定开发AI应用
|
6月前
|
存储 弹性计算 Cloud Native
云原生数据库的演进与应用实践
随着企业业务扩展,传统数据库难以应对高并发与弹性需求。云原生数据库应运而生,具备计算存储分离、弹性伸缩、高可用等核心特性,广泛应用于电商、金融、物联网等场景。阿里云PolarDB、Lindorm等产品已形成完善生态,助力企业高效处理数据。未来,AI驱动、Serverless与多云兼容将推动其进一步发展。
279 9
|
6月前
|
存储 弹性计算 安全
现有数据库系统中应用加密技术的不同之处
本文介绍了数据库加密技术的种类及其在不同应用场景下的安全防护能力,包括云盘加密、透明数据加密(TDE)和选择列加密。分析了数据库面临的安全威胁,如管理员攻击、网络监听、绕过数据库访问等,并通过能力矩阵对比了各类加密技术的安全防护范围、加密粒度、业务影响及性能损耗。帮助用户根据安全需求、业务改造成本和性能要求,选择合适的加密方案,保障数据存储与传输安全。
|
8月前
|
安全 Java Nacos
0代码改动实现Spring应用数据库帐密自动轮转
Nacos作为国内被广泛使用的配置中心,已经成为应用侧的基础设施产品,近年来安全问题被更多关注,这是中国国内软件行业逐渐迈向成熟的标志,也是必经之路,Nacos提供配置加密存储-运行时轮转的核心安全能力,将在应用安全领域承担更多职责。
|
7月前
|
存储 人工智能 数据库
视图是什么?为什么要用视图呢?数据库视图:定义、特点与应用
本文三桥君深入探讨数据库视图的概念与应用,从定义特点到实际价值全面解析。视图作为虚拟表具备动态更新、简化查询、数据安全等优势,能实现多角度数据展示并保持数据库重构的灵活性。产品专家三桥君还分析了视图与基表关系、创建维护要点及性能影响,强调视图是提升数据库管理效率的重要工具。三桥君通过系统讲解,帮助读者掌握这一常被忽视却功能强大的数据库特性。
1771 0
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
516 0
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
319 7
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
402 0