ASP.NET站点性能提升-缓存

缓存可以将web页面保存在访问者的浏览器、中间代理和服务器内存中。这样,就不用每次请求都重新生成页面,降低了CPU和数据库的负载。

  • 浏览器缓存:保存文件在浏览器缓存中,这样浏览器就不需要下载页面。
  • 代理缓存:保存文件在代理服务器上。
  • 输出缓存:在web服务器上缓存.aspx页面,这样就不用重新生成页面,降低了CPU和数据库的负载。
  • 基于IIS的缓存:使用IIS内置的缓存。
  • 数据缓存:缓存独立的对象。

浏览器缓存

服务器可以通过cache-control响应头要求浏览器缓存内容。浏览器缓存有以下优点:

  • 快速文件获取:浏览器不需要访问Internet。
  • 减少带宽成本和服务器处理负载。
  • 可扩展性。流量越大,就有更多的浏览器缓存数据。

浏览器缓存也有缺点:

  • 不可预测的生命周期:因为其它网站也使用同样的缓存空间,浏览器可能随时删除你的文件。
  • 过期的内容:如果服务器上的文件发生变化,没有任何方法更新浏览器缓存。对于图片、CSS文件,可以使用在文件名中加入版本号的方法解决这个问题。
  • 安全风险。因为文件是存储在访问者的计算机中,一个没有经过授权的人只要可以访问计算机,就可以读文件。

OutputCache指令

在aspx页面的顶部加入OutputCache指令开启浏览器缓存。

<%@ Page ... %>
<%@ OutputCache Duration="300" Location="Any" VaryByParam="None" %>

这会使得服务器在响应中加入头:

Cache-Control: public, max-age=300
Expires: <date and time>

这告诉浏览器和中间代理可以缓存响应300秒。至于浏览器和中间代理是否这样做,那由浏览器和中间代理决定,没有保证它们会必须这么做。

VaryByParam是必须的,否则会收到编译错误。

Location值包括:

Location值缓存位置使用场景
Any服务器、浏览器、代理服务器所有情况。除非有以下的列举的理由不这么做。
None必须使用文件的最新版本。
Client浏览器服务器上没有足够的内存或者页面对于不同的访问是不同的。因为安全问题不能使用代理服务器。
DownStream浏览器、代理服务器服务器上没有足够的内存或者页面对于不同的访问是不同的。代理服务器将页面发送给错误的访问者也没关系。
Server服务器对浏览器使用的文件的版本完全控制。
ServerAndClient服务器和浏览器因为安全问题不能使用代理服务器。

如果把Location值设为Client或ServerAndClient,就禁止了代理服务器缓存。这会导致以Cache-Control头中加入“private”,告诉代理不要缓存文件:

 

Cache-Control: private, max-age=300
Expires: <date and time>

代码开启缓存

开启缓存:
Response.Cache.SetMaxAge(new TimeSpan(0, 5, 0));
Response.Cache.SetCacheability(HttpCacheability.Public);

关闭服务器缓存:

Response.Cache.SetNoServerCaching();

关闭代理服务器缓存:

Response.Cache.SetCacheability(HttpCacheability.Private);

禁用缓存

Response.Cache.SetCacheability(HttpCacheability.NoCache);

代理缓存

请求和响应在服务器和浏览器间可能通过代理。代理可以缓存内容,可以使用这些内容响应来自其它访问者的请求。

使用代理缓存的优点:

  • 如果代理一个用户访问网站时缓存了文件,它就可以使用缓存的文件响应其它不相关的访问者。
  • 更快获取文件。浏览器在地理上比web服务器更接近代理。
  • 与浏览器缓存相同,减少了web服务器负载、带宽。有更好的扩展性。

缺点:

  • 安全风险。代理可能将访问者相关的数据发送给完全不相关的访问者。
  • 代理不缓存有查询字符串的文件。
  • 不能用于设置cookie的响应。
  • 与浏览器缓存相同,代理随时丢弃缓存内容,并且当内容发生改变时,也不能更新代理缓存。

缓存同一页面的不同版本

根据请求头,同一个页面,可能有不同的版本。例如,当接收到一个有如下头的请求,可能发送英文版本:

Accept-Language: en-US

当接收到一个有如下头的请求,可能发送德文版本:

Accept-Language: de-DE

解决方案是告诉代理URL和Accept-Language请求头必须都匹配时,才能缓存中发送页面。可以在响应中发送Vary头:

Vary: Accept-Language

可以在OutputCache指令中包括VaryByHeader让ASP.NET发送那个头:

<%@ OutputCache Duration="300" Location="Any" VaryByParam="None"  
  VaryByHeader="Accept-Language" %>

也可以使用代码设置:

Response.Cache.VaryByHeaders["Accept-Language"] = true;

IIS有一个功能可以压缩响应,以节省带宽和传输时间。然后,当你打开动态文件压缩功能,这样IIS就可以压缩.aspx文件,IIS会覆盖Vary头:

Vary: Accept-Encoding

这样做是为了保证压缩的内容不会发送到不能解压缩的访问者。所以,如果需要根据头区分响应,并且使用动态压缩,不要使用代理缓存,将Location设置为Client、Server或ServerAndClient。

Cookies

Cookies和代理缓存不能结合的很好。当服务器发送了cookie,响应中就会包括一个Set-Cookie响应头:

Set-Cookie: MyCookie=LanguagePreference=Dutch; path=/

从那以后,浏览器就会在以后的每次请求的Cookie请求头中都会返回这人cookie,直到cookie过期:

Cookie: MyCookie=LanguagePreference=Dutch

当代理缓存页面时,也会缓存响应头。问题是cookies是用户相关的,所以不应该发送给另一个用户。牢记当第一次在session state中存储数据时,ASP.NET会使用session ID设置cookie。当用户成功登录时,ASP.NET也会设置cookie,记住用户。

一些用户不会缓存有Set-Cookie头的页面。但并不能得到保证。

所以,当设置cookie时,不要允许代理缓存。设置Location为Client、Server或ServerAndClient。

从URL中移除查询字符串

如果URL中包含?,即包含查询字符串时,很多代理不会缓存响应。

解决这个问题,例如default.aspx?id=55,可以使用default/55.aspx。如果使用System.Web.Routing命名空间中的类映射请求URL至处理程序,就可以很容易通过更新路由解决它,如果使用ASP.NET MVC也是一样的。否则,可以使用以下两种方法:

  • 使用IIS7的URLRewrite扩展。(最容易)
  • 使用Global.asax中的RewritePath方法。

IIS7 URLRewrite扩展

URLRewrite扩展允许在web.config中包含重写规则。

首先,安装URL Rewriting模块,下载地址:

http://www.iis.net/download/URLRewrite

然后,在web.config中加入:

<configuration>
...
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="restore query string">
          <match url="(.*)/(\d+)\.aspx$" />
          <action type="Rewrite" url="{R:1}.aspx?id={R:2}" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
...
</configuration>
这个规则匹配所有满足正则表达式(.*)/(\d+)\.aspx$。如果URL匹配,URL重写为{R:1}.aspx?id={R:2}。{R:1}包括第一组(.*)中的内容,{R:2}包括第二组(\d+)中的内容。

Global.asax中的RewritePath方法

如果使用IIS6,或者不能安装URLRewrite扩展,可以在Global.asax的Application_BeginRequest中使用RewritePath方法。

在Global.asax中加入代码:

private void Application_BeginRequest( 
  Object source, EventArgs e)
{
  HttpApplication application = (HttpApplication)source;
  HttpContext context = application.Context;
  string rawUrl = context.Request.RawUrl;
  string newUrl = Regex.Replace(rawUrl, @"/(\d+)\.aspx$",  
    @".aspx?id=$1");
  context.RewritePath(newUrl, false);
}

重置form action属性

ASP.NET页面post back到它们自己,它们的HTML form标签的action属性设置为页面的URL自身。现在,URL重写成内部格式,ASP.NET也将action属性设置成内部URL。在这里,这不是个问题,当重写代码得到表单的URL:default.aspx?id=55,它不匹配正则表达式。所以它不会修改URL,不会影响表单功能。

但是,如果需要将表单的action属性设置为原来的URL(例如/default/55.aspx),进行以下操作:

  1. 从HtmlForm继承一个新的控件:RawUrlForm。
  2. 覆写RenderAttributes方法:
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    public class RawUrlForm : HtmlForm
    {
      protected override void RenderAttributes(HtmlTextWriter writer)
      {
        this.Action = Page.Request.RawUrl;
        base.RenderAttributes(writer);
      }
    }
  3. 编辑web.config,告诉ASP.NET使用新控件代替HtmlForm。这样,当它生成form标签时,它就可以自动使用新控件代替HtmlForm,不用修改HTML:
    <pages>
      <tagMapping>
        <add tagType="System.Web.UI.HtmlControls.HtmlForm"
          mappedTagType="RawUrlForm" />
      </tagMapping> 
    </pages>

输出缓存

输出缓存可以在服务器上缓存整个页面或部分页面。它有这些优点:

  • 灵活性:通过查询字符串变量、请求头或自定义变量支持缓存不同版本的文件,
  • 允许缓存部分页面。
  • 缓存项的生存时间相对来说是可预测的,因为没有其它网站的缓存空间竞争。如果使用共享主机,在同一个web服务器上有多个网站,则不能保证。

输出缓存也有缺点:

  • 没有必要缓存用户相关页面,因为这会存储很多低点击率页面。
  • 占用服务器内存。
  • 不能跨服务器缓存。在服务器园中,每一个服务器都有自己的缓存。在每个服务器缓存中会重复存在,占用更多的内存并且导致不同缓存间的不一致。
  • 如果应用程序池回收或web服务器重启,缓存内存消失。

可以通过将缓存放置到单独的缓存服务器上解决后两个缺点。考虑使用Memcached(http://memcached.org)和Windows Server AppFabrid(http://msdn.microsoft.com/en-us/windowsserver/ee695849.aspx)。另外,也可以实现输出缓存提供程序。

缓存什么不缓存什么

因为输出缓存占用内存,所以必须仔细考虑缓存什么,不缓存什么。一般情况下:下列内容是值得缓存的:

  • 频繁被请求的内容。这些内容更新不频繁,或者提供了过期的内容也不是大问题。
  • 生成操作很昂贵的内容。
  • 占用空间很小的内容。

首页是主要的候选者,因为它很可能是最繁忙的页面。在一个电子商务网站,分类页面或报表显示页面也可能需要缓存,因为生成它们很昂贵。

另一方面,用户相关的内容只会被一个用户获取。如果有成千上万个产品页面,缓存所有页面也没有意义。对于postback的响应也不应该被缓存,因为他们依赖于POST请求的值。

启用输出缓存

启用输出缓存和启用浏览器缓存和代理缓存方式相同,在页面顶部加入OutputCache指令。

<%@ Page ... %>
<%@ OutputCache Duration="300" Location="Any" VaryByParam="None" %>

这里设置失效时间为300秒。如果页面的静态程度更高,设置更长的时间。

输出缓存示例

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CachedTime.
aspx.cs" Inherits="CachedTime" %>
<%@ OutputCache Duration="10" Location="Any" VaryByParam="None" %>
<%= DateTime.Now.ToString() %>

VaryByParam

前面的OutputCache指令都没有考虑将查询字符串。为了解决这个问题,在OutputCache指令中使用VaryByParam告诉ASP.NET根据查询字符串参数id缓存页面的一个版本:

<%@ OutputCache Duration="10" Location="Any" VaryByParam="id" %>

如果页面使用多个查询字符串参数,使用分号分隔它们:

<%@ OutputCache Duration="10" Location="Any" VaryByParam="id;location" %>

使用星号根据所有参数缓存,不管它们的名称:

<%@ OutputCache Duration="10" Location="Any" VaryByParam="*" %>

VaryByHeader

根据请求头存储不同的版本,使用VaryByHeader:

<%@ OutputCache Duration="10" Location="Any" VaryByHeader="Accept-Language" VaryByParam="None" %>

Http请求头完整列表:

http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html

VaryByCustom

如何不通过查询字符串参数或请求头缓存页面?假设有一个页面根据工作日进行缓存。

首先在OutputCache指令中加入VaryByCustom属性:

<%@ OutputCache Duration="300" Location="Any" VaryByCustom="weekday" VaryByParam="None"  %>
显然,ASP.NET不知道怎么解释weekday。它会调用Global.asax中的GetVaryByCustomString方法:
public override string GetVaryByCustomString(HttpContext context, 
string custom)
{
    if (custom == "weekday")
    {
        return DateTime.Now.DayOfWeek.ToString();
    }
    else
    {
        return base.GetVaryByCustomString(context, custom);
    }
}

根据浏览器VaryByCustom

因为很多页面有浏览器相关的页面,所以需要根据浏览器类型进行缓存。VaryByCustom能够识别浏览器,不用实现GetVaryByCustomString方法:

<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ OutputCache Duration="300" Location="Any" VaryByCustom="browser" VaryByParam="None"  %>
<%= Request.UserAgent %>

它会使用浏览器名和主版本号区分浏览器。

可以混合使用VaryByParam、VaryByHeader和VaryByCustom。但是要注意到底创建了多少个版本,会占用多少空间。

部分缓存

如果只缓存部分页面,将这些部分放到用户控件中,缓存这些控件。通过在用户控件顶部包括OutputCache指令缓存用户控件:

<%@ Control Language="C#" %>
<%@ OutputCache Duration="10" VaryByParam="None"  %>
Cached user control: <%= DateTime.Now.ToString() %>

在用户控件的OutputCache指令中不能使用Location。

可以像使用非缓存用户控件一样使用缓存用户控件。然而,如果用户控件向页面暴露了属性,当用户控件缓存时,不能访问这些属性。

如果在不同的页面上使用同一个用户控件,每一个页面都会缓存用户控件。page1.aspx上的用户控件可能和page2.aspx上的同一个用户控件有不同的输出。如果想让不同的页面上用户控件有相同的输出,在OutputCache指令中使用Shared属性,这样用户控件只会缓存一次:

<%@ OutputCache Duration="300" VaryByParam="None" Shared="true"%>

Post-cache substitution

Post-cache substitution是部分缓存的反面。部分缓存是在非缓存页面缓存部分内容,Post-cache substitution是在缓存页面不缓存部分内容。

使用OutputCache指令缓存页面。

如果希望页面上放置非缓存内容,放置一个substitution控件。当页面生成时或从缓存中获取页面时,会调用一个静态方法。这个静态方法返回一个字符串,插入到Substitution控件的位置。

<asp:Substitution MethodName="GetSubstitutedTime" runat="server"/>

当运行时调用静态方法时,会传入HttpContext,提供请求的信息。不能依靠页面对象、生命周期事件或其它控件,因为当从缓存中获取页面时,这些都不会触发。

private static string GetSubstitutedTime(HttpContext context)
{
  return DateTime.Now.ToString();
}

如果希望针对每个请求都执行一段代码,例如登录,可以使用Post-cache substitution执行代码。

创建输出缓存提供程序

输出缓存提供程序是从OutputCacheProvider继承的类。在这个类中覆写四个方法:Set,Add,Get和Remove。输出缓存提供程序的框架如下:

using System.Web.Caching;
public class FileCacheProvider : OutputCacheProvider
{
    public override void Set(string key, object item, DateTime expiry) 
{ ... }
    public override object Add(string key, object item, DateTime 
expiry) { ... }
    public override object Get(string key) { ... }
    public override void Remove(string key) { ... }
}

Set

这个方法的任务是给定一个键值,存储一个条目。它也接收一个UTC日期/时间,表示条目失效时间。失效时间需要与条目一同存储,这样当获取条目时,就可以检查它。如果给定键值的条目已经在缓存中已经存在,它的内容和失效时间就要被覆盖。

Get

这个方法只是给定一个键值从缓存中获取条目并返回它的内容。如果条目不存在或者已经失效,Get返回null。

Add

这个方法有点像Set。如果条目在缓存中不存在,存储条目,并且由方法返回。但是,如果缓存中已经存在条目,那么不会进行更新操作,返回缓存中已经存在的条目。

Remove

这个方法根据给定的键值从缓存中移除条目。

使用输出缓存提供程序

修改web.config:

<configuration>
  <system.web>
  ...
    <caching>
      <outputCache defaultProvider="FileCache">
        <providers>
          <add name="FileCache" type="OutputCacheProviderSample.FileCacheProvider, OutputCacheProviders" />
        </providers>
      </outputCache>
    </caching>
  ...
  </system.web>
</configuration>

通过设置属性defaultProvider为新提供程序的名字,保证ASP.NET默认使用这个新提供程序提供输出缓存。

如果只希望对特定页面或用户控件使用新提供程序。

  • 在web.config中配置新提供程序,但默认使用ASP.NET内置的提供程序。
  • 对特定页面使用新提供程序。
  • 对特定用户控件使用新提供程序。

首先,在web.config中设置默认提供程序为AspNetInternalProvider:

<outputCache defaultProvider="AspNetInternalProvider">
  <providers>
    <add name="FileCache"  
      type="OutputCacheProviderSample.FileCacheProvider,  
      OutputCacheProviderSample" />
  </providers>
</outputCache>

在Global.asax中覆写GetOutputCacheProviderName方法,根据请求的文件名选择输出缓存提供程序。

public override string GetOutputCacheProviderName(HttpContext context)
{
    if (context.Request.Path.EndsWith("CachedWeekday.aspx"))
        return "FileCache";
    else
        return base.GetOutputCacheProviderName(context);
}

在用户控件中指定输出缓存提供程序更简单,只要在OutputCache指令中加入ProviderName属性:

<%@ OutputCache Duration="10" VaryByParam="None" ProviderName="FileCache" %>

内核缓存和IIS 7输出缓存

当使用服务器上的输出缓存时,也可以使用内核缓存和IIS 7输出缓存。IIS 7首先引入输出缓存,但IIS6也已经支持内核缓存。从这些缓存中响应请求要比从ASP.NET输出缓存中快,因为它们是集成在IIS中的。下面是工作原理。

当一个新的请求到达web服务器时,最先由内核驱动http.sys处理。这个驱动首先尝试从内核缓存中响应请求。如果不能,它会将请求发送到IIS的一个监听线程端口。这个IIS线程试图从IIS 7的输出缓存中响应请求。如果不能,将请求转交ASP.NET,这会激活另一个线程处理请求。ASP.NET尝试从它自己的缓存中响应请求,如果不能,再生成输出。结果会发送给第三个线程,再发送给浏览器。

这意味着,如果可以从内核缓存中提供文件,可以节省三个线程切换和切换到用户模式的开销。这使得从内核缓存中提供文件非常快。IIS 7输出缓存需要一个线程切换和切换到用户模式,但仍然要比ASP.NET缓存快。

配置IIS缓存

使用服务器缓存OutputCache指令时,内核缓存和IIS 7输出缓存会启用。但是,如果使用HTTP Handler生成一个PNG图片时,就没办法加OutputCache指令。通过在IIS管理器中配置缓存开启内核缓存和IIS 7输出缓存。

  1. 打开IIS管理器。
  2. 选择服务器或站点。
  3. 双击输出缓存图标。
  4. 点击添加,打开添加缓存规则对话框。对于两种类型的类型,可以指定被缓存文件的扩展名、失效时间、是否使用文件更改通知。IIS 7输出缓存也允许根据查询字符串变量和HTTP头缓存不同的版本。点击“高级”进行设置。

内存缓存的局限

  • 不能动态压缩响应。静态压缩是可以的。
  • 不能缓存默认文档,所以不能缓存站点首页。如果访问http://mydomain.com没有键入文件名,不会缓存。如果访问http://mydomain.com/default.aspx,就会被缓存。
  • 请求不能包括查询字符串。当然,可以使用代理缓存中使用的方法重写URL。但这不会起作用:如果在OutputCache指令中使用VaryByParam或VaryByHeaders,不会缓存。如果不使用VaryByParam,ASP.NET缓存只会存储一个版本,而无论是否有查询字符串。
其它更多不能缓存响应的情况:

检查内核缓存的内容

使用命令检查内核缓存内容:

netsh http show cachestate

注意,即使进行了配置,一个响应也不会立即被缓存。只有当达到一定的访问频率也会被缓存,默认阈值是每10秒超过2次。

数据缓存

数据缓存中的目标是在代码中缓存单独的对象,而不是通过页面指令缓存页面或用户控件。它允许在服务器的缓存中保存键值对。同一网站的所有请求都可以访问这些键值对。但和输入缓存一样,如果是由多台服务器缓存的web园,每一台服务器只能访问它自己的缓存。数据缓存是非常灵活的,可以指定失效时间,优先级和依赖项,例如文件和数据库对象。

基本用法

访问缓存就像使用一个字典。最简单的向缓存中加入对象的方法是给一个键名赋值一个对象:

Cache["key"] = myObject;

获取对象:

MyObject myObject = (MyObject)Cache["key"];

当获得一个对象,实际上是获得了缓存中对象的引用。这个对象在所有的处理请求的线程间是共享的。所以要保证对象是线程安全的,或者在获取对象后进行深拷贝。

一个条目不能保证在缓存中,因为由于内存压力,缓存管理器可能移除其中的条目。如果请求的条目不在缓存中,就会得到null。所以从缓存中获取条目的模式如下:

MyObject myObject = (MyObject)Cache["key"];
if (myObject == null)
{
    myObject = new MyObject();
    // Load data into myObject ... 
    Cache["key"] = myObject;
}
其中一个问题是缓存由多个线程访问。对缓存的单个操作是线程安全,但一个操作序列是不安全的。当一个线程将数据载入到myObject中,另一个线程进入,发现myObject在缓存中找不到,也开始加载数据。所以要使用锁:
public Object lockObject = new Object();
...
lock (lockObject)
{
  MyObject myObject = (MyObject)Cache["key"];
  if (myObject == null)
  {
    myObject = new MyObject();
    // Load data into myObject ...
    Cache["key"] = myObject;
  }
}

牢记Cache只是返回缓存条目的一个引用,它可能随时消失,即使代码在运行中。所以避免写这样的代码:

// Don't do this
MyObject myObject = (MyObject)Cache["key"];
// Lengthy operation ...
// Access myObject as retrieved from cache
string s = myObject.data;

失效

ASP.NET允许为缓存条目设置绝对失效时间。也支持相对失效时间:当一个条目没有被获取一段时间后将被移除。

指定绝对失效时间:

using System.Web.Caching;
Cache.Insert("key", myObject, null, DateTime.Now.AddMinutes(30), Cache.NoSlidingExpiration);  

指定相对失效时间:

Cache.Insert("key", myObject, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(30));

优先级

一些条目的创建操作要比其它对象昂贵。你可以指定一个条目的优先级。当有内存压力时,低优先级的条目会比优先级的条目先移除。

优先级有六级:Low、BelowNormal、Normal、AboveNormal、High和NotRemovable。如果未指定优先级,默认是Normal。

设定优先级:

Cache.Insert("key", expensiveObject, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, null);

文件依赖

当向缓存中插入条目时,可以指定条目依赖其它条目、文件或数据库条目。这样,当这些依赖项改变时,缓存条目会自动移除。这个特性对缓存文件和缓存数据库记录非常有用,因为这将防止缓存条目过期。

指定文件依赖:

using System.Web.Caching;
CacheDependency cd = new CacheDependency(filePath);
Cache.Insert(key, fileContent, cd);

数据库依赖

数据库依赖要比文件依赖复杂。因为,跟踪文件修改很容易,而数据库依赖需要跟踪一张表。为了得到需要依赖的表,需要分析生成数据的查询语句。所以,查询语句就有很多限制。还需要对数据库进行一些配置。

当数据库执行一个可能改变由查询语句返回的数据的命令时,它会通知web服务器,服务器会移除缓存条目。命令不需要来自web服务器,来自任何地方的会影响数据的命令都可以。

显然这有一些额外的开销,所以只缓存不经常改变的数据库项。

SQL Server 2005以上版本支持数据依赖。SQL Server 2000不支持,但是通过一种更复杂的方式可以实现这个功能。

查询语句限制

数据库依赖只能使用查询语句的一个小子集。如果超过这个子集,无论数据是否发生变化,都会移除缓存项。最重要的限制:

  • 只能使用单一的SELECT语句或包括单一SELECT语句的存储过程。
  • 如果使用存储过程,当创建存储过程时,ANSI_NULLS和QUOTED_IDENTIFIER必须是ON。
  • 在存储过程中不要使用SET NOCOUNT ON。也不要使用TRY CATCH或RETURN。
  • 不要使用SELECT *。指定所有列名。
  • 使用两部分表名,例如dbo.Books。不要在表名中包括数据库名。
  • 不要包括计算字段。
  • 不要使用聚合表达式。如果使用GROUP BY,COUNT_BIG和SUM是允许的。但SUM不能用在可为空列。不要使用HAVING、CUBE或ROLLUP。
  • 不能引用视图、系统表、派生表、临时表或表变量。
  • 查询语句中双精度/实型数据类型不能包括比较或表达式。
  • 不能使用TOP。

其它的一些限制:

  • 不能使用未命名列,或重复列名。
  • SELECT语句中做为表达式的列不能出现超过一次。
  • 不能使用PIVOT或UNPIVOT操作符。
  • 不能使用UNION、INTERSECT或EXCEPT。不能使用DISTINCE、COMPUTE或COMPUTE BY或INTO。
  • 不能引用服务器全局变量。
  • 不能包含子查询,全连接或自连接。
  • 不能使用text、ntext和image类型。
  • 不能使用CONTAINS或FREETEXT全文谓词。
  • 不能使用数据集函数,包括OPENROWSET和OPENQUERY。
  • 不能使用非确定性函数,包括ranking和windowing函数。
  • 不能包括FOR BROWSE信息。
  • 不能引用队列。
  • 不能包括不会改变和不会返回结果集的条件语句(例如,WHERE 1=0)。
  • 不能指定READPAST锁提示。
  • 不能引用Service Broker队列。
  • 不能引用同义词。

一个可以使用的存储过程的示例:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE GetData
AS
BEGIN
  --Do NOT use SET NOCOUNT ON;
  SELECT [BookId], [Title], [Author], [Price]
    FROM [dbo].[Book]
END
GO

如果需要使用数据库依赖,通过这些步骤实现:

  1. 开启SQL Server Service Broker。
  2. 在网站中开启监听服务。
  3. 在缓存中插入条目时创建依赖。

开启Service Broker

  1. 重启数据库服务器,保证数据库没有活动的会话,包括来自Visual Studio的会话。
  2. 执行命令(使用数据库替换TuneUp):
    USE TuneUp
    ALTER DATABASE TuneUp SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE

开启监听服务

在Global.asax中的Application_Start中开启监听服务:

<%@ Application Language="C#" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Configuration" %>
<script runat="server">
  void Application_Start(object sender, EventArgs e)
  {
    // Code that runs on application startup
    string connectionString = ...;
    SqlDependency.Start(connectionString);
  }
</script>

创建依赖

using (SqlCommand cmd = new SqlCommand(sql, connection))
{
  using (SqlDataAdapter adapter = new SqlDataAdapter(cmd))
  {
    SqlCacheDependency cd = new SqlCacheDependency(cmd);
    dataSet = new DataSet();
    adapter.Fill(dataSet);
    Cache.Insert("key", dataSet, cd);
  }
}

条目移除回调方法

可以指定一个方法,当一个条目过期时,由ASP.NET调用。

private void ItemRemovedCallback(
    string itemKey, object itemValue,
    CacheItemRemovedReason cacheItemRemovedReason) { ... }
Cache.Insert(
    "key", expensiveObject, null,
    Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration,
    CacheItemPriority.Default,
    new CacheItemRemovedCallback(ItemRemovedCallback));

CacheItemRemovedReason的可能值:

描述
Removed通过调用Cache.Remove移除或通过调用Cache.Insert替换条目。
Expired因为绝对或相对失效时间到达而失效。
UnderusedASP.NET释放内存而移除。
DependencyChanged因为关联的依赖发生改变而移除。

注意事项

  • 条目可能独立于请求失效。当条目移除回调方法运行时,不能保存存在请求上下文,所以HttpContext.Current可能为null。所以,如果需要在回调方法中访问缓存,使用HttpRuntime.Cache,不要使用HttpContext.Current.Cache。
  • 回调方法应该是静态的。如果是一个对象的实例方法,垃圾收集器不会清除这个对象,因为缓存条目对它有引用。
  • 回调方法不仅在条目从缓存移除时执行,当条目被替换时也会执行。替换时,老的条目首先被移除,然后新的条目被插入。

服务器缓存的正确使用

使用ASP.NET提供的缓存相关计数器可以查看缓存的使用情况。如果使用输出缓存:

分类:ASP.NET Applications

Output Cache Hit Ratio:从输出缓存中提供服务的请求比率。

Output Cache Hits:输出缓存命中总数。

Output Cache Misses:输出缓存未命中总数。

Output Cache Entries:输出缓存中的条目数量。

Output Cache Turnover Rate:每秒输出缓存添加和移除的数量。

如果使用数据缓存:

分类:ASP.NET Applications

Cache API Hit Ratio:从数据缓存中提供服务的请求比率。

Cache API Hits:数据缓存命中总数。

Cache API Misses:数据缓存未命中总数。

Cache API Entries:数据缓存中的条目数量。

Cache API Turnover Rate:每秒数据缓存添加和移除的数量。

当解释这些数字时,注意:

  • 当有足够的内存时,期望命中率在80%以上。
  • 缓存中条目的数量越多,就需要越多的内存取得期望的命中率,就越有可能造成内存不足。
  • 每秒数据缓存添加和移除的数量越多,重新生成数据需要的资源越多。

更多资料

转载于:https://www.cnblogs.com/ntwo/archive/2010/11/24/1886948.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值