[转载]分布式缓存Memcached---开篇的话 - aspnetdream - 博客园

mikel阅读(1010)

[转载]分布式缓存Memcached—开篇的话 – aspnetdream – 博客园.

大数据、高并发这是最近一段时间内被IT行业提的最为火热的概念,看过《大数据时代》的同学应该不会陌生大数据的概念,尤其是对于互联网行业来说,大数 据是每天都要接触的问题,简单通俗地说,每天得大数据,就给系统的性能带来了新的要求—高并发,有了这样一个技术应用的实际背景,对技术人员就提出了 要求,随着java环境下的Hadoop,PHP的Memcached,包括Redis的产生和技术的扩散,我们就开始接触这些应用与大数据高并发领域的 架构理念,Memcached基于C语言编写,天生的跨平台,让这个技术很容易产生了Windows平台的版本,.NET环境对Memcached玩转就 显得让人兴奋不已了。

我的环境:

Thinkpad T430  I5/2.8GHZ  8G/DDR3

Windows 8 Enterprise X64

首先,下载Memecached服务端安装包,据我所知官方不提供windows版本的安装包,需要自己编译,不过在github上有MSI安装包可以下载。

服务端:http://code.jellycan.com/memcached/

客户端:http://sourceforge.net/projects/memcacheddotnet/

当然还存在一些其它较好的客户端,这里我实现一个分布式缓存实现单点登录的功能,就使用了这个客户端。下载完成后,含exe安装包文件夹解压copy到自定义目录。我的环境中,我把它放到了E盘根目录。命令行安装

完成安装后,使用start命令来启动memcached,这样memcached就会作为一个windows服务进程在运行

服务端运行完成后,可以基本了解一下这个版本的Memcahced在windows平台下的一些基本特性:

Memcached默认使用端口是11211

默认最大连接数是1024个

默认最大使用内存是64M

默认每个键值对,值存储空间为1M
服务端准备工作完成后,我们需要支持进行memcached编程的客户端,对于net技术来说,就是需要支持的DLL,当然,如果对Memcached特 别有研究的同学,可以自己写Dll,Memecahed服务端会暴露相应的API供客户端调用,使用Sockt连接来进行数据交互。我使用的是 Memcacheddotnetclient_1.1.5的客户端 。

所有的准备工作完成之后,就要进入我们的代码阶段了。打开visual studio新建一个项目(控制台、web、win都可以),然后项目引入客户端DLL就可以进行编程了。为了方便单点登录的Session数据共享的功能,我新建的是Web MVC程序。

程序目录结构:

实现单点登录毫无疑问,首先使用MVC框架建立Login页面

View Login前端

一个密码输入框,一个用户名输入框,简单模拟登录。当用户输入用户名密码之后,进行校验,校验通过将生成SessionID,通过Respons写 入客户端,并将用户登录信息存入Memecached,在下次用户直接请求Index页面的时候,首先通过Cookie存入的SessionId查找 Memecached,如果查找到并且验证合格则直接跳转到Index,否则继续进入Login要求用户信息验证。

HomeControll代码如下:

复制代码
        [HttpPost]
        public ActionResult LoginAction(FormCollection values)
        {
            string name = values["txtName"];
            string password = values["txtPass"];

            if (name == myname && password == mypassword)
            {
                string sessionId = Guid.NewGuid().ToString();
                MemcachHelper.Set(sessionId, name + "$" + password, DateTime.Now.AddDays(1));
                Response.Cookies["SessionId"].Value = sessionId;
                return View("Index");
            }

            return View("Login");
        }
复制代码

使用POST方式提交用户页面信息进行验证,我这里放了2个静态变量,真实的环境中,这个数据应该是从数据库中查询到的。

当用户直接请求非身份验证的页面的时候,我们首先是查找Cookie中是否有我们在登录的时候写入的SessionID,存在SessionId则直接去Memecached拉取前次登录的信息。

代码实现如下:

复制代码
        public ActionResult Index()
        {
            if (Request.Cookies["SessionId"] == null || string.IsNullOrEmpty(Request.Cookies["SessionId"].ToString()))
            {
                return View("Login");
            }
            else
            {
                string key = Request.Cookies["SessionId"].Value.ToString();
                string[] arrUserInfo = MemcachHelper.Get(key).ToString().Split('$');
                if (arrUserInfo.Length >= 2)
                {
                    if (arrUserInfo[0] == myname && arrUserInfo[1] == mypassword)
                    {
                        return View("Index");
                    }
                    return View("Login");
                }
            }
            return View("Login");
        }
复制代码

实现起来的业务逻辑并不复杂了,这里两次操作了Memecached,所以在Model中加入了一个操作Memecached的帮助类,类代码如下:

复制代码
    public class MemcachHelper
    {
        private static MemcachedClient mclient;

        static MemcachHelper()
        {
            string[] serverList = new string[] { "127.0.0.1:11211" };

            SockIOPool pool = SockIOPool.GetInstance("First");
            pool.SetServers(serverList);
            pool.Initialize();
            mclient = new MemcachedClient();
            mclient.PoolName = "test";
            mclient.EnableCompression = false;
        }

        public static bool Set(string key, object value, DateTime expiry)
        {
            return mclient.Set(key, value, expiry);
        }

        public static object Get(string key)
        {
            return mclient.Get(key);
        }
    }
复制代码

干脆利索的三个功能,由于使用的是静态类的静态方法,所以构造函数中对Memcached进行来了初始化,在类进行静态方法调用的时候,第一次回调 用静态构造函数,将Memecached需要进行初始化的信息进行写入。其次,定义了Get和Set两个方法,一个读取,一个写入数据。操作 Memecached就这么简单。

这里稍微解释一下静态构造函数中的代码:serverList实际上就是我们说的服务器集群列表,这个列表直接告诉了我们那台服务器,通过那个端口,加入这个memecached集群。建立SockIOPool连接,初始化连接池,并设置相应的过期时间。

 

抛砖引玉,欢迎吐槽学习!贴上我的源代码:http://files.cnblogs.com/aspnetdream/MemcachedMVC.rar

[转载]SQL Server 2008 对XML 数据类型操作 - 分享我的世界 - 博客园

mikel阅读(936)

[转载]SQL Server 2008 对XML 数据类型操作 – 分享我的世界 – 博客园.

一、前言

SQL Server 2005 开始,就增加了 xml 字段类型,也就是说可以直接把 xml 内容存储在该字段中,并且 SQL Server 会把它当作 xml 来对待,而不是当作 varchar 来对待。

随着SQL Server 对XML字段的支持,相应的,T-SQL语句也提供了大量对XML操作的功能来配合SQL Server中XML字段的使用。本文主要说明如何使用SQL语句对XML进行操作。

二、定义XML字段

          在进行数据库的设计中,我们可以在表设计器中,很方便的将一个字段定义为XML类型。需要注意的是,XML字段不能用来作为主键或者索引键。同样,我们也 可以使用SQL语句来创建使用XML字段的数据表,下面的语句创建一个名为“docs”的表,该表带有整型主键“pk”和非类型化的 XML 列“xCol”:

CREATE TABLE docs (pk INT PRIMARY KEY, xCol XML not null)

XML类型除了在表中使用,还可以在存储过程、事务、函数等中出现。下面我们来完成我们对XML操作的第一步,使用SQL语句定义一个XML类型的数据,并为它赋值:

set @xmlDoc='<?xml version=”1.0″ ?>

              <books>

                            <book id=”0001″>

<title>C Program</title>

<author>David</author>

<price>21</price>

  </book>

  <book id=”0002″>

<title>你必须知道的.NET</title>

<author>王涛</author>

<price>79</price>

 </book>

      </books>’

select @xmlDoc 

三、XML字段注意点

  • SQL Server 中以 Unicode(UTF-16) 来存储 XML 数据。
  • XML 字段最多可存储 2G 的数据。
  • 可以像插入字符串一样向 XML 字段写入内容。
  • 当在 xml 数据类型实例中存储 XML 数据时,不会保留 XML 声明(如 <?xml version=’1.0′?>)。
  • 插入的 xml 内容的属性的顺序可能会与原 xml 实例的顺序变化。
  • 不保留属性值前后的单引号和双引号。
  • 不保留命名空间前缀。
  • 可以对 XML 字段中的 XML 内容建立索引。
  • 可以对 XML 字段中的 XML 内容建立约束,比如 age 节点必须大于等于 18。
  • 可以通过创建架构来对 XML 进行类型化,比如让 xml 内容的 <user> 节点下面必须有 <fullname> 节点。

四、查询操作

在定义了一个XML类型的数据之后,我们最常用的就是查询操作,下面我们来介绍如何使用SQL语句来进行查询操作的。

          在T-Sql中,提供了两个对XML类型数据进行查询的函数,分别是query(xquery)和value(xquery, dataType),其中,query(xquery)得到的是带有标签的数据,而value(xquery, dataType)得到的则是标签的内容。接下类我们分别使用这两个函数来进行查询。

1、使用query(xquery) 查询

我们需要得到书的标题(title),使用query(xquery)来进行查询,查询语句为:

select @xmlDoc.query(‘(books/book/title)[1]’)

运行结果如图:

2、使用value(xquery, dataType) 查询

同样是得到书的标题,使用value函数,需要指明两个参数,一个为xquery, 另一个为得到数据的类型。看下面的查询语句:

select @xmlDoc.value(‘(books/book/title)[1]’, ‘nvarchar(max)’)

运行结果如图:

 

3、查询属性值

无论是使用query还是value,都可以很容易的得到一个节点的某个属性值,例如,我们很希望得到book节点的id,我们这里使用value方法进行查询,语句为:

select @xmlDoc.value(‘(books/book/@id)[1]’, ‘nvarchar(max)’)

运行结果如图:

4、使用xpath进行查询

xpath是.net平台下支持的,统一的Xml查询语句。使用XPath可以方便的得到想要的节点,而不用使用where语句。例如,

–得到id为0002的book节点

select @xmlDoc.query(‘(/books/book[@id=”0002″])’)

上面的语句可以独立运行,它得到的是id为0002的节点。运行结果如下

五、修改操作

         SQL的修改操作包括更新和删除。SQL提供了modify()方法,实现对Xml的修改操作。modify方法的参数为XML修改语言。XML修改语言类似于SQL 的Insert、Delete、UpDate,但并不一样。

1、修改节点值

我们希望将id为0001的书的价钱(price)修改为100, 我们就可以使用modify方法。代码如下:

set @xmlDoc.modify(‘replace value of (/books/book[@id=0001]/price/text())[1] with “100”‘)

–得到id为0001的book节点

select @xmlDoc.query(‘(/books/book[@id=”0001″])’)

注意:modify方法必须出现在set的后面。运行结果如图:

 

2、删除节点

接下来我们来删除id为0002的节点,代码如下:

–删除节点id为0002的book节点

set @xmlDoc.modify(‘delete /books/book[@id=0002]’)

select @xmlDoc

运行结果如图:

3、添加节点

很多时候,我们还需要向xml里面添加节点,这个时候我们一样需要使用modify方法。下面我们就向id为0001的book节点中添加一个ISBN节点,代码如下:

–添加节点

set @xmlDoc.modify(‘insert <isbn>78-596-134</isbn> before (/books/book[@id=0001]/price)[1]’)

select @xmlDoc.query(‘(/books/book[@id=”0001″]/isbn)’)

运行结果如图:

4、添加和删除属性

当你学会对节点的操作以后,你会发现,很多时候,我们需要对节点进行操作。这个时候我们依然使用modify方法,例如,向id为0001的book节点中添加一个date属性,用来存储出版时间。代码如下:

–添加属性

set @xmlDoc.modify(‘insert attribute date{“2008-11-27”} into (/books/book[@id=0001])[1]’)

select @xmlDoc.query(‘(/books/book[@id=”0001″])’)

运行结果如图:

如果你想同时向一个节点添加多个属性,你可以使用一个属性的集合来实现,属性的集合可以写成:(attribute date{“2008-11-27”}, attribute year{“2008”}),你还可以添加更多。这里就不再举例了。

5、删除属性

删除一个属性,例如删除id为0001 的book节点的id属性,我们可以使用如下代码:

–删除属性

set @xmlDoc.modify(‘delete  books/book[@id=”0001″]/@id’)

select @xmlDoc.query(‘(/books/book)[1]’)

运行结果如图:

6、修改属性

修改属性值也是很常用的,例如把id为0001的book节点的id属性修改为0005,我们可以使用如下代码:

–修改属性

set @xmlDoc.modify(‘replace value of ( books/book[@id=”0001″]/@id)[1] with “0005”‘)

select @xmlDoc.query(‘(/books/book)[1]’)

运行结果如图:

经过上面的学习,相信你已经可以很好的在SQL中使用Xml类型了,下面是我们没 有提到的:exist()方法,用来判断指定的节点是否存在,返回值为true或false; nodes()方法,用来把一组由一个查询返回的节点转换成一个类似于结果集的表中的一组记录行。 你可以去MSDN查阅  http://msdn.microsoft.com/zh-cn/library/ms190798.aspx

[转载]Windows Phone Studio-任何人都能开发Windows Phone App的在线工具 - 施炯 - 博客园

mikel阅读(842)

[转载]Windows Phone Studio-任何人都能开发Windows Phone App的在线工具 – 施炯 – 博客园.

    在一段时间的内测以后,微软于今天早些时候发布了其Windows Phone应用开发的在线工具,名字叫做Windows Phone Studio。其意义在于,通过简单的内容添加和样式选择,实现Windows Phone应用的定制,它不仅屏蔽了Windows Phone应用开发的技术细节,使得没有相关开发经验的普通用户可以创建Windows Phone平台的App,而且可以完成应用原型的快速开发,方便有经验的程序员快速实现其应用程序的原型。

1. 注册Windows Phone Studio

目前,Windows Phone Studio的注册通过Live ID就可以通过,其平台网址是:http://apps.windowsstore.com/default.htm。其界面如下:

clip_image002

    通过点击Start Building,进入Live ID登陆页面。之后会有AppStudio需要访问个人信息的提示,点击“是”,就可以进入Windows Phone Studio的注册页面,如下图所示:

clip_image004

    在输入并确认相关的注册信息之后,就可以进行你的App定制了。

2. Windows Phone Studio功能简介

进入Windows Phone Studio的主页面之后,可以在页面上方看到4个Tab,分别是Create、Dashboard、How to和Send us your feedback。如下图所示。

clip_image006

  • Create:在我们想要新建一个应用时,点击Create,就可以进入应用程序创建的流程。
  • Dashboard:该页面展示了我们在Windows Phone Studio中所创建的应用程序,无论是出于编辑状态,还是处于生成状态。
  • How to:该页面给出了Windows Phone Studio的使用指南。
  • Send us your feedback:该页面和我们之前只用的user voice类似,可以给微软提供改进的建议和意见。

3. Create Your App

在点击Create之后,进入应用创建页面。应用创建分为两种:一种是创建新的App,不依赖于现有的模板;另一种是在现有模板的基础上创建。目前来看, 提供的模板还不多,只包含一些最基本的应用模型,包括企业、家庭、娱乐、音乐、运动、旅行、购物、阅读等等。Windows Phone Studio Team会在之后逐渐添加新的模板。我们从创建一个基于现有模板的应用(以Our Company为例)开始进行介绍。

3.1 App information

点击Create该模板之后,出现该应用的预览动态页面,如下图所示:

clip_image008

    点击其中的Create App按钮,出现第一个应用信息页面:App Information。在修改应用程序名称、加入应用简介、同事上传应用的Logo(按提示上传160*160像素的png图片)之后,点击界面上方的向右箭头,进入下一个步骤。

clip_image010

3.2 Configure App Content

    在该页面布局如下图所示:

clip_image012

    在Data Sources这一栏,展现了所有应用相关的数据源。数据源可以包含HTML、Collection、RSS和YouTuBe这4个种类。 Application Sections栏展示了应用相关的所有的页面。我们可以通过上方的“+”来添加新的Sections,每个Section可以包含多个页面。我们可以在 每个Section的编辑页面中进行添加。

3.2.1 编辑Section

通过点击Edit按钮,进入Section的编辑界面,如下图所示:

clip_image013

    进入编辑页面之后,我们可以进行数据源和具体页面的添加和删除。如下图所示:

clip_image015

    在每个具体的页面,我们可以对显示的内容样式进行配置,图下图所示:

clip_image017

    右边的预览窗口对所做的更改进行实时的效果显示,使得我们可以立刻看到效果。而不需要在应用生成以后进行更改。

4. Configure App Style

在应用配置界面,我们可以进行背景、磁贴、应用程序启动画面、锁屏画面等相关的设置,如下图所示:

clip_image019

    同样,我们可以在右边的预览界面看到实时的更改效果。

5. Summary

在该页面,统计应用的所有信息,如果要别人可见,可以选择Make App Public。如下图所示:

clip_image021

    在确认没有问题之后,可以点击Generate按钮,生成应用。生成应用的速度非常快,一般在几分钟就可以在注册的邮箱中收到邮件,通知应用已经生成,可以点击下载。

在下载的页面,我们可以选择下载其生成的源代码、生成的XAP包,甚至可以通过扫描二维码来直接安装。通过源代码,专业开发者可以进行修改,添加一些该平台无法完成的功能和特性。如下图所示。

clip_image023

    以上就是Windows Phone Studio的注册和使用,目前该平台还处于Beta阶段,Windows Phone Studio Team还在不断改进之中。目前该平台相关的资源链接如下:

Windows Phone App Studio beta: http://apps.windowsstore.com/default.htm

Recent blog posts on Windows Phone App Studio here:

http://blogs.windows.com/windows_phone/b/wpdev/

Windows Phone App Studio Forum:

http://social.msdn.microsoft.com/Forums/wpapps/en-US/home?forum=wpappstudio

User Voice: http://wpdev.uservoice.com/forums/216486

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]ORM对象缓存探讨 - robbin的自言自语

mikel阅读(1077)

[转载]ORM对象缓存探讨 – robbin的自言自语.

对象缓存和n+1问题分析

我们常见的OLTP类型的web应用,性能瓶颈往往是数据库查询,因为应用服务器层面可以水平扩展,但是数据库是单点的,很难水平扩展,当数据库服 务器发生磁盘IO,往往无法有效提高性能,因此如何有效降低数据库查询频率,减轻数据库磁盘IO压力,是web应用性能问题的根源。

对象缓存是所有缓存技术当中适用场景最广泛的,任何OLTP应用,即使实时性要求很高,你也可以使用对象缓存,而且好的ORM实现,对象缓存是完全透明的,完全不需要你的程序代码进行硬编码。

用不用对象缓存,怎么用对象缓存,不是一个简单的代码调优技巧,而是整个应用的架构问题。在你开发一个应用之前,你就要想清楚,这个应用最终的场景是什么?会有多大的用户量和数据量。你将采用什么方式来架构这个应用:

也许你偏好对SQL语句级别的优化,数据库设计当大表有很多冗余字段,会尽量消除大表之间的关联关系,当数据量很大以后,选择分库分表的优化方式, 这是目前业界常规做法。但是也可以选择使用ORM的对象缓存优化方式:数据库设计避免出现大表,比较多的表关联关系,通过ORM以对象化方式操作,利用对 象缓存提升性能。举个例子:

论坛的列表页面,需要显示topic的分页列表,topic作者的名字,topic最后回复帖子的作者,常规做法:

select ... from topic left join user left join post .....  

你需要通过join user表来取得topic作者的名字,然后你还需要join post表取得最后回复的帖子,post再join user表取得最后回贴作者名字。也许你说,我可以设计表冗余,在topic里面增加username,在post里面增加username,所以通过大 表冗余字段,消除了复杂的表关联:

select ... from topic left join post... 

OK,且不说冗余字段的维护问题,现在仍然是两张大表的关联查询。然后让我们看看ORM怎么做?

select * from topic where ... --分页条件

就这么一条SQL搞定,比上面的关联查询对数据库的压力小多了。 也许你说,不对阿,作者信息呢?回贴作者信息呢?这些难道不会发送SQL吗?如果发送SQL,这不就是臭名昭著的n+1条问题吗? 你说的对,最坏情况下,会有很多条SQL:

select * from user where id = topic_id...;
....
select * from user where id = topic_id...;

select * from post where id = last_topic_id...;
....
select * from post where id = last_topic_id...;

select * from user where id = post_id...;
....
select * from user where id = post_id...;

事实上何止n+1,根本就是3n+1条SQL了。那你怎么还说ORM性能高呢? 因为对象缓存在起作用,你可以观察到后面的3n条SQL语句全部都是基于主键的单表查询,这3n条语句在理想状况下(比较繁忙的web网站的热点数据), 全部都可以命中缓存。所以事实上只有一条SQL,就是:

select * from topic where ...--分页条件 

这条单表的条件查询和直接使用join查询SQL通过字段冗余简化过后的大表关联查询相比,当数据量大到一定程度以后对数据库磁盘IO的压力很小,这就是对象缓存的真正威力!

更进一步分析,使用ORM,我们不考虑缓存的情况,那么就是3n+1条SQL。但是这3n+1条SQL的执行速度一定比SQL的大表关联查询慢吗? 不一定!因为使用ORM的情况下,第一条SQL是单表的条件查询,在有索引的情况下,速度很快,后面的3n条SQL都是单表的主键查询,在繁忙的数据库系 统当中,3n条SQL几乎可以全部命中数据库的data buffer。但是使用SQL的大表关联查询,很可能会造成大范围的表扫描,造成频繁的数据库服务器磁盘IO,性能有可能是非常差的。

因此,即使不使用对象缓存,ORM的n+1条SQL性能仍然很有可能超过SQL的大表关联查询,而且对数据库磁盘IO造成的压力要小很多。这个结论貌似令人难以置信,但经过我的实践证明,就是事实。前提是数据量和访问量都要比较大,否则看不出来这种效果。

对象缓存的命中率

应用场景

是OLTP还是OLAP应用,即使是OLTP,也要看访问的频度,一个极少被访问到的缓存等于没有什么效果。一般来说,互联网网站是非常适合缓存应用的场景。

缓存的粒度

毫无疑问,缓存的粒度越小,命中率就越高,对象缓存是目前缓存粒度最小的,因此被命中的几率更高。举个例子来说吧:你访问当前这个页面,浏览帖子, 那么对于ORM来说,需要发送n条SQL,取各自帖子user的对象。很显然,如果这个user在其他帖子里面也跟贴了,那么在访问那个帖子的时候,就可 以直接从缓存里面取这个user对象了。

架构的设计

架构的设计对于缓存命中率也有至关重要的影响。例如你应该如何去尽量避免缓存失效的问题,如何尽量提供频繁访问数据的缓存问题,这些都是考验架构师 水平的地方。再举个例子来说,对于论坛,需要记录每个topic的浏览次数,所以每次有人访问这个topic,那么topic表就要update一次,这 意味着什么呢?对于topic的对象缓存是无效的,每次访问都要更新缓存。那么可以想一些办法,例如增加一个中间变量记录点击次数,每累计一定的点击,才 更新一次数据库,从而减低缓存失效的频率。

缓存的容量和缓存的有效期

缓存太小,造成频繁的LRU,也会降低命中率,缓存的有效期太短也会造成缓存命中率下降。

所以缓存命中率问题不能一概而论,一定说命中率很低或者命中率很高。但是如果你对于缓存的掌握很精通,有意识的去调整应用的架构,去分解缓存的粒度,总是会带来很高的命中率的。

[转载]ASP.NET网站优化(转自一位博友的文章,写的非常好) - 一个北漂的女孩 - 博客园

mikel阅读(1346)

[转载]ASP.NET网站优化(转自一位博友的文章,写的非常好) – 一个北漂的女孩 – 博客园.

本文将介绍一些方法用于优化ASP.NET网站性能,这些方法都是不需要修改程序代码的。 它们主要分为二个方面: 1. 利用ASP.NET自身的扩展性进行优化。 2. 优化IIS设置。

配置OutputCache

用 缓存来优化网站性能的方法,估计是无人不知的。 ASP.NET提供了HttpRuntime.Cache对象来缓存数据,也提供了OutputCache指令来缓存整个页面输出。 虽然OutputCache指令使用起来更方便,也有非常好的效果, 不过,它需要我们在那些页面中添加这样一个指令。

对 于设置过OutputCache的页面来说,浏览器在收到这类页面的响应后,会将页面响应内容缓存起来。 只要在指定的缓存时间之内,且用户没有强制刷新的操作,那么就根本不会再次请求服务端, 而对于来自其它的浏览器发起的请求,如果缓存页已生成,那么就可以直接从缓存中响应请求,加快响应速度。 因此,OutputCache指令对于性能优化来说,是很有意义的(除非所有页面页面都在频繁更新)。

在网站的优化阶段,我们可以用Fiddler之类的工具找出一些内容几乎不会改变的页面,给它们设置OutputCache, 但是,按照传统的开发流程,我们需要针对每个页面文件执行以下操作: 1. 签出页面文件。 2. 添加OutputCache指令。 3. 重新发布页面。 4. 签入文件(如果遇到多分支并行,还可能需要合并操作)。 以上这些源代码管理制度会让一个简单的事情复杂化,那么,有没一种更简单的方法能解决这个问题呢?

接下来,本文将介绍一种方法,它利用ASP.NET自身的扩展性,以配置文件的方式为页面设置OutputCache参数。 配置文件其它就是一个XML文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<OutputCache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Settings>
        <Setting Duration="3" FilePath="/Pages/a3.aspx"  />
        <Setting Duration="10" FilePath="/Pages/a5.aspx"  />
    </Settings>
</OutputCache>

看了这段配置,我想您应该也能猜到它能有什么作用。

每一行配置参数为一个页面指定OutputCache所需要的参数, 示例文件为了简单只使用二个参数,其它可以支持的参数请参考OutputCache指令

为了能让这个配置文件有效,需要在web.config中配置以下内容(适用于IIS7):

<system.webServer>
    <modules>
        <add name="SetOutputCacheModule" type="WebSiteOptimize.SetOutputCacheModule, WebSiteOptimize" />
    </modules>
</system.webServer>

在这里,我注册了一个HttpModule,它的全部代码如下:

public class SetOutputCacheModule : IHttpModule
{
    static SetOutputCacheModule()
    {
        // 加载配置文件
        string xmlFilePath = Path.Combine(HttpRuntime.AppDomainAppPath, "OutputCache.config");
        ConfigManager.LoadConfig(xmlFilePath);
    }

    public void Init(HttpApplication app)
    {
        app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute);
    }

    void app_PreRequestHandlerExecute(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;

        Dictionary<string, OutputCacheSetting> settings = ConfigManager.Settings;
        if( settings == null )
            throw new ConfigurationErrorsException("SetOutputCacheModule加载配置文件失败。");

        // 实现方法:
        // 查找配置参数,如果找到匹配的请求,就设置OutputCache
        OutputCacheSetting setting = null;
        if( settings.TryGetValue(app.Request.FilePath, out setting) ) {
            setting.SetResponseCache(app.Context);
        }
    }

ConfigManager类用于读取配置文件,并启用了文件依赖技术,当配置文件更新后,程序会自动重新加载: 

internal static class ConfigManager
{
    private static readonly string CacheKey = Guid.NewGuid().ToString();

    private static Exception s_loadConfigException;
    private static Dictionary<string, OutputCacheSetting> s_settings;

    public static Dictionary<string, OutputCacheSetting> Settings
    {
        get{
            Exception exceptin = s_loadConfigException;
            if( exceptin != null )
                throw exceptin;

            return s_settings;
        }
    }

    public static void LoadConfig(string xmlFilePath)
    {
        Dictionary<string, OutputCacheSetting> dict = null;

        try {
            OutputCacheConfig config = XmlHelper.XmlDeserializeFromFile<OutputCacheConfig>(xmlFilePath, Encoding.UTF8);
            dict = config.Settings.ToDictionary(x => x.FilePath, StringComparer.OrdinalIgnoreCase);
        }
        catch( Exception ex ) {
            s_loadConfigException = new System.Configuration.ConfigurationException(
                "初始化SetOutputCacheModule时发生异常,请检查" + xmlFilePath + "文件是否配置正确。", ex);
        }

        if( dict != null ) {
            // 注册缓存移除通知,以便在用户修改了配置文件后自动重新加载。

            // 参考:细说 ASP.NET Cache 及其高级用法
            //          http://www.cnblogs.com/fish-li/archive/2011/12/27/2304063.html
            CacheDependency dep = new CacheDependency(xmlFilePath);
            HttpRuntime.Cache.Insert(CacheKey, xmlFilePath, dep,
                Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, CacheRemovedCallback);
        }

        s_settings = dict;
    }

    private static void CacheRemovedCallback(string key, object value, CacheItemRemovedReason reason)
    {
        string xmlFilePath = (string)value;

        // 由于事件发生时,文件可能还没有完全关闭,所以只好让程序稍等。
        System.Threading.Thread.Sleep(3000);

        // 重新加载配置文件
        LoadConfig(xmlFilePath);
    }
}

有了AutoSetOutputCacheModule,我们就可以直接使用配置文件为页面设置OutputCache参数,而不需要修改任何页面,是不是很容易使用?

说明:MyMVC框架已支持这种功能,所有相关的可以从MyMVC框架的源码中获取。

建议:对于一些很少改变的页面,缓存页是一种很有效的优化方法。

启用内容过期

每 个网站都会有一些资源文件(图片,JS,CSS),这些文件相对于ASPX页面来说, 它们的输出内容极有可能在一段长时间之内不会有变化, 而IIS在响应这类资源文件时不会生成Cache-Control响应头。 在这种情况下,浏览器或许会缓存它们,也许会再次发起请求(比如重启后),总之就是缓存行为不受控制且缓存时间不够长久。

有没有想过可以把它们在浏览器中长久缓存起来呢?

为了告诉浏览器将这些文件长久缓存起来,减少一些无意义的请求(提高页面呈现速度),我们可以在IIS中启用内容过期, 这样设置后,IIS就能生成Cache-Control响应头,明确告诉浏览器将文件缓存多久。

在IIS6中,这个参数很好找到:

然而,在IIS7中,这个参数不容易被发现,需要以下操作才能找到: 选择网站(或者网站子目录)节点,双击【HTTP响应标头】

再点击右边的【设置常用标头】链接,

此时将会显示:

说明:【启用内容过期】这个设置可以基于整个网站,也可以针对子目录,或者一个具体的文件。

注意:如果您在IIS7中针对某个子目录或者文件设置【启用内容过期】,前面的对话框看起来是一模一样的, 然而,在IIS6中,我们可以清楚地从对话框的标题栏中知道我们在做什么:

有时真感觉IIS7的界面在退步!

最后我想说一句:可以直接为整个网站启用内容过期,ASPX页面是不会被缓存的!

说 到这里可能有人会想:这个过期时间我该设置多久呢? 十分钟,2个小时,一天,还是一个月? 在我看来,这个时间越久越好。 可能有人又会说了:万一我要升级某个JS文件怎么办,时间设置久了,用户怎么更新呢? 如果你问我这个问题,我也只能说是你的代码不合理(毕竟你解决不了升级问题),想知道原因的话,请继续阅读。

解决资源文件升级问题

对于一些规模不大的网站来说,通常会将资源文件与程序文件一起部署到一个网站中。 这时可能会采用下面的方式来引用JS或者CSS文件:

<link type="text/css" href="aaaa.css" rel="Stylesheet" />
<script type="text/javascript" src="bbb.js"></script>

在这种情况下,如果使用了前面所说的【启用内容过期】方法,那么当有JS,CSS文件需要升级时, 由于浏览器的缓存还没有过期,所以就不会请求服务器,此时会使用已缓存的版本, 因此可能会出现各种奇怪的BUG

对 于前面谈到的BUG,我认为根源在于引用JS,CSS文件的方式有缺陷, 那种方法完全没有考虑到版本升级问题, 正确的方法有二种: 1. 给文件名添加版本号,像JQuery那样,每个版本一个文件(JQuery-1.4.4.min.js)。 2. 在URL后面添加一个版本号,让原先的URL失效。

第一种方法由于每次升级都产生了一个新文件,所以不存在缓存问题,但是,维护一大堆文件的成本可能会比较大, 因此我建议采用第二种方法来解决。

MyMVC的示例代码中,我使用了下面的方法来引用这些资源文件:

<%= HtmlExtension.RefCssFileHtml("/css/StyleSheet.css")%>
<%= HtmlExtension.RefJsFileHtml("/js/MyPage/fish.js")%>

在页面运行时,会产生如下的输出结果:

<link type="text/css" rel="Stylesheet" href="/css/StyleSheet.css?_t=634642185820000000" />
<script type="text/javascript" src="/js/MyPage/fish.js?_t=634642154020000000"></script>

这二个工具方法的实现代码如下(在MyMVC的示例代码中): 

private static readonly string s_root = HttpRuntime.AppDomainAppPath.TrimEnd('\\');

public static string RefJsFileHtml(string path)
{
    string filePath = s_root + path.Replace("/", "\\");
    string version = File.GetLastWriteTimeUtc(filePath).Ticks.ToString();
    return string.Format("<script type=\"text/javascript\" src=\"{0}?_t={1}\"></script>\r\n", path, version);
}

public static string RefCssFileHtml(string path)
{
    string filePath = s_root + path.Replace("/", "\\");
    string version = File.GetLastWriteTimeUtc(filePath).Ticks.ToString();
    return string.Format("<link type=\"text/css\" rel=\"Stylesheet\" href=\"{0}?_t={1}\" />\r\n", path, version);
}

上 面这种获取文件版本号的方法,是一种比较简单的解决方案。 每个引用的地方在生成HTML代码时,都会访问文件的最后修改时间,这会给磁盘带来一点读的开销, 如果您担心这种实现方式可能会给性能带来影响,那么也可以增加一个配置文件的方式来解决(请自行实现), 例如以下结构:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfFileVersion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <FileVersion FilePath="/js/JScript.js" Version="255324" />
    <FileVersion FilePath="/css/StyleSheet.css" Version="2324235" />
</ArrayOfFileVersion>

如 果您认为这种配置文件需要手工维护,不够自动化,还可以采用程序的方式自动在运行时维护一个列表, 总之,直接引用资源文件的方法是一种直接耦合,会给文件升级带来麻烦, 我们可以通过一个外部方法来解开这个直接耦合(给FileVersion增加一个属性还还可以将内部地址改成一个CDN地址)。

启用压缩

压缩响应结果也是常用的网站优化方法,由于现在的浏览器都已支持压缩功能, 因此,如果在服务端能压缩响应结果,对于网速较慢的用户来说,会减少很多网络传输时间,最终的体验就是网页显示速度变快了!

IIS6虽然提供压缩的设置界面,然而配置是基于服务器级别的:

注意:这里的【应用程序文件】不包括aspx,如果需要压缩aspx的响应, 需要手工修改x:\WINDOWS\system32\inetsrv\MetaBase.xml文件(参考加大字号部分): 

<IIsCompressionScheme    Location ="/LM/W3SVC/Filters/Compression/gzip"
        HcCompressionDll="%windir%\system32\inetsrv\gzip.dll"
        HcCreateFlags="1"
        HcDoDynamicCompression="TRUE"
        HcDoOnDemandCompression="TRUE"
        HcDoStaticCompression="TRUE"
        HcDynamicCompressionLevel="9"
        HcFileExtensions="htm
            html
            txt
            js
            css
            htc"
        HcOnDemandCompLevel="10"
        HcPriority="1"
        HcScriptFileExtensions="asp
            exe
            aspx
            axd"
    >

说明:要修改MetaBase.xml,需要停止IIS Admin Service服务。

在IIS7中,我们可以在服务器级别配置压缩参数:

然后在每个网站中开启或者关闭压缩功能:

说 明:IIS7中已经不再使用MetaBase.xml,所以我们找不到IIS6的那些设置了。 IIS7压缩的过滤条件不再针对扩展名,而是采用了mimeType规则(保存在applicationHost.config)。 根据IIS7的压缩规则,当我们启用动态压缩后,会压缩aspx的响应结果。

二种压缩方法的差别: 1. 静态内容压缩:当服务器在第一次响应某个静态文件时,会生成一个压缩后的结果,并保存到磁盘中,以便重用。 2. 动态内容压缩:【每次】在响应客户端之前,压缩响应结果,在内存中完成,因此会给CPU带来一些负担。

注意:要不要【启用动态内容压缩】这个参数,需要评估服务器的CPU是否能以承受(观察任务管理器或者查看性能计数器)。

删除无用的HttpModule

对一个网站来说,ASP.NET提供的有些HttpMoudle可能并不是需要的, 然而,如果你不去手工禁用它们,它们其实会一直运行。 比如  会禁用下面这些HttpMoudle:

<httpModules>
    <remove name="Session"/>
    <remove name="RoleManager"/>
    <remove name="PassportAuthentication"/>
    <remove name="Profile"/>
    <remove name="ServiceModel"/>
</httpModules>

对于使用Forms身份认证的网站的来说,下面这些HttpModule也是可以禁用的:

<httpModules>
    <remove name="WindowsAuthentication"/>
    <remove name="FileAuthorization"/>
</httpModules>

其它优化选项

优 化ASP.NET网站是一个大的话题,除了博客中介绍的这些方法之外,还有以下方法也是可以参考的: 1. 升级服务器硬件配置。 2. 使用Windows Server 2008以上版本操作系统(网络性能比2003要好)。 3. 优化操作系统配置(例如禁用不需要的服务)。 4. 禁用调试模式。 5. 网站使用专用应用程序池。

[转载]ASP.NET Mvc利用淘宝Kissy uploader实现图片批量上传附带瀑布流的照片墙 - 狼性法则 - 博客园

mikel阅读(988)

[转载]Mvc利用淘宝Kissy uploader实现图片批量上传附带瀑布流的照片墙 – 狼性法则 – 博客园.

前言

KISSY  是由阿里集团前端工程师们发起创建的一个开源 JS 框架。它具备模块化、高扩展性、组件齐全,接口一致、自主开发、适合多种应用场景等特性。本人在一次项目中层使用这个uploader组件。

关于kissy uploader:

Uploader是非常强大的异步文件上传组件,支持ajax、iframe、flash三套方案,实现浏览器的全兼容,调用非常简单,内置多套主题支持和常用插件,比如验证、图片预览、进度条等。

广泛应用于淘宝网,比如退款系统、爱逛街、二手、拍卖、我的淘宝、卖家中心、导购中心等。

拥有非常不错的扩展性,可以自己定制主题和插件。

  uploader的特性:

  • 支持ajax、flash、iframe三种方案,兼容所有浏览器。(iframe不推荐使用)
  • 多主题支持,可以自己定制主题
  • 支持多选批量上传
  • 支持上传进度显示
  • 支持取消上传
  • 支持图片预览(使用flash上传不支持)
  • 支持上传验证
  • 多种配置方式

Kissy uploader配置简单而且使用方便,官网提供许多主题稍加修改便可用于项目当中,本文的案例采用的是kissy uploader的在线js库。更多的资料大家可以去官网:http://gallery.kissyui.com/uploader/1.4/guide/index.html#demo汇总查找相关资料,讲述的很全面。

案例截图

相关配置

1、本文照片的相关信息我采用EF coder first将其保存在数据库中了,相关代码

实体类:

复制代码
[Table("Photos")]
    public class Photos
    {
        [Key]
        public int ID { get; set; }
        public string Name { get; set; }
        public string Desc { get; set; }
        public string Src { get; set; }
        public DateTime? PubliseTime { get; set; }
    }
复制代码

数据库上下文:

复制代码
public class PhotoDB:DbContext
    {
        public PhotoDB()
            : base("name=PhotoDB")
        {

        }
        public DbSet<Photos> Photos { get; set; }
    }
复制代码

连接字符串:

<connectionStrings>
    <add name="PhotoDB" connectionString="server=(local);Initial Catalog=Photo;Integrated Security=SSPI" providerName="System.Data.SqlClient" />
  </connectionStrings>

2、上传图片配置(相关配置说明大家可以参考官网示例)

上传页面index:

复制代码
<!doctype html>
<html>
<head>
    <meta charset="utf-8"/>
    <script src="http://a.tbcdn.cn/s/kissy/1.3.0/kissy-min.js" charset="utf-8"></script>
    <style type="text/css">
        .textvalue{padding: 0;width: 118px;border-top: 1px solid #E2E2E2;height: 25px;}
        .sub{height:30px; width:120px;position:relative;top:30px;}
    </style>
</head>
<body>
@using (Html.BeginForm())
{
    <div class="grid">
        <input type="file" class="g-u" id="J_UploaderBtn" value="上传图片" name="Filedata" />
        <input type="hidden" id="J_Urls" name="urls" value="" />
        <div class="g-u">还可以上传<span id="J_UploadCount">14</span>张图片</div>
    </div>
    <ul id="J_UploaderQueue" class="grid">
        <script type="text/uploader-theme">
            <li id="queue-file-{id}" class="g-u" data-name="{name}" style="height: 140px !important"> 
            <div class="pic"> 
                <a href="javascript:void(0);"><img class="J_Pic_{id} preview-img" src="" /></a> 
                </div> 
            <div class=" J_Mask_{id} pic-mask"></div> 
            <div class="status-wrapper"> 
                <div class="status waiting-status"><p>等待上传,请稍候</p></div> 
                <div class="status start-status progress-status success-status"> 
                    <div class="J_ProgressBar_{id}"><s class="loading-icon"></s>上传中...</div> 
                    </div> 
                <div class="status error-status"> 
                    <p class="J_ErrorMsg_{id}">上传失败,请重试!</p>
                </div>
            </div>
            <a class="J_Del_{id} del-pic" href="#">删除</a>
            <input type="text" value="" name="desc" id="desc" class="textvalue" placeholder="描述">
            </li>
        </script>
    </ul>
    <input type="submit" value="保存" class="sub" />
}
<script type="text/javascript">
    var S = KISSY;
    if (S.Config.debug) {
        var srcPath = "../../../../";
        S.config({
            packages: [
                {
                    name: "gallery",
                    path: srcPath,
                    charset: "utf-8"
                }
            ]
        });
    }

    var $ = S.Node.all;

    S.use('gallery/uploader/1.4/index,gallery/uploader/1.4/themes/imageUploader/index,gallery/uploader/1.4/themes/imageUploader/style.css', function (S, Uploader, ImageUploader) {
        //上传插件
        var plugins = 'gallery/uploader/1.4/plugins/auth/auth,' +
                'gallery/uploader/1.4/plugins/urlsInput/urlsInput,' +
                'gallery/uploader/1.4/plugins/proBars/proBars,' +
                'gallery/uploader/1.4/plugins/filedrop/filedrop,' +
                'gallery/uploader/1.4/plugins/preview/preview';

        S.use(plugins, function (S, Auth, UrlsInput, ProBars, Filedrop, Preview) {
            var uploader = new Uploader('#J_UploaderBtn', {
                //处理上传的服务器端脚本路径
                action: "/Home/PictureUpload",
                multiple: true
            });
            //上传成功后显示图片描述
            uploader.on('success', function (ev) {
                var result = ev.file.result;
                if (result.desc) {
                    var $desc = $('.J_Desc_' + ev.file.id);
                    $desc.html(result.desc);
                }
            })

            //使用主题
            uploader.theme(new ImageUploader({
                queueTarget: '#J_UploaderQueue'
            }))
            //验证插件
                    .plug(new Auth({
                        //最多上传个数
                        max: 14,
                        //图片最大允许大小
                        maxSize: 2000
                    }))
            //url保存插件
                    .plug(new UrlsInput({ target: '#J_Urls' }))
            //进度条集合
                    .plug(new ProBars())
            //拖拽上传
                    .plug(new Filedrop())
            //图片预览
                    .plug(new Preview())
            ;
            //渲染默认数据
            uploader.restore();
        });
    })

</script>
</body>
</html>
复制代码

后台临时保存方法:

复制代码
//图片上传处理
        public ActionResult PictureUpload()
        {
            //保存到临时文件
            HttpPostedFileBase postedfile = Request.Files["Filedata"];
            var filename = postedfile.FileName;
            var newname =Guid.NewGuid()+filename.Substring(filename.LastIndexOf('.'));
            var filepath = Server.MapPath("/UpLoad/temp/") + newname;
            Image image = Image.FromStream(postedfile.InputStream, true);
            image.Save(filepath);//保存为图片
            return Json(new { status = 1, url = "/UpLoad/temp/" + newname });
        }
复制代码

表单提交保存数据相关方法:

复制代码
    [HttpPost]
        public ActionResult Index(string urls, FormCollection fc)
        {
            var urlArray = urls.Split(',');
            var desArray = fc["desc"].Split(',');
            for (int i = 0; i <desArray.Length; i++)
            {
                //保存为正式文件
                var filename = urlArray[i].Substring(urlArray[i].LastIndexOf('/') + 1);
                var oldfile = Server.MapPath(urlArray[i]);
                var newfile = Server.MapPath("/UpLoad/photo/")+filename;
                System.IO.File.Move(oldfile, newfile);
                db.Photos.Add(
                        new Photos { Name = filename, Desc = desArray[i], Src = "/UpLoad/photo/"+filename, PubliseTime = DateTime.Now }
                    );
            }
            db.SaveChanges();
                return View();
        }
复制代码

3、瀑布流照片墙

后台返回所有照片实体类传递给视图;

    //照片墙
        public ActionResult Photo()
        {
            var photos = db.Photos.ToList();
            return View(photos);
        }

前台动态加载图片js及照片墙页面:

复制代码
@model List<MvcApplication3.Models.Photos>
@{Layout = null;}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>jQuery实现的瀑布流布局的图片特效代码</title>
<link href="../../Content/lanrenzhijia.css" rel="stylesheet" type="text/css" />
<script src="../../Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<script src="../../Scripts/blocksit.min.js" type="text/javascript"></script>
<script>
    $(document).ready(function () {
        //vendor script
        $('#header')
    .css({ 'top': -50 })
    .delay(1000)
    .animate({ 'top': 0 }, 800);

        $('#footer')
    .css({ 'bottom': -15 })
    .delay(1000)
    .animate({ 'bottom': 0 }, 800);

        //blocksit define
        $(window).load(function () {
            $('#container').BlocksIt({
                numOfCol: 5,
                offsetX: 8,
                offsetY: 8
            });
        });

        //window resize
        var currentWidth = 1100;
        $(window).resize(function () {
            var winWidth = $(window).width();
            var conWidth;
            if (winWidth < 660) {
                conWidth = 440;
                col = 2
            } else if (winWidth < 880) {
                conWidth = 660;
                col = 3
            } else if (winWidth < 1100) {
                conWidth = 880;
                col = 4;
            } else {
                conWidth = 1100;
                col = 5;
            }

            if (conWidth != currentWidth) {
                currentWidth = conWidth;
                $('#container').width(conWidth);
                $('#container').BlocksIt({
                    numOfCol: col,
                    offsetX: 8,
                    offsetY: 8
                });
            }
        });
    });
</script>
</head>
<body>
<!-- Content -->
<section id="wrapper">
  <div id="container">
  @foreach (var item in Model)
  {
    <div class="grid">
      <div class="imgholder"> <img src="@item.Src" /> </div>
      <strong>@item.Desc</strong>
      <p>上 传 时 间:</p>
      <div class="meta">@item.PubliseTime.ToString()</div>
    </div>
  }
    <!---->
  </div>
</section>
</body>
</html>
复制代码

瀑布流采用国外的一个JQuery插件实现。

结语

  本人强烈推荐采用kissy uploader上传插件,原因在于:采用的上传方案,当值是数组时,比如 “type” : [“flash”,”ajax”,”iframe”],按顺序获取浏览器支持的方式,该配置会优先使用flash上传方式,如果浏览器不支持flash, 会降级为ajax,如果还不支持ajax,会降级为iframe;当值是字符串时,比如“type” : “ajax”,表示只使用ajax上传方式。这种方式比较极端,在不支持ajax上传方式的浏览器会不可用;当“type” : “auto”,auto是一种特例,等价于[“ajax”,”iframe”]。这一点很重要,由于以前一个项目采用flash上传,当遇到移动设备时发现不支持上传了,采用该插件则不会出现此问题。

[转载]SQL Server 2008性能监视_.NET教程网——简单专业的.NET技术网站

mikel阅读(889)

[转载]SQL Server 2008性能监视_.NET教程网——简单专业的.NET技术网站.

性能监视

SQL Server 2008监视本质上可以分为5个区域:系统资源;SQL Server自身;数据库;数据库应用程序和网络。

监视和优化SQL 的策略是相当简单的,分为以下几步:创建一个性能基准;完成定期性能审核;作出改动并评估他们的影响;重设基准

那么基准中应该都包含些什么呢?下面是一个基准和性能审核对象和进程的列表:

计数器:计数器包括Processor:%Processor Timer计数器,它显示了处理非限制线程所用时间的总百分比;Process:%Processor Time计数器用来确定总处理时间中有多少由SQL Server占用;System:Processor Queue Length计数器显示等待由CPU处理的线程的数量。

磁盘计数器:磁盘计数器可以返回每个物理磁盘或所有磁盘的磁盘读写性能信息和数据传输信息。PhysicalDis:Avg.Disk Queue Length计数器返回等待访问一个独立磁盘或所有磁盘的读写操作的平均数量;PhysicalDisk:%Disk Time计数器测量一个物理磁盘或硬件磁盘阵列的繁忙程度。

内存计数器:Memory:Pages/Sec计数器测量每秒从内存中分页到磁盘或从磁盘分页到内存的页数;Memory:Available Bytes计数器指示有多少内存可供进程使用;Process:Working Set(sqlservr)计数器显示了SQL Server使用了多少内存;SQL Server:Buffer Manager:Buffer Cache Hit Ratio计数器测量在缓冲区中找到数据,而不用再去读磁盘的时间百分比;SQL Server:Buffer Manager:Page Life Expectancy计数器返回一个单位为秒的时间值,显示数据页在缓冲区中停留而不会被数据操作引用的时间。

网络计数器:Network Interface:Bytes Total/Sec计数器测量在服务器和网络之间来回发送的字节总数

SQL Server计数器:SQL Server:General Statistics:User Connections计数器显示了当前连接到SQL Server的用户连接数;SQL Server:Locks:Average Wait Time计数器是一个很好的计数器,用来监视和跟踪由于并发数据阻塞造成的用户对于数据资源的请求需要等待的平均时间;SQL Server:Locks:Deadlock/Sec计数器是描述关于死锁的相关指标;SQL Server Access Methods:Page Splits/Sec计数器通过对页拆分活动进行监视来确定表索引的所片化速度。

动态管理视图:SQL Server 2008提供了很多动态管理视图,可以用来搜集基准信息和诊断性能问题。如sys.dm_os_performance_counters, sys.dm_index_physical_stats, sys.dm_db_index_usage_Stats等

用于监视的工具和技术

日志文件查看器:可在SQL Server Management Studio中展开管理文件夹,然后展开SQL Server日志文件夹,右击想查看的日志,然后选择查看SQL Server日志命令。每次重启SQL Server和SQL Server代理时,会关闭各自的日志文件并打开一个新的日志,在一个生产系统中,这种情况可能不会经常发生,于是就会形成一个较大的日志文件。想要避免 出现过于庞大的日志文件,必须到处日志文件的内容,然后再循环使用该文件。要想循环使用SQL Server日志,可以执行sp_cycle_errorlog存储过程。要想循环使用代理日志,可以使用 sp_cycle_agent_errorlog存储过程。这些过程可以清除日志的内容而不用重启服务。

活动监视器:活动监视器是一个可以帮助对服务器的整体运行状况和性能有更深入理解的优秀工具。它显示直观的图形、详细的进程和锁定信息、文件 IO统计信息和长时间运行的查询的信息。另外,可以对所有的网格视图进行配虚和筛选。要运行活动监视器,需要具有view server state权限,要终止任何进程,还需要是sysadmin或processadmin服务器角色的成员。活动监视器主要有5个部分构成:概述、进程、资 源等待、数据文件IO和最近耗费大量资源的查询。

系统存储过程:虽然就查看进程以及他们使用的资源而言,活动监视器是一个很好的图形工具,但通常来说,系统存储过程的输出更简单,更适合用来识 别当前进程以及任意资源竞争。sp_who2存储过程是一个未归档的系统过程,和已归档的同类存储过程sp_who相比,它有显著的优势。他们都返回当前 SQL Server进程的信息,但是前者返回的信息更加全面。sp_lock存储过程返回活动数据库进程所持有的锁的数目和类型。还有KILL命令,它虽然不是 一个存储过程,但是它使得数据库管理员可以终止一个违反规则的进程,其语法很简单:KILL spid。KILL命令很有用,但是要注意,虽然有时候有必须要终止一个终端的进程,但是在终止它之前搜集与它有关的尽可能多的信息还是很重要的。

现在打开一个查询窗口,执行如下代码:

USE AdventureWorks2008;
GO
BEGIN TRAN
UPDATE Person.Person SET LastName='Gates' WHERE BusinessEntityID=1

打开另外一个查询窗口,输入并执行下列代码

USE AdventureWorks2008;
GO
SELECT * FROM Person.Person WHERE BusinessEntityID=1;

在执行此语句时不会看到任何返回的结果,因为在第一个查询释放其锁定之前,此语句不会完成执行,现在打开第三个窗口,执行下列命令运行sp_who系统存储过程

EXEC sp_who

执行sp_who2存储过程,但将结果集限制为阻塞绘画的服务进程ID,这里是60

EXEC sp_who2 60

确定哪个对象在被两个进程竞争,执行sp_lock存储过程,输入并执行下列命令显示有关被阻塞的SPID的信息

EXEC sp_lock 59

要终止阻塞进程,需要执行KILL命令并制定合适的SPID,这里是60:KILL 60.

使用Profiler:Profiler指示一个查看SQL跟踪的图形化界面,它是一个很好的工具,但对于有着繁重事务负荷的大型数据库来说, 可能需要使用sp_trace_setevent,sp_trace_setfilter,sp_trace_setstatus和 sp_trace_create存储过程,通过文件中搜集的跟踪数据创建、配置和运行跟踪。然后可以使用Profiler直接通过搜集的文件查看这些数据 或者可以将它们导入到一个数据库中以供分析。但是使用Profiler时一个需要考虑到事项时开销,交互式运行Profiler会导致大量服务器开销,以 及教的的不确定因素。

打开SQL Server Management Studio,连接到驻留AdventureWorks2008数据库的服务器,在连接后通过工具菜单启动SQL Server Profiler,创建一个基于空白模板的新跟踪,在事件选择选项卡上,选择Deadlock graph和Lock:Deadlock Chain事件,如果选择了Deadlock graph事件,则会出现事件提取设置选项卡;要限制返回给Profiler的数据,单击列筛选按钮,然后选择DatabaseName,在不类似于文本 框中,输入MSDB以避免跟踪SQL代理和计划的监视活动,单击确定按钮。在事件提取设置选项卡上,选中分别保存死锁XML事件复选框,然后输入一个文质 来保存文件,选中不同文件中的每个死锁XML批单选按钮,然后单击运行按钮。在SQL Server Management Studio中,打开两个新的查询窗口,第一个查询窗口中输入如下代码并执行:

--Connection 1
USE AdventureWorks2008;
GO
BEGIN TRAN
UPDATE Person.Address SET City='Redmond' WHERE AddressID=1;

在第二个查询窗口中输入如下代码并运行:

--Connection 2
USE AdventureWorks2008;
GO
BEGIN TRAN
UPDATE Person.Person SET LastName='Gates' WHERE BusinessEntityID=1;
UPDATE Person.Address SET AddressLine1='1 Microsoft Way' WHERE AddressID=1;

此更新将不会完成,因为Connection1中的事务在想要更新的Person.Person表的行上有一个排他锁。在第一个连接上,编写并执行如下代码以更新Person.Person表

UPDATE Person.Person SET FirstName='Bill' WHERE BusinessEntityID=1

此更新会导致一个死锁发生,因为两个连接都建立在对立事务完成所需的资源上都持有排他锁。回到Profiler,停止跟踪,然后选择 Deadlock graph事件类行。死锁图形显示了被死锁的服务器进程ID和已锁定的资源,将鼠标指针悬停在一个进程上面会显示参与死锁的进程。要捕捉用来运行这个跟踪 的脚本,单击SQL Profiler中的文件菜单,然后选择到处-编写跟踪定义的脚本-用于SQL Server 2005-2008选项,这时候将会出现一个另存为对话框,存储为DeadlockTrace.SQL。

在捕捉到跟踪文件以后,可以使用Management Studio打开,而不用启动和运行Profiler。如果跟踪较大,可以把它插入表,以便使用传统的TSQL查询分析。要把数据移动至表中,可以使用 fn_trace_gettable表值函数。此表值函数需要两个值,需要导入的跟踪文件的名称和需要搜集的滚动更新文件的最大值。默认的文件数目是为跟 踪设置的最大文件数,下面的例子显示了如何将之前搜集的跟踪添加到AdventureWorks2008数据库的一个叫做 DeadLockTraceTable的表里面

USE AdventureWorks2008
GO
SELECT * INTO DeadLockTraceTable FROM fn_trace_gettable(‘C:\ProfilerTraces\DeadLocks.trc’,NULL);

使用Profile检测和分析长时间运行的查询:Profiler是一个很好的工具,可以用于分析所,以及调式存储过程和数据库应用程序。它也 可以用于检测和分析影响SQL Server性能的长时间运行的查询。Profiler可以返回查询执行信息,数据库管理员可以通过这些信息找出导致冗长查询的原因。要分析查询,需要遵 循下列步骤:启动Profiler,使用空白模板创建一个叫做QueryTuning的新跟踪,在时间选择选项卡上选择下列事 件:Performance:ShowPlan XML,Stored Procedures:SP:Completed,TSQL:SQL:BatchCompleted,单击列筛选器按钮创建一个筛选器,其中数据库名称类 似于AdventureWorks2008,然后单击确定按钮应用筛选器,单击组织列按钮,找到Duration列,将其移至列表的顶部,使得易于阅读持 久数据,在事件提取设置选项卡上,选中分别保存XML显示计划事件复选框。选择一个保存ShowPlan信息的位置,将文件命名为 QueryTuning,然后选择不同文件中的每个XML显示计划批选项。SQLPlan是给予ShowPlan数据的文件扩展,ShowPlan数据坤 出位XML格式,并且可以使用Management Studio查看,当将查询计划保存在单独的文件中时,每个文件都采用在目标位置中定义的名称,并且名称后面附加有数字标识符。单击运行按钮运行此跟踪, 接下来,在SQL Server Management Studio中打开一个iexin的查询窗口,输入并执行下列代码:

USE AdventureWorks 2008;
GO
SELECT P.ProductID,P.name AS Product, TH.TransactionDate, SUM(TH.Quantity),SUM(TH.ActualCost),SUM(P.StandardCost) FROM Production.Product P INNER JOIN Production.TransactionHistory TH ON P.ProductID=TH.ProductID GROUP BY P.ProductID, P.Name, TH.TransactionDate;
GO
EXEC dbo.uspGetManagerEmployees 109;
GO
EXEC dbo.uspGetEmployeeManagers 1;
GO
SELECT P.name AS Product, SUM(SOD.OrderQty) AS SumQty,SUM(SOD.UnitPrice) AS SumPrice, SUM(SOD.LineTotal) AS SumTotal, CONVERT(char(10),SOH.OrderDate,101) AS orderDate, CONVERT(char(10),SOH.ShipDate,101) AS ShipDate, CONVERT(char(10),SOH.DueDate,101) AS DueDate FROM Sales.SalesOrderDetail SOD INNER JOIN Sales.SalesOrderHeader SOH ON SOH.SalesOrderID=SOD.SalesOrderID INNER JOIN Production.Product P ON P.ProductID=SOD.ProductID GROUP BY P.Name, SOH.OrderDate, SOH.ShipDate, SOH.DueDate

完成查询以后,停止跟踪并检查结果,导航至ShowPlan目标文件夹并检查内容,应该看到4个文件,分别从 QueryTuning_1.SQLPlan到QueryTuning_4.SQLPlan进行命名。双击QueryTuning_4.SQLPlan文 件,这将启动SQL Server Management Studio,并把该文件作为一个图形化执行计划打开。

监视文件:数据库管理员较为普通的任务之一就是监视和管理文件的大小。一般来说,在数据库的设计和规划阶段已经确定了数据库的大小,这一决定中 应表示数据库文件的起始大小,以及每个文件类型的预计增长率。监视数据库文件大小的方法有很多种,SQL Server Management Studio中的磁盘使用情况的报表即是一种;sp_spaceused存储过程也可以用来返回一些和磁盘使用报表类似的信息,但是如果没有给他传递具体 对象名称参数,该存储过程将智慧返回整个数据库的信息空间。下列显示了如何运行sp_spaceused存储过程从AdventureWorks2008 数据库或其中的一个表中检索信息

SET NOCOUNT ON;
SUE AdventureWorks2008;
GO
SELECT 'AdventureWorks2008 Space Used Data'
EXEC sp_spaceused;

SELECT 'Person.Person Space Used Data'
EXEC sp_spaceused;

性能计数器可能是监视数据和日志文件中的空闲空间的最有效办法。SQL Server:Databases性能对象有几个可以用来监视磁盘和日志文件大小的计数器。可以使用这些计数器创建警报。这样SQL Server就能替您执行监视,并在数据文件超出预制的大小或事务日志的填充程度超出一定百分比时通知您。

审核

从根本上讲,审核就是监视和跟踪对系统的更改的过程。

SQL Server审核的创建包括以下过程:创建一个SQL Server审核包并定义输出目标;创建一个服务器审核规范和一个或多个数据库审核规范,他们定义了审核事件标准;启用审核规范;启用审核;查看那并分析不活动审核事件。

审核安全事件:要审核安全事件,需要遵守下列步骤,打开SQL Server Management Studio,连接至驻留AdventureWorks2008数据库的服务器。展开对象资源管理器中的安全性节点,右击审核文件夹并选择新建审核命令, 创建一个新的审核;右击服务器审核规范文件夹,选择新建服务器审核规范命令,在审核下拉列表中,选择上面创建的审核并添加下列操作 组:SERVER_PRINCIPAL_CHANGE_GROUP;SERVER_PRINCIPAL_IMPERSONATION_GROUP;LOGIN_CHANGE_PASSWORD_GROUP, 保存审核规范,然后右击它并选择启用服务器审核规范命令,审核规范上的图标将显示已被启用;展开AdventureWorks2008数据库,通过安全性 -数据库审核规范文件夹创建一个新的数据库审核规范,将这个规范映射至上面创建的审核报,然后添加下列操作组:SELECT:对象类Schema对象名称 Person主体名称public,DATABASE_OBJECT_PERMISSION_CHANGE_GROUP,保存并启用该审核规范,数据库审 核规范的图标应改为显示它已被启用;现在启用审核以便开始接收包含的事件。在启用了审核后,将下列代码输入到一个新的窗口并执行:

--Create a new server login
EXEC sp_addlogin 'Paul','Microsoft123','AdventureWorks2008';
GO

--Exclude this login from OS policy constraints
ALTER LOGIN Paul WITH CHECK_POLICY=OFF
GO

--change password
EXEC sp_password @old='Microsoft123',@new='Microsoft456',@loginname='Paul'
GO

--Allow this user to access AdventureWorks2008
USE AdventureWorks2008
GO
CREATE USER Paul FOR LOGIN Paul
GO

--Try to select as Paul, no permissions yet
EXECUTE AS LOGIN='Paul'
SELECT * FROM Person.Person WHERE BusinessEntityID=1;
REVERT
GO

--Assign permissions
GRANT SELECT ON Person.Person  TO Paul;
GO

--now the select should succeed
EXECUTE AS LOGIN='Paul'
SELECT * FROM Person.Person WHERE BusinessEntityID=1;
REVERT
GO

--Clean up
DROP USER Paul
GO
EXEC sp_droplogin 'Paul';
GO

最后,禁用该审核,然后右击审核并选择查看审核日志命令,在日志文件查看器中显示结果,在审核及锅中可以注意到,所有的目标事件都被捕获,包括失败的SELECT尝试,也可以使用新的fn_get_audit_file函数以表形式查看审核结果

SELECT * FROM fn_get_audit_file('C:\SQLAudit、*'defaultdefault)

审核登录:登录审核仅记录成功的登录尝试、失败的的登录尝试或者两者都记录。对登录审核的支持内置于SQL Server中,可通过SQL Server Management Studio的服务器属性对话框的安全性启用页,在更改了登录审核级别后,只有重启服务器实例,新的设置才会生效。登录成功和失败事件都会被写入 WIndows 应用程序日志以及SQL Server 的日志中。

C2审核模式:C2审核模式是由美国国防部制定的一组应用于指定计算机系统中的安全级别的等级。C2审核所设计的不仅仅是服务器事件,而是扩展 至包含访问所有语句和对象的成功和失败的尝试。如果您想标识出可能存在的违反安全的行为,这是很有用的。但是它会耗费大量的空间和性能。要启用C2审核模 式,右击SSMS的对象资源管理器中的服务器实例,选择属性命令,然后选择安全性页,在选择启用C2审核跟踪设置。要禁用C2审核跟踪,取消选择即可。设 置后,必须重启服务器实例才能生效,也可以使用TSQL启用C2审核跟踪

--Enable C2 audit mode
sp_configure ‘show advanced options’,1
GO
RECONFIGURE
GO
sp_configure 'c2 audit mode',1
GO
RECONFIGURE
GO

--Disable c2 audit mode
sp_configure 'show advanced options',1
GO
RECONFIGURE
GO
sp_configure 'c2 audit mode',0
GO
RECONFIGURE
GO

当然,也同样需要重启服务器实例才有效果,C2审核日志跟踪文件总是存储在服务器实例的数据目录中,可以使用SQL Server Profiler或sys.fn_trace_gettable系统函数阅读这些文件

安全审核事件类别:SQL Profiler可以用来监视各种安全审核事件,当选择要包含进跟踪的事件时,可以在Security Audit部分看到这些事件。安全审核事件使您可以有选的监视和完整的C2审核几乎同样的信息,但是在使用SQL Profiler时,可以挑选确实想监视的内容并旨在必要时进行监视。要使用SQL Server Profiler审核安全事件,可以遵循下列步骤:启动SQL Server Profiler,使用空白模板创建一个新的名为SecurityAudit的跟踪,在“事件选择”选项卡的Security Audit部分选择下列事件:Audit Add DB User Event,Audit Add Member to DB Role Event,Audit Login Change Password Event,Audit Server Principal Management Event;单击组织列按钮,找到EventSubClass,TargetLoginName,TargetUserName,RoleName和 ObjectName列,将他们移动至列表顶部并排在EventClass之后,以便于阅读结果;单击运行按钮启动这一跟踪;在SQL Server Management Studio中打开一个新的查询窗口,输入下列代码

--Create a new server login
EXEC sp_addlogin 'Paul','Microsoft123','AdventureWorks2008';
GO

--Exclude this login from OS policy constraints
ALTER LOGIN Paul WITH CHECK_POLICY=OFF
GO

--Allow this user to access AdventureWorks2008
USE AdventureWorks2008
GO

CREATE USER Paul FOR LOGIN Paul
GO
EXEC sp_addrolemember N'db_owner','Paul'
GO

--Clean up
DROP USER Paul
GO
EXEC sp_droplogin 'Paul';
GO

在查询完成后,停止跟踪并检查结果,图(略)

SQL跟踪:SQL跟踪提供了使用SQL Server Profiler捕获事件的另外一种方式。通过SQL跟踪,可以使用TSQL系统存储过程定义跟踪,这对于想开发自己的自定义审核解决方案的组织特别有 用。创建SQL 跟踪的基本步骤如下所示:使用sp_trace_create存储过程创建一个跟踪;使用sp_trace_setevent存储过程添加想包含的事件。 在添加事件时,必须为想包含进跟踪中的每个事件和列组合执行一次该存储过程;如果有需要,可以使用sp_trace_setfilter存储过程为捕获的 事件定义一个筛选器。创建好跟踪后,可以使用sp_trace_setstatus存储过程启动,停止和关闭它。

下列代码创建和启动一个“安全审核事件类别”一节所包含的SQL Server Profiler示例等效的跟踪,这个跟踪将一直运行,直到使用sp_trace_setstatus存储过程停止它或者服务器实例重启

--Create a new trace
Declare @id int
exec sp_trace_create @id output,0,N'C:\ProfilerTraces\SecurityAudit'
select @id 'traceid' --Display the trace id

--Add some events
Declare @On bit
SET @On=1
--Event 109=Audit Add DB User Event
exec sp_trace_setevent @id,109,21,@On --EventSubClass
exec sp_trace_setevent @id,109,42,@On --TargetLoginName
exec sp_trace_setevent @id,109,39,@On --TargetUserName
exec sp_trace_setevent @id,109,38,@On --RoleName
exec sp_trace_setevent @id,109,34,@On --ObjectName
--Event 104=Audit Add Login Event
exec sp_trace_setevent @id,104,21,@On --EventSubClass
exec sp_trace_setevent @id,104,42,@On --TargetLoginName
exec sp_trace_setevent @id,104,39,@On --TargetUserName
exec sp_trace_setevent @id,104,38,@On --RoleName
exec sp_trace_setevent @id,104,34,@On --ObjectName
--Event 110=Audit Add Number to DB Role Event
exec sp_trace_setevent @id,110,21,@On --EventSubClass
exec sp_trace_setevent @id,110,42,@On --TargetLoginName
exec sp_trace_setevent @id,110,39,@On --TargetUserName
exec sp_trace_setevent @id,110,38,@On --RoleName
exec sp_trace_setevent @id,104,34,@On --ObjectName
--Event 107=Audit Login  Change Password Event
exec sp_trace_setevent @id,107,21,@On --EventSubClass
exec sp_trace_setevent @id,107,42,@On --TargetLoginName
exec sp_trace_setevent @id,107,39,@On --TargetUserName
exec sp_trace_setevent @id,107,38,@On --RoleName
exec sp_trace_setevent @id,107,34,@On --ObjectName

--Start the trace
exec sp_trace_setstatus @tracedid=@id,@status=1 --Starts the trace
GO

下列代码将停止和关闭跟踪,要确保替换了启动跟踪时所返回的跟踪ID

--Stop and close the trace
Declare @id int
SET @id=3  --enter the value recorded above

exec sp_trace_setstatus @traceid=@id,@status=0 --Stops the trace
exec sp_trace_setstatus @traceid=@id,@status=2 --Closes the trace
GO

跟踪数据更改

  SQL Server 2008引入了两种不同的跟踪更改的方法,第一种是变更数据捕获,这是一个功能全面的,高度可定制的更改和数据跟踪解决方案;第二种是更改跟踪,它与第一种方法十分相似,但是没有跟踪数据的功能。

变更数据捕获Change Data Capture,CDC:在使用变更数据捕获之前,必须配置它,配置过程非常简单,使用sys.sp_cdc_enable_db存储过程在数据库上启用 变更数据捕获;使用sys.sp_cdc_enable_table存储过程为数据库中的一个或多个表启用变更数据捕获。在数据库上启用变更数据捕获会创 建所有必须的元数据表和支持的存储过程、函数和数据管理视图。要在数据库上启用CDC,必须是sysadmin固定服务器角色的成员。CDC进程将独占使 用一个名为cdc的架构和一个名为cdc的数据库用户。如果数据库中已存在此名称的架构或用户,那么在重命名或删除冲突对象以前将无法启用CDC

Use AdventureWorks2008
GO

--Enable Change Data Capture
EXEC sys.sp_cdc_enable_db
GO

在对该数据库启用了CDC后,可以使用sys.sp_cdc_enable_table存储过程为特定表启用CDC

User AdventureWorks2008
GO

--Enable Person.Person table for Change Data Capture
EXEC sys.sp_cdc_enable_table 'Person','Person',@role_name=NULL,@supports_net_changes=1
GO

如果启用了变更数据捕获,CDC会创建一个相应的更改表,该表用一些额外的元数据列镜像源表架构。另外还会创建两个作业,一个是用于处理SQL事务 日志的捕获作业,另一个是用于从更改表中清除超过指定保留时间的行。需要确保运行了SQL Server代理服务来处理这些作业。如果想获得启用了CDC的数据库或表,可使用sys.databases中的iscdc_enabled列或 sys.tables中的istracked_by_cdc列:

--Display the name of every database enabled for cdc
SELECT name FROM sys.databases WHERE is_cdc_enabled=1

--Display the name of every table enabled for cdc in AdventureWorks2008
Use AdventureWorks2008
GO
SELECT name FROM sys.tables WHERE is_tracked_by_cdc=1
GO

可以使用sys.sp_cdc_disable_table存储过程禁用表的CDC,也可以使用sys.sp_cdc_disable_db存储过程对整个数据库禁用CDC

USE AdventureWorks2008
GO
EXEC sys.sp_cdc_disable_table 'Person','Person','All' 
GO

Use AdventureWorks2008
GO
EXEC sys.sp_cdc_disable_db
GO

在配置了变更数据捕获之后,您可能想在某个时间查询更改表以了解是否发挥僧了变更,如果,具体又是什么,对于每个更改表,有两个表值函数可用于 此目的:cdc.fn_get_all_changes_<schema_table>,为制定的更改表返回一个完整的变更列 表,cdc.fn_get_net_changes_<schema_table>,返回一个变更的合并列表,其中一个输出行代表变更的每行 的最终版本。下面的示例将显示为Person.Person表捕获的所有更改,以及表明更改是否设计LastName列

USE AdventureWorks2008
GO
Declare @from_lsn binary(10)
Declare @to_lsn binary(10)
Declare @lastname_ordinal int

--Get the availabe log sequence range
SET @from_lsn=sys.fn_cdc_get_min_lsn('Person_Person')
SET @to_lsn=sys.fn_cdc_get_max_lsn()

--Get the ordinal value for the LastName column
SET @lastname_ordinal=sys.fn_cdc_get_column_ordinal('Person_Person','LastName')

--Return the list of changes and whether or not the change affected in the LastName column
SELECT sys.fn_cdc_is_bit_set(@lastname_ordinal,_$update_mask) 'LastNameChanged',* FROM cdc.fn_cdc_get_all_changes_Person_Person(@from_lsn,@to_lsn,'all')
GO

如前所述,变更数据捕获使用捕获作业和清除作业使更改表保持最新,并且是一个可管理的大小,当为数据库的第一个比表启用变更数据捕获时,会自动 创建这些作业,对于这些作业的默认配置不能进行任何控制;不过,可以在他们创建之后使用sys.sp_cdc_change_job存储过程进行修改。可 以使用sys.sp_cdc_help_jobs查看当前作业配置,对作业配置的更改将在停止并重启作业后生效。通过 sys.sp_cdc_stop_job和sys.sp_cdc_start_job存储过程可以方便地完成这项工作,下面的示例显示了如果修改清除作 业,使其变更数据保留五天

USE AdventureWorks2008
GO

--Change the Cleanup Job retention to 6000 minutes
EXEC sys.sp_cdc_change_job @job_type='cleanup',@retention=6000
GO

EXEC sys.sp_cdc_stop_job 'cleanup'
GO
EXEC sys.sp_cdc_start_job 'cleanup'
GO

EXEC sys.sp_cdc_help_jobls
GO

还有一些动态管理视图可以帮助管理员监视变更数据捕获进程的状态,sys.dm_cdc_log_scan_sessions四暗示子服务器最 近一次启动以来已运行的每个日志扫描会话的详细信息;sys.dm_repl_traninfo显示有关每个复制的事务的信息,如果一起启用了CDC和复 制,这将只与变更数据捕获有关;sys.dm_cdc_erros这个视图返回有关在最后32个会话中发生的任何错误的信息。

更改跟踪:更改跟踪可确定是否对表中的行做了改动,但不会提供有关已更改的实际数据的详细信息。下面的示例显示了如何在AdventureWOrks2008数据库中启用更改跟踪

ALTER DATABASE AdventureWorks2008
SET CHANGE_TRACKING=ON
(CHANGE_RETENTION=2 DAYS,AUTO_CLEANUP=ON)
GO

USE AdventureWorks2008
GO
ALTER TABLE Person.Person ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED=ON)
GO

现在假定有一个外部应用程序需要与Person.Person同步,其中设计的最困难的任务是如何确定自上次更新后发生了哪些改变,此时更改跟 踪就有了用武之地。更改跟踪使用一个内部的,数据库范围的版本控制系统将所有更改排序。为了获得最近一次同步以来的净变更,需要知道从哪个版本开始。如果 这是初始加载,那么将记录当前版本号,并完整复制源数据以建立初始基准。为了获得这个模拟同步的起点,需要遵循以下步骤,在一个查询窗口中输入并执行下面 的代码

SELECT CHANGE_TRACKING_CURRENT_VERSION() 'current_version'

写下结果,因为后面会用到它。此时,假定外部应用程序是断开的,当外部应用程序重新连接时,在这一时刻之后发生的变更需要同步,在一个查询窗口中输入并执行下列代码,对Person.Person表做一些更改

USE AdventureWorks2008
GO

--Change some data in the Person table
UPDATE Person.Person
SET FirstName='Paul',LastName='Allen' WHERE BusinessEntityID=1
GO

--Change the ame record again
UPDATE Person.Person
SET FirstName='Bill',LastName='Gates' WHERE BusinessEntityID=1
GO

--Insert a new row
INSERT Person.Person (BusinessEntityID,PersonType,FirstName,LastName) VALUES(310,'SC','Joan','Ballmer')
GO

--Update the new row
UPDATE Person.Person
SET FirstName='Steve' WHERE BusinessEntityID=310
GO

接着,外部应用程序重新连接并试图同步,为此,它需要知道自他最近一次同步以来发生了哪些变。更改跟踪使用之前记录的初始版本号提供这一信息,将下列代码输入一个命令窗口并执行,确保替换之前记录的版本号:

USE AdventureWorks2008
GO

Declare @sync_version bigint
SET @sync_version=[INSERT YOUR VALUE HERE!]

SELECT CHANGE_TRACKING_CURRENT_VERSION() 'current_version';

--Identify the changes made after an earlier version
SELECT CT.BusinessEntityID, p.FirstName, p.LastName, CT.SYS_CHANGE_OPERATION,CT.SYS_CHANGE_COLUMNS,CT.SYS_CHANGE_CONTEXT FORM CHANGETABLE(CHANGES Person.Person,@sync_version) CT left join Person.Person p on CT.BusinessEntityID=p.BusinessEntityID
GO

  有可能外部应用程序断开提啊就,变更记录变得不再可用,通过使用 CHANGE_TRACKING_MIN_VALID_VERSION函数确保保存的基准版本大于最低版本,可以很容易地确定这一点。如果保存的基准版本 不大于最低版本,那么外部应用程序需要获取同步对象的完整副本并建立新的基准

数据收集

  数据收集可以自动地将不同的可定制的管理数据收集到一个数据仓库中,并在这里对数据进行分析和制作报表。这 种功能与本文其他介绍的监视同能不同,因为数据收集是定期反复捕捉数据快照,从而允许监视数据随时间推移如何改变并查看趋势。数据收集器不仅可以被用来使 用SQL跟踪和性能监视,还可以为跨多个服务器管理数据提供了一个中央收集点;允许控制捕获哪些管理度量标注;允许使用SQL Server Reporting Services生成报表;还可以通过一个功能丰富的API扩展。

数据收集器是将所有事务联系在一起的中心对象。它以来数据收集组来定义数据提供程序、计划和所收集数据的保持期。SQL Server代理使用收集组中定义的计划运行数据收集器,后者执行SSIS包从数据提供程序中收集所请求的数据,SQL Server代理通过使用收集组中的单独计划,再次运行数据收集器,这次后者运行SSIS包处理数据并将其上载到管理数据仓库。收据收集器还维护审核和历 史记录信息,这样管理员可轻松确定并解决问题。

数据收集器使用msdb存储其大部分控制数据,包括配置,运行时,审核和收集历史记录信息,如果服务器上没有msdb数据库,将不能使用数据收 集。想使用数据收集器,用户必须是已定义的数据收集器安全角色的成员,角色只有三个,分别是 dc_admin;dc_operator,dc_proxy。

如果还没有数据仓库数据库,那么需要创建一个,在创建了该数据库后,就可以配置数据收集了。这两个人物都可通过SSMS中的配置管理数据仓库向 导完成,该向导位于服务器实例的管理节点的数据收集节点的上下文菜单中,你inkeneng需要运行该向导两次,一次用于创建一个新的存储数据库,另一次 用于配置数据收集。在该向导的第一个页面上可以选择执行哪个人物。在创建一个新的数据仓库时,必须制定存储数据库的位置,如果没有存储数据库,可以单击新 建按钮从同一对话框中创建一个。在选择了存储数据库后,下一步是指明应将什么访问级别指派给特定用户或组,数据收集使用3个预定义的安全角 色:mdw_admin,mdw_writer,mdw_reader。在创建一个管理数据仓库后,需要运行向导配置数据收集,对于这个人物,需要设置两 个选项:用于存储的管理数据仓库的名称;用于缓存文件的目录的名称。在配置数据收集向导完成后,SQL Server 2008将启用数据收集并启动系统数据收集组。数据收集节点旁边的图标将会改变,而您将可以启用或禁用数据收集并管理单独的数据收集组。

数据收集器类型定义了可用于为数据收集组收集数据的数据提供程序。目前MS提供了四种不同收集类型:TSQL查询收集器类型,SQL跟踪收集器 类型,性能计数器收集器类型和查询活动收集器类型。数据收集组收集来自一个或多个收集想的数据,并将数据上载到数据仓库。每个收集项基于使用XML格式的 输入参数配置的数据收集器类型。收集组还定义了数据收集和上载的计划,运行作业的帐户以及上载数据的保持其。SQL Server 2008本身安装了3个系统数据收集组,磁盘使用情况,服务器活动,和查询统计信息

每个系统数据收集组都有一个相关联的报表,可以通过右击“数据收集”节点并选择报表-管理数据仓库命令在SSRS中进行查看,可用的报表分别是 服务器活动历时记录,磁盘使用情况摘要和查询统计信息历史记录。可以使用sp_syscollector_create_collection_set系 统存储过程创建自定义的数据收集组。目前,必须使用T-SQL创建新的数据收集组,因为还没有GUI可用。

收据收集器的主要用途之一就是收集和监视性能数据;不过,默认的收集组可能包含太多的信息。下面的示例演示如何创建一个新的数据收集组,它包含 一些用于检测CPU瓶颈的CPU特定的性能计数器。首先,新黄建一个新的自定义数据收集组,在新的查询窗口中输入并执行下列代码

--Step1
USE msdb
GO

DECLARE @collection_set_id int
DECLARE @collection_set_uid uniqueidentifier
SET @collection_set_uid-'9170CBA3-2C8D-402f-82F5-CD427F75D221'

exec dbo.sp_syscollector_create_collection_set
@name='CPU Bottlenecks',
@target='', --use this for another server '/Server[@Name="MSSQLSERVER"]'
@collection_mode=0,
@days_until_expiration=5,
@description='Collects selected PerfMon CPU counters',
@logging_level=0, --0-2 are valid, in increasing verbosity
@schedule_name=N'CollectorSchedule_Every_5min',
@collection_set_id=@collection_set_id OUTPUT,
@collection_set_uid=@collection_set_uid OUTPUT

SELECT @collection_set_id 'collection set id',
@collection_set_uid 'collection set uid'
GO

记下在collection set id列中返回的值。后面需要用到它。使用性能计数器数据收集器类型添加一个新的收集项,这个收集项引用3个用于标识CPU瓶颈的CPU计数器。在一个新的查询窗口中输入并执行下列代码,确保替换上面记录的收集组ID值

--STEP 2
USE msdb
GO

DECLARE @collector_type_uid uniqueidentifier,@collection_item_id int,@collection_set_id int
SET @collection_set_id=[YOUR VALUE] --use the value you recorded above

--Get collector type uid
SELECT @collector_type_uid=collector_type_uid FROM [dbo].[syscollector_collector_types] WHERE name=N'Performance Counters Collector Type';

--Add a new collection item
EXEC [dbo].[sp_syscollector_create_collection_item]
@name='Perfmon CPU counters',
@parameters='
<ns:PerformanceCountersCollector xmlns:ns="DataCollectorType">
<PerformanceCounters Objects="System" Counters="Processor Queue Length" Instances="*" />
<PerformanceCounters Objects="Processor" Counters="% Processor Time" Instances="_Total"/>
<PerformanceCounters Objects="Process" Counters="% Processor Time" Instances="sqlservr">
</ns:PerformanceCountersCollector>',
@frequency=5,--every 5 seconds
@collection_set_id=@collection_set_id,
@collector_type_uid=@collector_type_uid,
@collection_item_id=@collection_item_id OUTPUT

select @collection_item_id 'collection item id'
GO

现在就创建了这个新的收集组。实际上,它已经显示在SQL Server Management Studio中,使用SSMS,展开服务器的管理-数据收集节点,您将看到一个名为CPU Bottlenecks的新项。需要启动这个新的数据收集组,使其开始在数据仓库中存储性能监视器计数器。为此,右击CPU Bottlenecks数据收集组,选择启动数据收集组命令。现在可以稍事休息,这个数据收集组将每五秒捕获一次数据,每五分钟上载一次数据。所以在查询 数据仓库之前,需要至少五分钟来确保一些数据上载完成,开始查询时,可以在一个新的查询窗口中输入并执行下列代码

--STEP 3
--Report the captured data
USE MgmtDataWarehouse
GO

DECLARE @collection_set_uid uniqueidentifier,@endtime datetime
SET @collection_set_uid='9170CBA3-2C8D-402f-82F5-CD427F75D221'

SELECT pci.[object_name],pci.counter_name,pci.instance_name,pc.* FROM snapshots.performance_counter_values AS pc INNER JOIN core.snapshots s ON s.snapshot_id=pc.snapshot_id left join snapshots.performance_counter_instances pci on pc.performance_counter_instance_id=pci.performance_counter_id WHERE s.collection_set_uid=@collection_set_uid ORDER BY pc.performance_counter_instance_id,pc.collection_time 
GO

另外,可以从SSMS的数据收集节点的上下文菜单或任意数据收集组的上下文菜单中选择查看日志命令来查看这些数据收集日志。

在SSMS中右击数据收集节点,然后通过报表-管理数据仓库命令选择一个报表可以运行他们。这三个报表分别是服务器活动历时记录、磁盘使用情况摘要和查询统计信息历史记录。

监视数据库修改

SQL Server 2008提供了一个新的功能,就是使用数据定义语言触发器和事件通知来监视甚至防止数据库被修改。DDL触发器可以在数据库和服务器作用域中进行定义,和 传统的数据修改语言触发器一样,显示事件被激发,然后在其上定义的DDL触发器被激发。如果一个触发器被定义为防止数据库删除,则该数据库会首先被删除, 然后当这个带有ROLLBACK语句的触发器被激发时,在还原这个数据库。和传统触发器不同,DDL触发器定义在一个特定的语句或语句组上,而不管该语句 指向的对象是什么,也不会被指派给一个特定的对象。因此,不管删除哪个数据库,DROP_DATABASE触发器都会激发。

在传统DML触发器中,触发器的很多功能是通过访问内存中的Deleted和Inserted表得到的,这些表值在触发器存在的时间里存在。 DDL触发器不使用Inserted和Deleted表。相反,如果需要捕获来自事件的信息,就需要使用EVENTDATA函数。

EVENTDATA函数返回一个XML文档,包含有关导致触发器执行的事件的预定义数据。数据类型很大程度上取决于导致触发器执行的触发器,但 是所有的触发器都返回触发器被激发的时间、导致触发器执行的进程ID以及事件类型。下面的例子创建了一个服务器范围的DDL触发器,它会在创建、修改或删 除数据库时执行

USE Master
GO
CREATE TRIGGER ServerDBEvent ON ALL SERVER FRO CREATE_DATEBASE,DROP_DATABASE_ALTER_DATABASE AS
DELCARE @Data AS xml
DECLARE @EventType AS nvarchar(25)
DECLARE @PostTime AS nvarchar(25)
DECLARE @ServerName AS nvarchar(25)
DECLARE @DBName AS nvarchar(25)
DECLARE @Login AS nvarchar(25)
DECLARE @TSQLCommand AS nvarchar(MAX)

SET @Data=EVENTDATA()
SELECT @EventType=@Data.value('(/EVENT_INSTANCE/EventType)[1]','nvarchar(25)'),@PostTime=@Data.value('(/EVENT_INSTANCE/PostTime)[1]','nvarchar(25)'),@ServerName=@Data.value('(/EVENT_INSTANCE/ServerName)[1]','nvarchar(25)'),@Login=@Data.value('(/EVENT_INSTANCE/LoginName)[1]','nvarchar(25)'),@DBName=@Data.value('(/EVENT_INSTANCE/DatabaseName)[1]','nvarchar(25)'),@TSQLCommand=@Data.value('(/EVENT_INSTANCE/TSQLCommand)[1]','nvarchar(max)')

PRINT @EventType
PRINT @PostTime
PRINT @ServerName
PRINT @login
PRINT @DBName
PRINT @TSQLCommand;
GO

要测试此触发器,执行下列代码并检查输出结果

USE Master;
GO
CREATE DATABASE SampleDB;
GO
ALTER DATABASE SampleDB SET RECOVERY SIMPLE;

下面的这个例子中,创建一个用于组织修改数据库的DDL触发器,并在一个审核表中记录视图修改数据库的人员以及他们采用的方式,步骤如下,首先创建一个表,用于包含从DDL触发器收集来的审核信息,执行下列代码

USE AdventureWorks2008;
GO
CREATE TABLE DatabaseDDLAudit (AuditID int IDENTITY(1,1) NOT NULL, PostTime datetime NOT NULL, LoginName nvarchar(128) NULL,Command nvarchar(MAX) NULL,EventData xml NULL);

在有了一个审核表之后,就可以创建DDL数据库触发器,它会插入到表中并防止修改数据库,执行下列代码

USE AdventureWorks2008
GO
CREATE TRIGGER NoDDLAllowed ON DATABASE FOR DDL_DATABASE_LEVEL_EVENTS
AS 
SET NOCOUNT ON
DECLARE @data AS xml, @PostTime AS datetime, @HostName AS nvarchar(128)
DECLARE @LoginName AS nvarchar(128), @Command AS nvarchar(MAX)

SET @data=EVENTDATA()

SELECT
@PostTIme=CAST(@Data.value('(/EVENT_INSTANCE/PostTime)[1]','nvarchar(25)') AS datetime),
@HostName=@Data.value('(/EVENT_INSTANCE/HostName)[1]','nvarchar(25)'),
@LoginName=@Data.value('(/EVENT_INSTANCE/TSQLCommand)[1]','nvarchar(max)')

RAISERROR ('What?! Are you nuts? Modifications to this database are not allowed! You can expect a visit from human resources shortly.',16,1)
ROLLBACK
INSERT DatabaseDDLAudit(PostTime, LoginTime, Command, EventData) VALUES (@PostTime, @LoginName, @Command, @Data) 
RETURN;

要测试此触发器并查看收集的数据,执行下列代码

USE AdventureWorks2008;
GO
ALTER TABLE Person.Person ADD NewColumn varchar(10) NULL;

现在,使用下列命令查询审核表以查看收集的数据

USE AdventureWorks2008;
GO
SELECT * FROM DatabaseDDLAudit;
GO

如果需要对数据库做授权改动,需要禁用或删除(最好禁用)触发器,下面的脚本演示了如何禁用DDL触发器,作出改动并在完成后重新启动触发器

USE AdventureWorks2008;
GO
DISABLE TRIGGER NoDDLAllowed ON DATABASE;
GO
CREATE TABLE TestTable(Column1 int,Column2 int);
GO
ENABLE TRIGGER NoDDLAllowed ON DATABASE;
GO

[转载]SQL Server 性能调优(内存) - Amaranthus - 博客园

mikel阅读(865)

[转载]SQL Server 性能调优(内存) – Amaranthus – 博客园.

 

存储引擎自调整

SQL server 是如何分配内存的

32bit地址空间的限制

用户模式vas分配和virtualalloc

boffer pool 分配内存(保留内存)

VAS调整

AWE

启动参数-g

诊断内存压力

内存相关计数器

SQL Server :Buffer Manager

buffer cache hit ratio

page life expectancy

Free Pages

Free list stalls/sec

lazy write/sec

SQL Server:memory Manager

total server memory target server memory

memory grants outstanding

memory grants pending

内存相关的DMV

内存相关问题

分页问题

因为lock pages 没有设置服务器最大内存导致系统不稳定

701错误和 FAILED_VIRTUAL_RESERVE

多实例下的内存设置

总结

 

内存的分配和使用在网上一直是讨论的话题。对sql server 来说使用了内存但是不释放是很正常的事情,和其他的应用程序是不一样的,导致一些用户认为sql server确实内存但事实并非如此。

存储引擎自调整

sql server 2005开始,内存的管理就是动态的,与其他关系型数据库不同使用已经调整好的内存空间。如plan cache 是全部的并且自动的,引擎控制根据当前数据库的负载和其他活动信息来控制plan chache内存。sql server 虽然缺少内存的控制方法,但是还有有参数可以设置内存的,如操作系统的版本,内存的大小和处理器的体系结构。

sql server 是如何分配内存的

第一反应就是查看windows任务管理关于sql server 的内存使用情况,一看到sql server 占用了很大的内存就可能会认为sql server 缺少内存,但是缺少内存和占用很大内存其实是没什么关系的。sql server是被设计为大内存使用的,如buffer cache,存放了大量的数据页,为了减少io,提高性能。通常,不管你提供了多少内存sql server 都能使用光,除非你接到一个来自操作系统的memory low通知,sql server 就会自动调节减小内存,通知有2中:

memory high:通知sql server可以增加内存的使用量

memory low:通知sql server 释放内存

如果windows 不通知,那么sql server 就不会增加或减少内存,在windows 2003 sql server 2005 以前的版本是没有的。有一篇关于sqlos中内存的文章介绍了不同类型的内存压力,大致如下:

 

文章地址:http://blogs.msdn.com/b/slavao/archive/2005/02/01/364523.aspx

sql server 最大内存使用量由以下几点要素:

1.安装的物理内存数量

2.操作系统的最大内存限制

3.sql server 体系结构,32b64b

4.sql server 配置项

5.sql server 版本



32bit地址空间的限制

对于sql server 32位最大的影响就是32位的地址空间,也就是最大4g而且包含内核模式和用户模式。用户模式和内核模式各2g。主要我们讨论一下几点

1.sql server 用户模式如何分配内存

2.buffer pool的保留空间

3.用户模式使用3g 内存

4.sql server 如何使用大于4g内存,data cache调用awe内存。

接下来主要讲的都是32bit 下内存使用的限制和64bit关系不大。

用户模式vas分配和virtualalloc

sql server 为用户默哀是保留了2g的内存地址,sql server 用户模式需要内存时,通过调用virtualalloc 分配内存并返回32位的指针,因为地址空间的限制,所以sql server 只能使用2g内存。

通过virtualalloc分配的内存并不一定是实际的物理内存,不管安装了多少物理内存,SQLServer都是2g的地址使用空间。windows也保证sql server 和其他应用程序使用的内存,不会超过实际物理内存和页面文件的总容量。如果总共安装的内存少于2g,那么sql server 就会又物理内存的限制,sql server buffer pool的内存也不会超过安装的内存数量。virtualalloc 分配的内存都是页模式的,也就是如果出现内存压力windows 就可以直接把内存写入到磁盘中。

boffer pool 分配内存(保留内存)

如果sql server 请求大于8k的连续内存的时候,会使用多页分配器分配内存。backup buffers 是非buffer pool分配的最大的一个,需要的内存是 maxtransfersize * backupbuffercount 在正常的备份下,需要16backup buffer,每一个buffer4m的内存。所以会吃掉64m的非buffer pool 内存。为了保证有住够非buffer pool32bsql server 在启动的时候会保留一部分内存。一旦有保留内存,那么buffer pool的内存空间会是安装的空间减去保留的空间。对于20052008保留空间为 maxworkerthreads*0.5mb+256mb(默认保留空间大小)maxworkerthreads = processprcount-4+256。在2000 maxworkerthreads = 256,按这个计算保留空间至少是384MB,但是通常少于432mb。保留空间的参数在sql server 启动参数内设置,标记为-g,可是适当在计算出来的结果上增加内存。sql server 一共2g的地址空间减去保留的,大概剩下1.6Gbuffer pool 使用。对于超过4gb的内存可以使用awe来分配内存。

VAS调整

对于4g的内存,系统可以修改内核的地址空间使用率把1:1,变为1:3。这个叫做4g调整,这样就减少了内核模式下的地址空间,导致了PTE减小,pte是虚拟内存和物理内存的映射,一但减小,sql server 总共可使用的内存也就背减少了。所以在调整的时候要谨慎。在windows 2008下可以使用bcdedit /set 命令设置increaseuserva 可以设置从 2048 3072的值。在2000 2003 可以使用/3g的标签来开启。

AWE

如果安装了超过了4个的内存,那么就可以使用awe,当然windows 必须支持才能使用。开启pae,系统最多能够使用64G内存。在内存分配上因为使用virtualalloc分配内存会有限制,那么就改用allocateuserphysicalpage来分配内存,一旦被分配那么内存页被锁住,无法交换到交换文件。当启用awe后所有的datacache使用过awe分配内存还有就是 plancache也是。要启用awe那么pae就必须被设置,还有在sql server awe enabled 参数sql server 的启动用户必须有lock pages 权限。因为awe内存无法被交换出去,所以设置最大内存数量很偶必要,使得sql server 内存使用量得到限制,不会无限期的增长影响其他应用程序和系统内存的使用问题。在系统中如果你的内存少于16g,你可以使用前面提到的awe+4gt的方式,但是不推荐因为如果你一旦设置了4gt,windows 可管理内存从64g下降到了16g yinwei 4gt减少了pte的大小。

启动参数-g

sql server 启动的时候会分配一部分保留内存,保留内存对buffer pool 来说是比较小的。很多的内存是从buffer pool上分配而不是从保留空间中分配,因此基本上不会有问题,但是随着日积月累应用程序变的越来越复杂,默认的保留空间已经无法满足需求那么就通过-g参数配置。因为不合适的空间大小和保留空间的碎片问题,导致无法请求到连续的内存空间。查看sys.dm_os_virual_address_dump 动态性能视图可以查看可用的虚拟地址空间。关于确定可用地址空间可以看相关文章:http://sqlblogcasts.com/blogs/christian/archive/2008/01/07/sql-servermemtoleave-vas-and-64-bit.aspx

保留空间的碎片问题是最难处理的,如果你打电话到微软技术支持,他们给的建议就是升级到64b,因为64b的虚拟地址空间是8t,不会不够如果你不想那么就加大你的保留空间地址。

 

诊断内存压力

sql server 内存不足的时候,那么data cache 存储的数据就少,查询不能在内存中请求到数据,那么就请求io,放入内存,这些数据又很快的被清出内存,需要的时候有继续从io读进来,这个就是buffer pool 滚筒。buffer pool 滚筒会照成io过高,就会误认为是io的问题,其实是内存不足的问题。

内存相关计数器

有一些重要等待和内存使用率相关的性能计数器,但是要记清楚并没有一个计数器就能够表明内存压力的,一个简单的计数器快照并不能说明问题。内存压力的诊断需要一段时间的跟踪。

SQL Server :Buffer Manager

又很多有用的计数器都是这 buffer manager 对象下面,可以帮助发现buffer pool滚筒的问题。

buffer cache hit ratio

buffer cache hit ratio一般情况下在oltp中要高于95%,在olap中要高于90%。可惜的是没有关于这个性能指标相关的解释,和这个值是如何影响预读机制的。如果这个指标的值有巨大的下降那么就说明有问题。这个不能说明内存压力和sql server 健康指数。

page life expectancy

page life expectancy是页生命周期,也就是一个数据页在内存中的时间。在以前sql server 2000 4g的内存已经很大了,sql server buffer pool的大小是1.6g,如果sql server 从磁盘上读取1.6g的数据也只要5分钟,但是今天64g的内存是主流,如果从磁盘一下子读取50g的内存,会严重的冲击io。当存在大量的查询扫描表,读入新的数据页,导致生命周期值下降也不是不正常的。这个值必须长期的监视来分析问题。

Free Pages

free pages是内存中空页的数量,不要接近于0。这个值说明查询能否在其他查询不是放内存的情况下,快速的分配内存的主要依据。如果free pages 很少,页生命周期很短,并且伴随着空页争用(free list stalls/sec)的情况那么很有可能导致内存压力。

Free list stalls/sec

Free list stalls/sec每秒空页等待的数量,如果一段时间内都在0以上那么说明可能存在内存压力。

lazy write/sec

lazy write/sec 就是每秒写入磁盘的次数。如果发生量很大并且生命周期很短,free page 很少,但是 free list stall/sec 量很大,那么就是发生内存压力了。



SQL Server:memory Manager

SQL Server:memory Manager对象内对内存的消费和内存管理的问题提供了很重要参考

total server memory target server memory

2个计数器代表了当前sql server 使用的总共内存和sql server 想要用的内存。如果 target server memory超过了total server memory,也是内存压力的重要标志。sql server 会减少内存的需求来接近服务的可用内存,或者通过最大服务器内存配置,所以当内存出现压力问题的时候不应该第一时间去查看这2个计数器

memory grants outstanding

该值是现实多少进程已经成功的获取了内存的授权。在一段时间内,业务高峰期,如果该值过低,那么标志可能存在内存压力,特别是 memory grants pending 也比较高的情况下。

 memory grants pending

该值是有过少进程正在等待内存的授权。如果为非0,那么说明需要调整或者优化负载或者增加内存。

 

内存相关的DMV

和内存相关的等待和非buffer pool 内存分配的信息,从dmv中获取。

sys.dm_exec_query_memory_grants 可以查看正在等待授权的查询,特别是大内存的授权

sys.dm_os_memory_cache_counter multi_pages_kb 显示了多页分配的内存分配

sys.dm_os_sys_memory 合计了系统当前内存,缓冲,cache,多页分配分配的内存。

sys.dm_os_memory_clerks 显示相关管理内存的书记进程,如 buffer pool 大内存的使用并且结合 MEMORYCLERK_SQLQERESERVATIONS 可以发现buffer pool 内存不住

 

内存相关问题

通常的一些问题可以被分为3种:错觉,错误配置,正在的问题。大量的疑似内存问题的最后其实只有一小部分才是真正的问题。

分页问题

sql server 重要的组件被page out了,会在error log 中出现一个信息a significant part of SQL Server process memory has been paged out对于 worksettrim通常是下列的情况:

1.当没启用lock pages的时候,不正确的最大服务器内存的设置

2.windows 中系统缓冲,被用来处理非缓存的io操作,如复制文件。

3.硬件驱动问题导出使用过多的内存。

最有效的阻止方法是,开启lock pages,文章:http://support.microsoft.com/kb/918483讲述了64b sql server 发生workset trim的根本原因。

 

因为lock pages 没有设置服务器最大内存导致系统不稳定

如果sql server开启了 lock pages 但是 最大服务服务内存又没设置,sql server 会吃光所有的服务器的可用内存。当windows 内存紧张会向通知sql server 内存压力,但是buffer pool working set 都不会被交换页面文件。这样会导致windows crash。如果最大内存数设置的过大也会造成同样的情况。

 

701错误和 FAILED_VIRTUAL_RESERVE

sql server 申请一个连续的vas失败,就会返回701错误和答应出需求大小的信息。这个错误只会发生在32bsql server32b sql server vas十分有限。这个错误和buffer pool 没有什么关系主要是大于8k内存分配的时候出现。解决办法就是使用-g启动参数,修改sql server保留空间。

 

多实例下的内存设置

sql server 如果多实例安装在单个机器上或者一个故障转移能减少license的购买。当一台服务器上有多个实例,那么设置min_server_memory max_server_memory 很重要,根据每个实例的负载,避免出现内存冲突的情况。根据先前提到过的性能指标和dmv 对内存使用情况监测,设置一个合理的最大内存和最小内存数。在多实例情况下,建议把最小内存数也设置上,因为如果有最小内存数,那么sql server 申请内存的时间会减少。如果最小内存数没有设置,sql server 可能会自愿减少内存的使用率而导致性能下降。

 

总结

在内存上面 32b 64b 又很大的不同,如果负载较高那么就是用64b6号那天sql server 2012发布,还没去看联机文档,但是听朋友说201232b不在支持awe,也就是说如果大内存那么就用64b64b内存方面很有优势。这篇主要讲了内存的状况,总结到这里我已经明显的感觉到,常见问题远没有原理来的重要。troubleshooting只是原理的一种应用而已。关于资源的都讲完了,cpu,内存,io,下一篇会讲讲miss indexmiss index 也是会引起 cpu,内存,io 的异常状况。

[转载]sqlserver内存释放 - chunchill - 博客园

mikel阅读(964)

[转载]sqlserver内存释放 – chunchill – 博客园.

由于SQL Server对于系统内存的管理策略是有多少占多少,除非系统内存不够用了(大约到剩余内存为4M左右),
SQL Server才会释放一点点内存。所以很多时候,我们会发现运行SQL Server的系统内存往往居高不下。
这些内存一般都是Sql Server运行时候用作缓存的,例如你运行一个select语句,
那么Sql Server会将相关的数据页(Sql Server操作的数据都是以页为单位的)加载到内存中来,
下一次如果再次请求此页的数据的时候,就无需读取磁盘了,大大提高了速度。这类的缓存叫做数据缓存。
还有一些其他类型的缓存,如执行存储过程时,Sql Server需要先编译再运行,编译后的结果也会缓存起来,
下一次就无需再次编译了。如果这些缓存已经不需要了,那么我们可以调用以下几个DBCC管理命令来清理这些缓存:

DBCC FREEPROCCACHE
DBCC FREESESSIONCACHE
DBCC FREESYSTEMCACHE(‘All’)
DBCC DROPCLEANBUFFERS

这几个命令分别用来清除存储过程相关的缓存、会话缓存、系统缓存以及所有所有缓存

但是需要注意的是,这几个命令虽然会清除掉现有缓存,为新的缓存腾地方,
但是Sql server并不会因此释放掉已经占用的内存。无奈的是,Sql Server
并没有提供任何命令允许我们释放不用到的内存。因此我们只能通过动态调整
Sql Server可用的物理内存设置来强迫它释放内存。

我们也可以通过Sql Server Management企业管理器进行动态控制。
连接到企业管理器之后打开Sql Server实例的属性面板,
找到内存设置,改变其中的最大服务器内存使用即可

–内存使用情况
SELECT * FROM sys.dm_os_performance_counters
WHERE counter_name IN (‘Target Server Memory (KB)’,’Total Server Memory (KB)’)

— 内存状态
DBCC MemoryStatus

–查看最小最大内存
SELECT
cfg.name AS [Name],
cfg.configuration_id AS [Number],
cfg.minimum AS [Minimum],
cfg.maximum AS [Maximum],
cfg.is_dynamic AS [Dynamic],
cfg.is_advanced AS [Advanced],
cfg.value AS [ConfigValue],
cfg.value_in_use AS [RunValue],
cfg.description AS [Description]
FROM
sys.configurations AS cfg

–设置最小最大内存

sp_configure ‘show advanced options’, 1

go
sp_configure ‘min server memory’, 0
RECONFIGURE
GO

sp_configure ‘max server memory’, 2147483647
RECONFIGURE
GO

sp_configure ‘max server memory’, 256
RECONFIGURE
GO
sp_configure ‘show advanced options’, 0

 

———————————————————————————————–

 

CREATE proc [dbo].reclaimmemory  –强制释放内存

as
begin

DBCC FREEPROCCACHE
DBCC FREESESSIONCACHE
DBCC
FREESYSTEMCACHE(‘All’)
DBCC DROPCLEANBUFFERS
 
exec sp_configure ‘max server memory’, 256
EXEC (‘RECONFIGURE’ )
WAITFOR DELAY ’00:00:05′
EXEC  sp_configure ‘max server memory’, 2147483647
EXEC (‘RECONFIGURE’
)
GO
 

end
–使用示例
/*
reclaimmemory

*/

[转载]Lambda表达式 - 狼性法则 - 博客园

mikel阅读(1067)

[转载]Lambda表达式 – 狼性法则 – 博客园.

lambda表达式是对匿名方法的一种改进,具有更加简洁的语法和更易理解的形式,lambda表达式可以包括表达式和语句,并且可以用与创建委托或表达式目录树类型。

lambda表达式都使用Lambda运算符=>,读作goes to。该Lambda运算符的左边是输入参数,右边包含表达式或者语句块。

Lambda表达式的声明:(参数列表)=>{方法体}

其中的参数列表和普通方法的参数列表相同,Lambda表达式是升级版的匿名方法,如下面语法所示:

返回类型  方法名(参数){方法体}  //普通方法
delegate (参数列表) {方法体}  //匿名方法
(参数列表)=>{方法体}  //

下面举个例子:

复制代码
//求两个整数最大值
            (int x,int y)=>{return x>y?x:y;}
            //决断一个字符串是否是数字
            (string text)=>
            {
                double d;
                return double.TryParse(text,out d);
            }
            //输入hello world
            ()=>{Console.WriteLine("hello world");}
复制代码

特别需要指出的是如果lambda表达式没有参数,则参数为空,但必须要圆括号。

下面看一下lambda表达式的更简洁语法:

(1)如果lambda表达式的参数类型是可以通过上下文推断时,参数类型也可以省略,如下代码所示

(x,y)=>{return x>y?x:y;}

(2)若果lambda表达式只有一个参数且参数类型被省略,则参数列表外面的圆括号也可以省略,如下代码

x=>{return x++;}

(3)如果lambda语句的方法体只有一条return语句,且return语句有返回类型,则return关键字、分号、大括号都可以省略,此时lambda表达式的方法体只剩下一个表达式,如下例子,

(x,y)=>x>y?x:y;
//(x,y)=>{retrun x>y?x:y;}
x=>x++;
//x=>{retrun x++;}

举例说明lambda表达式的应用:

输出的结果是: