[JQuery]Ajax+jQuery实现LightBox与服务器通信

mikel阅读(805)

  LightBox效果又叫windows关机效果。就是像windows关机时一样,桌面上的应用失效而只能点击关机等框内的几个按钮。在网页中其实就是利用了两个层。其中一个设置成半透明遮住整个屏幕,另一个放在其上面用来显示内容。

     在写之前看过了cloudgamer的效果,自认JavaScript功力远不及他。参考了他的效果之后写了这个简单版并加入了LightBox与服务器的通信。
     为了方便演示,我把代码都写到一个文件里了,css也直接写在行内。
     首先是覆盖层:

<div id="coverLayer" style=" display:none; background:#000000;  position:absolute; "></div>    

     先不让他显示、黑色背景、绝对定位。

     然后是LightBox:

1    <div id="lightBox" style=" display:none; width:300px; height:200px;position:absolute;z-index:1001; background:#ffffff; left:50%; top:50%; margin-left:-150px; margin-top:-100px; border:#00FFFF double 4px;">
2        <id="discover" href="#" onclick="discover();">关闭</a>
3        <form id="formLogin">
4        <p>用户名:<input id="tbUserName" name="tbUserName" type="text" /></p>
5        <p>密码:<input id="tbPassword" name="tbPassword" type="password" /></p>
6        <p><input id="btnLogin" name="" type="button" value="登录" onclick="Login();" /></p>
7        </form>
8    </div>
9

     要与数据库通信,我放了一个表单在里面获取输入。两个文本框、一个按钮。还有一个关闭LightBox的连接。对于LightBox的样式。首先还是不让其显示,高、宽这样的就不用说了。绝对定位。设置z-index为1001保证他显示在最上面。白色背景。left:50%; top:50%; margin-left:-150px; margin-top:-100px;让其居中。这是css中一种比较常见的设置块状容器居中的方法。当然,这和容器的定位有关。cloudgamer的文章中也讲到了这种方法。要了解更多的关于css的东西可以去标准之路 http://www.aa25.cn/ 或者蓝色理想 http://www.blueidea.com/等地方。当然园子里也很多。(好像有点啰嗦-_-)之后是为了好看设置了一个4像素的边框。

     再是要一个开启LightBox的链接

<id="cover" href="#" onclick="cover();">登录</a>

     单击他就执行cover();覆盖函数。

     最后加了一个select用于在IE中屏蔽的测试

1  <form >     
2     <label>select
3        <select name="select" id="select" >
4            <option>测试Select覆盖</option>
5            <option>测试Select覆盖</option>
6        </select> 
7     </label>
8 </form>

 

     之后是JavaScript
     先看一下cover();覆盖函数,这个函数只是让遮盖层和LightBox显示出来,并影藏所有的select,以免在IE中效果有误。

 1<script type="text/JavaScript" >
 2    function cover()
 3    {
 4        $("select").each(function(){this.style.visibility="hidden";})
 5        //选择所有的select并设置为隐藏
 6         $("#coverLayer").fadeTo("fast",0.5,function(){$("#coverLayer").css("display","block");})
 7                        .width(Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth))
 8                        .height(Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight));
 9        //显示覆盖层 并设置其高和宽
10        $("#lightBox").show();
11        //显示LightBox层
12    }

13</script>

     关于遮盖层的高和宽的设置cloudgamer已经有很详细的讲述了。我也是参考他的做法。这里就不多讲了。有兴趣大家可以直接去看:http://www.cnblogs.com/cloudgamer/archive/2008/09/15/1290954.html

     再是discover();故名思议,这个函数的功能刚好和上一个相反,所以只需要把两个层设成影藏并重新显示刚才影藏的select就可以了。

1    function discover()
2    {
3        $("select").each(function(){this.style.visibility="visible";})
4        $("#coverLayer").fadeOut("normal",function(){$("#coverLayer").css("display","none");})
5        $("#lightBox").fadeOut("normal",function(){$("#lightBox").css("display","none");})
6    }

7
8

 

 

     到这里LightBox效果就基本实现了。接下来要做的是用Ajax让LightBox和服务端通信。首先创建XMLHttpRequest对象:

 1var xmlHttp;
 2
 3function CreateXMLHttpRequest()
 4{
 5    if (window.ActiveXObject)
 6    {
 7        xmlHttp=new ActivXObjct("Microsoft.XMLHTTP");
 8    }

 9    else
10        if (window.XMLHttpRequest)
11        {
12            xmlHttp=new XMLHttpRequest();
13        }

14        
15}

16

    这里的变量xmlHttp是全局的,下面还会再用到。因为IE和其他标准浏览器的XMLHttpRequest对象不一样,所以我用了两个条件判断来兼容IE。

     创建了XMLHttpRequest对象以后就要用它向服务端发送请求了

 1function Login()
 2{
 3    CreateXMLHttpRequest();
 4
 5    var strUserName=$("#tbUserName").val();
 6    var strPassword=$("#tbPassword").val();
 7    var url="/test/Ajax.aspx?userName="+strUserName+"&password="+strPassword;
 8    xmlHttp.open("GET",url,true);                
 9    xmlHttp.onreadystatechange=CallBack;
10    xmlHttp.send(null);
11}

12

     首先获取表单上的值,并根据获得的值构造请求用的url。再用get方法发送请求。这些似乎都是Ajax的标准代码了。发送了请求我们再看服务器要怎么响应了。

 1    protected void Page_Load(object sender, EventArgs e)
 2    {
 3        //string strAction = Request.QueryString["action"];
 4        //switch (strAction)
 5        //{
 6        //    case login:
 7        //        {
 8        string strUserName = Request.QueryString["userName"];
 9        string strPassword = Request.QueryString["password"];
10        LoginCallBack(strUserName,strPassword);
11                    //break;
12        //        }
13        //}
14
15
16    }

17
18    protected void LoginCallBack(string strUserName,string strPassword)
19    {
20        string strCommandText = "Select * FROM admin Where name='" + strUserName + "' AND password='" + strPassword + "'";
21        SQLConnection myConnection = new SQLConnection(strCONNECTION_STRING);
22        SQLCommand myCommand = new SqlCommand(strCommandText, myConnection);
23        SqlDataReader myReader;
24        myConnection.Open();
25        myReader = myCommand.ExecuteReader();
26        if (myReader.Read())
27        {
28            Session["UserName"= myReader.GetString(1);
29            Session["Password"= myReader.GetString(2);
30            Response.ContentType = "text/plain";
31            Response.AppendHeader("Cache-Control""no-cache");
32            Response.AppendHeader("Pragma""no-cache");
33            Response.Write("欢迎:" + Session["UserName"]);
34            Response.End();
35        }

36        else
37        {
38            Response.Write("登录失败");
39        }

40    }

41}
42

     第一步当然是获得查询参数。之后再作为函数的参数用函数对其进行处理。我这里是模仿了一个登录。至于怎么样从数据库中查出数据以及查出什么数据不是我们这篇文章要讨论的问题。这里不多说。关键是下面几句

1             Response.ContentType = "text/plain";
2             Response.AppendHeader("Cache-Control""no-cache");
3             Response.AppendHeader("Pragma""no-cache");
4             Response.Write("欢迎:" + Session["UserName"]);
5             Response.End();
6 

     第一句是设置XMLHttpRequest对象返回的类型。我这里设置的是纯文本。也可以设置成“text/xml”。这样返回的就是 xml类型的。在客户端可以用DOM获取里面的内容。接下去的两句是设置首部,使浏览器不会在本地缓存结果。之所以要设置两个是为了向后兼容。然后那句大 家就应该很熟悉了——输出内容。最后是关闭或者说结束response对象。

     现在服务端已经返回数据了,看在客户端的接收。

 1function CallBack()
 2{
 3    if(xmlHttp.readyState==4)
 4    {
 5        if(xmlHttp.status==200)
 6        {
 7            var strMeesage=xmlHttp.responseText;
 8            InsertMessage(strMeesage);
 9Tag标签: jQuery,JavaScript,C#,LightBox,Ajax

posted @ 2009-03-04 22:34 咖啡不苦

[C#]CodeFx:一站式微软开发技术解决方案

mikel阅读(888)

转载:http://www.cnblogs.com/phinecos/archive/2009/03/04/1403450.html    
晚上在博客堂读完VSTO
写的一篇文章,介绍了CodePlex上面的一个项目,叫做All-In-One Code Framework,代号CodeFx简单的说,就是收集了几乎所有常见的微软开发技术的示例项目,将其打包到这个框架里,而且还使用多各种不同的语言进行实现。比如创建一个ActiveX控件和COM组件,CodeFx里面使用ATLMFCVBC#来实现同样的功能。

适合新手入门,也可以作为一份模板供经验丰富的开发者使用,可节省大量的时间。官方网站上给出了框架的基本结构,如下图所示:

COM组件和ActiveX控件示例

 

数据访问示例

库示例

进程间通信示例 

 

花了2个小时粗略阅读了代码,记录下学习心得:

1)先来说ActiveX这条线,它里面使用了ATL(这里有2种实现,进程内和进程外)MFC,C#,VB四种技术来实现。功能就是四点:一个返回字符串的HelloWorld方法,一个float类型的属性FloatProperty,一个返回进程号和线程号的GetProcessThreadID方法,一个FloatPropertyChanging事件。

2)授权支持是 ActiveX 控件的一项可选功能,它使您得以控制能使用或分发该控件的人。(请参见MFC ActiveX 控件:授权 ActiveX 控件》)。

头文件的修改     

“ActiveX 控件向导将下列代码放置在控件头文件中。声明了 factory 对象的两个成员函数,其中一个成员函数验证控件 .LIC 文件是否存在,而另一个成员函数则对包含该控件的应用程序中使用的许可证密钥进行检索:

    BEGIN_OLEFACTORY(CMFCActiveXCtrl)        // Class factory and guid
        virtual BOOL VerifyUserLicense();
        
virtual BOOL GetLicenseKey(DWORD, BSTR FAR*);
    END_OLEFACTORY(CMFCActiveXCtrl)

 

实现文件的修改

“ActiveX 控件向导将下面两条语句放置在控件实现文件中,以声明许可文件名和许可字符串:

static const TCHAR BASED_CODE _szLicFileName[] = 
   _T(
"License.lic");
static const WCHAR BASED_CODE _szLicString[] =
   L
"Copyright (c) 2000 ";

注意:如果以任何方式修改 szLicString,则必须也修改控件 .LIC 文件的第一行,否则授权将无法正确运行。
“ActiveX 控件向导”将下列代码放置在控件实现文件中,以定义控件类的 VerifyUserLicense 函数和 GetLicenseKey 函数:

// CMFCActiveXCtrl::CMFCActiveXCtrlFactory::VerifyUserLicense –
// Checks for existence of a user license

BOOL CMFCActiveXCtrl::CMFCActiveXCtrlFactory::VerifyUserLicense()
{
    
return AfxVerifyLicFile(AfxGetInstanceHandle(), _szLicFileName,
        _szLicString);
}
// CMFCActiveXCtrl::CMFCActiveXCtrlFactory::GetLicenseKey –
// Returns a runtime licensing key
BOOL CMFCActiveXCtrl::CMFCActiveXCtrlFactory::GetLicenseKey(DWORD dwReserved,
    BSTR FAR
* pbstrKey)
{
    
if (pbstrKey == NULL)
        
return FALSE;
    
*pbstrKey = SysAllocString(_szLicString);
    
return (*pbstrKey != NULL);
}

最后,“ActiveX 控件向导修改控件项目 .IDL 文件。将关键字 licensed 添加到控件的 coclass 声明中,如下例所示:

    [ uuid(E389AD6C4FB647AFB03AA5A5C6B2B820), licensed,
      helpstring(
"MFCActiveX Control"), control ]
    coclass MFCActiveX

  3)作者封装了一个方法AutoWrap来调用COM组件公开出来的属性或方法。

HRESULT AutoWrap(int autoType, VARIANT *pvResult, IDispatch *pDisp, 
                 LPOLESTR ptName, 
int cArgs
{
    
// Begin variable-argument list
    va_list marker;
    va_start(marker, cArgs);
    
if (!pDisp) 
    {
        _putts(_T(
"NULL IDispatch passed to AutoWrap()"));
        _exit(
0);
    }
    
// Variables used
    DISPPARAMS dp = { NULL, NULL, 00 };
    DISPID dispidNamed 
= DISPID_PROPERTYPUT;
    DISPID dispID;
    HRESULT hr;
    
char szName[200];
    
    
// Convert down to ANSI
    WideCharToMultiByte(CP_ACP, 0, ptName, 1, szName, 256, NULL, NULL);
    
    
// Get DISPID for name passed
    hr = pDisp->GetIDsOfNames(IID_NULL, &ptName, 1, LOCALE_USER_DEFAULT,
        
&dispID);
    
if (FAILED(hr))
    {
        _tprintf(_T(
            
"IDispatch::GetIDsOfNames(\"%s\") failed w/err 0x%08lx\n"
            ), szName, hr);
        
return hr;
    }
    
    
// Allocate memory for arguments
    VARIANT *pArgs = new VARIANT[cArgs+1];
    
// Extract arguments
    for(int i=0; i<cArgs; i++
    {
        pArgs[i] 
= va_arg(marker, VARIANT);
    }
    
    
// Build DISPPARAMS
    dp.cArgs = cArgs;
    dp.rgvarg 
= pArgs;
    
    
// Handle special-case for property-puts
    if (autoType & DISPATCH_PROPERTYPUT)
    {
        dp.cNamedArgs 
= 1;
        dp.rgdispidNamedArgs 
= &dispidNamed;
    }
    
    
// Make the call
    hr = pDisp->Invoke(dispID, IID_NULL, LOCALE_SYSTEM_DEFAULT,
        autoType, 
&dp, pvResult, NULL, NULL);
    
if (FAILED(hr)) 
    {
        _tprintf(_T(
            
"IDispatch::Invoke(\"%s\"=%08lx) failed w/err 0x%08lx\n"
            ), szName, dispID, hr);
        
return hr;
    }
    
// End variable-argument section
    va_end(marker);
    
    delete[] pArgs;
    
    
return hr;
}

4)DLL的延迟加载使得我们不需要使用LoadLibrary和GetProcAddress。这样的好处是直到程序调用DLL中的函数时才加载此DLL。

#include <Delayimp.h>

  卸载延迟加载的DLL的代码:

    PCSTR pszDll = "CppDllExport.dll";
    _tprintf(_T(
"__FUnloadDelayLoadedDLL2 => %d\n"),
    __FUnloadDelayLoadedDLL2(pszDll));

posted on 2009-03-04 23:15 Phinecos(洞庭散人)

[C#]请别埋没了URL Routing

mikel阅读(720)

转载:http://www.cnblogs.com/JeffreyZhao/archive/2009/03/05/fully-leverage-url-routing.html

实现分析

  既然Model Binder机制有着明显的缺陷,那么我们又该如何处理这样的问题呢?

   我们再来回顾一下目前问题:对于从URL中表现出来的参数,我们可以把URL Routing捕获到的数据使用Model Binder进行转化(例如上例中的DateTimeModelBinder);但是如果我们在生成URL时直接提供复杂参数,则框架只会把它简单的 ToString后放入URL。这是因为那些与URL有关的HTML Helper会将数据交给URL Ruoting组件来生成URL,而Route规则在生成URL时不知道一个复杂对象该如何转变为URL,因此……

  慢着,你刚才说, 把数据“交给URL Routing组件来生成URL”?URL Routing不是解析URL用的吗?为什么还负责“生成”URL?没错,与Model Binder不同,URL Routing的工作职责是“双向”的。它既负责从URL中提取RouteData,也负责根据Route生成一个URL——可惜微软没有对URL Routing给出足够的资料,有相当多的朋友没有意识到这一点。

  可恶的微软。

  既然问题的原因是Model Binder的“单向性”,那么如果存在一个“双向”的Model Binder就应该可以解决问题。例如,我们可以继承现有的IModelBinder接口进行扩展,那么至少从解析URL到执行Action方法这个流程 中所有的功能都不需要任何额外工作。可惜,这种做法对于大多数HTML Helper来说,我们就必须定义新的扩展,才能利用所谓的“双向Model Binder”。不过其实我们可以有更好的解决方案——成本低廉,通用性强。既然上次提到了传说中的“Model Binder强迫症”,那么我们现在就把目光移到Model Binder以外的地方。

  您一定已经猜到我们要从哪里入手了。没错,就是URL Routing。关于这方面,大名鼎鼎的Scott Hanselman同 学提出将DateTime类型进行分割,也就是将一个DateTime切成年、月、日多个部分进行表示。这个做法老赵颇不赞同,无论从易用性还是通用性等 角度来看,这种做法都是下下之策。说实话,这样的做法其实并没有跳出框架既有功能给定的圈子,它只是通过“迎合框架”来满足自己的需求,而不是让框架为我 们的需求服务。

  那么,我们来分析一下URL Routing组件的运作方式吧,这是必要的预备工作:

  • 首 先,应用程序为RouteCollection类型的RouteTable.Routes集合添加一些Route规则,每个规则即为一个 RouteBase对象。RouteBase是一个抽象类型,其中包含两个抽象方法,GetRouteData和GetVirtualPath。
  • 在 捕获URL中数据的时候,URL Routing组件将调用RouteTable.Routes.GetRouteData方法来获得一个RouteData对象。简单来说,它会依次调用 每个RouteBase对象的GetRouteData方法,直到得到第一个不为null的RouteData对象。
  • 在生成URL 时,URL Routing组件将调用RouteTable.Routes.GetVirtualPath方法来获得一个VirtualPathData对象。简单来 说,它会依次调用每个RouteBase对象的GetVirtualPath方法,直到得到第一个不为null的VirualPathData对象。

   显然,光有RouteBase抽象类型是不足以提供任何有用功能的。因此URL Routing框架还提供了一个具体的Route类型供大家使用。说起Route类,它的功能可谓非常强大。我们在使用ASP.NET MVC框架时用到的MapRoute方法,其实就是在向RouteTable.Routes集合中添加Route对象。而其中的URL占位符,默认值,约 束等功能,实际上完全由Route对象实现了。多么强大的Route类型!如果想要写一个足以匹敌,并且包含额外功能的RouteBase实现可不是一件 容易的事情。幸好我们生活在面向对象的美好世界中,“复用”是我们手中威力非凡的利器。如果我们基于现有的Route类型进行扩展,那么大部分的工作我们 弹指间便可完成。

  现有的Route只能从URL中提取字符串类型的数据,同时也只能把任何对象作为字符串来生成URL。而我们将要构 造RouteBase实现,就要弥补这一缺陷,让Route规则能够直接从URL中提取出复杂对象,并且知道如何将一个复杂对象转化为一个URL。有了前 者,RouteData就能包含复杂类型的对象,以此应对Action方法的参数自然不是问题;有了后者,我们只需要提供一个强类型的复杂对 象,Route规则也能顺利地将其转化为可以识别的URL——多么美好。

Route Formatter

  那么解析字符串,或生成URL的职责由谁来完成呢?于是我们定义一个IRouteFormatter来负责这件事情:

public interface IRouteFormatter
{
bool TryParse(object value, out object output);
bool TryToString(object value, out string output);
}

  TryParse方法负责将一个对象转化为我们需要的复杂类型对象,而TryToString则将一个复杂类型对象转化为字符串(即URL)。 两个方法都返回一个布尔值,以表示这次转化是否合法。您可能会发现,TryToString输出的是一个string,而TryParse……他接受的是 一个object类型的参数,这是怎么回事呢?原因在于Route规则中的“默认值”设置。在Route规则中我们可以为RouteData中的某个“字 段”设定默认值,这样即使URL中无法捕获到这个字段,它也可以出现在RouteData中。从URL中捕获得到的自然是一个字符串,但是默认值则可以设 为任意类型的对象。因此Formatter需要可以接受一个object参数,并设法将其转化为我们需要的复杂类型。

  是不是有点绕?请继续看下去,您会了解它的作用的。虽说TryParse需要接受一个object参数,但是在大多数情况下,我们更多是要处理强类型。因此我们不妨再定一个RouteFormatter抽象类,方便强类型IRouteFormatter对象的编写:

public abstract class RouteFormatter<T> : IRouteFormatter
{
public abstract bool TryParse(string value, out T output);
public abstract bool TryToString(T value, out string output);
bool IRouteFormatter.TryParse(object value, out object output)
{
if (value is T)
{
output = value;
return true;
}
string s = value as string;
if (s == null)
{
output = null;
return false;
}
else
{
T t;
var result = this.TryParse(s, out t);
output = t;
return result;
}
}
bool IRouteFormatter.TryToString(object value, out string output)
{
if (value is T)
{
return this.TryToString((T)value, out output);
}
else
{
output = null;
return false;
}
}
}

  RouteFormater<>类接受一个范型参数,并且准备两个强类型的抽象方法让子类实现。至于接口中的两个类型,它们会处理 一部分逻辑——主要是类型判断——只在合适的时候将操作交给范型方法来实现。TryToString方法朴实无华,而TryParse方法相对较为有趣, 它会首先判断value参数的类型,如果已经符合当前的范型类型,则直接将其转化后返回。这就是为了“默认值”而进行的处理,例如用户准备了一个 DateTime类型的默认值,并被Route规则采纳了,则我们的RouteFormatter<DateTime>就会将其直接返回,不 做任何转化。

  为了解决目前提出的问题,我们会编写一个DateTimeFormatter,它接受一个Format参数表示日期的格式:

public class DateTimeFormatter : RouteFormatter<DateTime>
{
public string Format { get; private set; }
public DateTimeFormatter(string format)
{
this.Format = format;
}
public override bool TryParse(string value, out DateTime output)
{
return DateTime.TryParseExact(value, this.Format, null, DateTimeStyles.None, out output);
}
public override bool TryToString(DateTime value, out string output)
{
output =  value.ToString(this.Format);
return true;
}
}

  那么有没有某个Route Formatter需要直接实现IRouteFormatter接口呢?有。之前提到TryParse方法将在value参数符合范型T的情况下直接返回 “通过”,如果某个Route Formatter不支持这条判断,则自然无法继承于RouteFormatter<>类型。例如下面的RegexFormatter,将使 用正则表达式对某个字段的值进行约束。在我们的RouteBase实现中,RegexFormatter便是Route类中“约束”功能的替代品。如下:

public class RegexFormatter : IRouteFormatter
{
public Regex Regex { get; private set; }
public RegexFormatter(string pattern)
{
this.Regex = new Regex(pattern,
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
public bool TryParse(object value, out object output)
{
string s;
bool result = this.Try(value, out s);
output = s;
return result;
}
public bool TryToString(object value, out string output)
{
return this.Try(value, out output);
}
private bool Try(object value, out string output)
{
var s = value as string;
if (s != null && this.Regex.IsMatch(s))
{
output = s;
return true;
}
else
{
output = null;
return false;
}
}
}

  RegexFormatter的关键在于Try方法。Try方法首先判断value参数是否为一个字符串,如果是,则使用正则表达式进行验证。当且仅当value为字符串并满足指定的正则表达式时,RegexFormatter才表示“通过”。

FormatRoute实现

  FormatRoute便是我们RouteBase抽象类的实现,它提供了Route类的所有功能,并可以为每个字段设置一个Route Formatter对象,以此对这个字段进行转换或约束。之前提到,我们会将主要功能委托给现有Route类型,这样可以大大简化我们的工作量。因此,我 们会在FormatRoute中包含一个Route类型的对象,此外还会保留所有字段与其Route Formatter的映射关系。请看如下构造函数:

public class FormatRoute : RouteBase
{
private Route m_route;
private IDictionary<string, IRouteFormatter> m_formatters;
public FormatRoute(
string url,
RouteValueDictionary defaults,
IDictionary<string, IRouteFormatter> formatters,
RouteValueDictionary constaints,
RouteValueDictionary dataTokens,
IRouteHandler routeHandler)
{
this.m_formatters = formatters;
this.m_route = new Route(
url,
defaults,
constaints,
dataTokens,
routeHandler);
}
...
}

  RouteBase的关键方法便是GetRouteData和GetVirtualPath。有了Route类型的辅助,这两个方法其实非常简单。如下:

public override RouteData GetRouteData(HttpContextBase httpContext)
{
var result = this.m_route.GetRouteData(httpContext);
if (result == null) return null;
var valuesModified = new Dictionary<string, object>();
foreach (var pair in result.Values)
{
var key = pair.Key;
IRouteFormatter formatter = null;
if (this.m_formatters.TryGetValue(key, out formatter))
{
object o;
if (formatter.TryParse(pair.Value, out o))
{
valuesModified[key] = o;
}
else
{
return null;
}
}
}
foreach (var pair in valuesModified)
{
result.Values[pair.Key] = pair.Value;
}
return result;
}
public override VirtualPathData GetVirtualPath(
RequestContext requestContext, RouteValueDictionary values)
{
var routeValues = new RouteValueDictionary();
foreach (var pair in values)
{
var key = pair.Key;
IRouteFormatter formatter = null;
if (this.m_formatters.TryGetValue(key, out formatter))
{
string s;
if (formatter.TryToString(pair.Value, out s))
{
routeValues[key] = s;
}
else
{
return null;
}
}
else
{
routeValues[key] = pair.Value;
}
}
return this.m_route.GetVirtualPath(requestContext, routeValues);
}

  GetRouteData会接受一个HttpContextBase对象,并调用Route对象的GetRouteData方法获取一个 RouteData对象。如果RouteData不为null,则遍历其中的所有字段,如果指定了对应的Route Formater,则还需要通过Route Formatter的检验及转化——没错,经历了Route Formatter之后的RouteData中已经包含了强类型对象。而GetVirtualPath方法则略有不同,它首先遍历values参数中的所 有字段,将其中的强类型对象转化为字符串,也就是URL片段,这样交给Route对象来生成VirtualPathData时,便可以得到正确的URL 了。

  最后便是FormatRoute的运用:

routes.Add(
"Demo.Date",
new FormatRoute(
"{controller}/{action}/{date}",
new RouteValueDictionary(), // defaults
new Dictionary<string, IRouteFormatter>
{
{"controller", new RegexFormatter("Demo")},
{"action", new RegexFormatter("Date")},
{"date", new DateTimeFormatter("yyyy-MM-dd")}
},
new RouteValueDictionary(), // constaints
new RouteValueDictionary(), // data tokens
new MvcRouteHandler()));

  除了为date字段指定了转化用的DateTimeFormatter之外,我们也为controller和action字段提供了负责约束的 RegexFormatter——这点只是为了演示。更好的做法是直接将URL设为Demo/Date/{date},并在默认值中指定 controller和action的值。此外,您也可以使用传统的方式为字段提供约束,而不是使用RegexFormatter。当然,效果几乎可以说 是一模一样的。

总结

  现在我们完美地解决了之前提出的问题。使用FormatRoute可以轻松地处理URL中特定类型对象的提取,并且可以把特定类型的对象转化为 URL的片段。除了日期时间之外,我们还可以转化语言文化,查询条件等任意复杂类型。而RouteFormatter对象与Route规则的分离,使得我 们可以对RouteFormatter进行独立的单元测试,这也是一件十分理想的事情。这下在视图中,无论是指定Route Values,还是使用强类型的方式,我们都可以正确获得所需的URL了。如下:

<%= Html.ActionLink("Yesterday", "Date", new { date = date.AddDays(-1) }) %>
<span><%= date.ToShortDateString() %></span>
<%= Html.ActionLink<DemoController>(c => c.Date(date.AddDays(1)), "Tomorrow") %>

  那么,从设计上讲,把数据的提取转移到URL Routing上是否合适呢?答案是肯定的。因为URL Routing的职责原本就是从URL中提取数据——任意类型的数据,以及把数据转化为URL,我们现在只是充分利用了URL Routing的功能而已。事实上,我建议任何使用URL表示的数据,都把转化的职责转移到URL Routing这一层,因为这时我们基本上无可避免地需要根据数据来生成URL。一般情况下,我们要尽可能地使用强类型数据。那么Model Binder难道就没有用了吗?当然不是。URL Routing负责从URL中提取数据,而Model Binder则用于从其他方面来获取参数。例如POST来的数据,例如《最佳实践》中的Url Referrer参数。

  打开视野,发挥程序员的敏捷思路,生活就会变得更加美好。

[SQL]通过 SQL Server 2005 索引视图提高性能

mikel阅读(904)

什么是索引视图?

多年以来,Microsoft® SQL Server™ 一直支持创建称为视图的虚拟表。通常,这些视图的主要作用是:

提供一种安全机制,将用户限制到一个或多个基表的某个数据子集中。

提供一种机制,允许开发人员自定义用户通过逻辑方式查看存储在基表中的数据的方式。

通 过 SQL Server 2000,SQL Server 视图的功能得到了扩展,实现了系统性能方面的收益。可在视图上创建唯一的聚集索引及非聚集索引,来提高最复杂的查询的数据访问性能。在 SQL Server 2000 和 2005 中,具有唯一的聚集索引的视图即为索引视图。本文所讨论的内容适用于 SQL Server 2005,其中有许多内容也适用于 SQL Server 2000。

从数据库管理系统 (DBMS) 的角度看来,视图是对数据(一种元数据类型)的一种描述。当创建了一个典型视图时,通过封装一个 Select 语句(定义一个结果集来表示为虚拟表)来定义元数据。当在另一个查询的 FROM 子句中引用视图时,将从系统目录检索该元数据,并替代该视图的引用扩展元数据。视图扩展之后,SQL Server 查询优化器会为执行查询编译一个执行计划。查询优化器会搜索针对某个查询的一组可能的执行计划,并根据对执行每个查询计划所需的实际时间的估计,选择所能 找到的成本最低的计划。

对于非索引视图,解析查询所必需的视图部分会在运行时被具体化。任何计算(比如:联接或聚合)都在每个引用视图的查询执行时完成1。在视图上创建了唯一的聚集索引后,该视图的结果集随即被具体化,并保存在数据库的物理存储中,从而在执行时节省了执行这一高成本操作的开销。

在 查询执行中,可通过两种方式使用索引视图。查询可直接引用索引视图,或者更重要的是,如果查询优化器确定该视图可替换成本最低的查询计划中的部分或全部查 询,那么就可以选定它。在第二种情况中,使用索引视图替代基础表及其一般索引。不必在查询中引用视图以使查询优化器在查询执行时使用该视图。这使得现有的 应用程序可以从新创建的索引视图中受益,而不必进行更改。

注意 索引视图是 SQL Server 2000 和 2005 各版本的一个功能。在 SQL Server 2000 和 2005 的 Developer 和 Enterprise 版本中,查询处理器可使用索引视图来解析结构上与该视图相匹配的查询,即便不按名称来引用视图。在其他版本中,必须按名称来引用视图,并对视图引用使用 NOEXPAND 提示来查询索引视图的内容。

通过索引视图改善性能

运用索引提高查询性能不算是一个新概念;但是,索引视图提供了一些借助标准索引无法取得的性能收益。索引视图可通过以下方式提高查询性能:

可预先计算聚合并将其保存在索引中,从而在查询执行时,最小化高成本的计算。

可预先联接各个表并保存最终获得的数据集。

可保存联接或聚合的组合。

该图说明了当查询优化器使用索引视图时,通常所能取得的性能改进。所列举的查询在复杂性上有所不同(比如:聚合计算的数量、所用表的数量或谓词的数量)并包含来自真实的生产环境的具有数百万行的表。

IPSQL501.gif

在视图上使用非聚集索引

其 次,视图上的非聚集索引可提供更好的查询性能。与表上的非聚集索引类似,视图上的非聚集索引可提供更多选项,供查询优化器在编译过程中选择。例如,如果查 询包含聚集索引所未涉及的列,那么优化器可在计划中选择一个或多个辅助索引,避免对索引视图或基表进行费时的完全扫描。

对架构添加索引会增加数据库的开销,因为索引需要持续的维护。在索引数量和维护开销间寻求适当的平衡点时,应谨慎权衡。

返回页首

应用索引视图的优点

在 实施索引视图前,分析数据库工作负荷。运用查询及各种相关工具(比如:SQL Profiler)方面的知识来确定可从索引视图获益的查询。频繁发生聚合和联接的情况最适合使用索引视图。无论是否频繁发生,只要某个查询需要很长的响 应时间,同时快速获得响应的开销很高,那么就适合使用索引视图。例如,一些开发人员发现为高级主管们在月末运行的报告,创建预先计算和存储查询的应答的索 引视图很有用。

不是所有的查询都能从索引视图中获益。与一般索引类似,如果未使用索引视图,就无法从中受益。在这种情况下,不仅无法实 现性能改善,而且会在磁盘空间、维护和优化方面产生额外的成本。然而,当使用索引视图时,可大大改善(在数量级上)数据访问。这是因为查询优化器使用存储 在索引视图(大幅降低了查询执行的成本)中预先计算的结果。

查询优化器仅考虑对具有高成本的查询使用索引视图。从而避免出现这样的情况:在查询优化成本高于使用索引视图所节约的成本时尝试匹配各种索引视图。在成本少于 1 的查询中很好使用索引视图。

从实施索引视图中获益的应用程序包括:

决策支持工作负荷

数据集市

数据仓库

联机分析处理 (OLAP) 存储和源

数据挖掘工作负荷

从查询类型和模式方面来看,获益的应用程序一般包含:

大型表的联接和聚合

查询的重复模式

几组相同或重叠的列上的重复聚合

相同键上相同表的重复联接

以上各项的组合

相反,执行许多写入操作的联机事务处理 (OLTP) 系统或者频繁更新的数据库应用程序可能无法运用索引视图,因为同时更新视图和底层基表会带来更高的维护成本。

查询优化器如何使用索引视图

SQL Server 查询优化器自动决定何时对给定的查询执行使用索引视图。不必在查询中直接引用视图以供优化器在查询执行计划中使用。所以,现有的应用程序可运用索引视图,而不用更改应用程序本身;只是必须创建索引视图。

优化器考虑事项

查询优化器通过考虑几个条件来决定索引视图能否涵盖整个或部分查询。这些条件对应查询中的一个 FROM 子句并由下列这几个部分组成:

查询 FROM 子句中的表必须是索引视图 FROM 子句中的表的超集。

查询中的联接条件必须是视图中的联接条件的超集。

查询中的聚合列必须可从视图中的聚合列的子集派生。

查询选择列表中的所有表达式必须可从视图选择列表或未包含在视图定义中的表派生。

如果与其他谓词所匹配的行的超集相匹配,那么该谓词将归入另一个谓词。例如,“T.a=10”归入“T.a=10 and T.b=20”。任何谓词都可归入其自身。视图中限制表值的那部分谓词必须归入查询中限制相同表的那部分谓词。此外,必须以 SQL Server 可验证的方式实现这一点。

属于视图定义中的表的查询搜索条件谓词的所有列必须出现在下列视图定义的一项或多项中:

1.

一个 GROUP BY 列表。

2.

视图选择列表(如不存在 GROUP BY)。

3.

视图定义中相同或等价的谓词。

情况 (1) 和 (2) 允许 SQL Server 对视图的列应用查询谓词,以便进一步限制视图的列。情况 (3) 比较特殊。在这种情况下,不需要对列进行筛选,因此该列不必出现在视图中。

如果查询不止包含一个 FROM 子句(子查询、派生表、UNION),优化器可能选择几个索引视图来处理查询,并将它们应用到不同 FROM 子句。2

本文档的末尾提供了涉及这些情况的具体查询。推荐的最佳实务是让查询优化器决定在查询执行计划中使用哪些索引(如果有的话)。

 

使用 NOEXPAND 视图提示

当 SQL Server 处理按名称引用视图的查询时,视图的定义只有在仅引用基表时才会被正常扩展。这个过程称为视图扩展。其属于一种宏扩展形式。

NOEXPAND 视图提示可强制查询优化器将视图视为带有聚集索引的普通表。其可防止视图扩展。只有在 FROM 子句中直接引用索引视图,才会应用 NOEXPAND 提示。例如,

Select Column1, Column2, ...FROM Table1, View1 WITH (NOEXPAND) Where ...

如要确保让 SQL Server 通过自己读取视图而不是从基表读取数据来处理查询,那么可使用 NOEXPAND。如果出于某种原因,SQL Server 选择了一个查询计划来对基表处理查询,而您想让其使用视图,那么可以考虑使用 NOEXPAND。必须在除 Developer 和 Enterprise 版本外的 SQL Server 的所有版本中使用 NOEXPAND 来让 SQL Server 直接对索引视图处理查询。可以看到 SQL Server 为计划的图形表达式选择了一个使用 SQL Server Management Studio 工具的显示预计的执行计划功能的语句。或者,可以看到使用 SHOWPLAN_ALL、SHOWPLAN_TEXT 或 SHOWPLAN_XML 的不同的非图形表达式。参阅 SQL Sever 联机丛书中有关 SHOWPLAN 的不同版本的相关讨论。

使用 EXPAND VIEWS 查询提示

处理按名称引用视图的查询时,除非对视图引用添加 NOEXPAND 提示,否则 SQL Server 总会扩展视图。该提示会尝试匹配索引视图和扩展查询,除非在查询末尾的一个 OPTION 子句中指定 EXPAND VIEWS 查询提示。例如,假设数据库中有一个索引视图 View1。在下方的查询中,根据其逻辑定义(其 Create VIEW 语句)对 View1 进行了扩展,然后 EXPAND VIEWS 选项会阻止在计划中使用 View1 的索引视图来解析该查询。

Select Column1, Column2, ... FROM Table1, View1 Where ...
OPTION (EXPAND VIEWS)

如要确保让 SQL Server 通过从查询所引用的基表直接访问数据来处理该查询,而不必访问索引视图,那么可使用 EXPAND VIEWS。在某些情况下,EXPAND 视图有助于消除因使用索引视图而导致的锁争用。在测试应用程序时,NOEXPAND 和 EXPAND VIEWS 都可帮助用户在使用和不使用索引视图的情况下进行性能评估。

返回页首

SQL Server 2005 的索引视图有哪些新增功能?

与 SQL Server 2000 相比,SQL Server 2005 包含了许多索引视图的改进功能。可索引的视图组已扩展至包含基于下列各项的视图:

标量聚合,包括 SUM 和不带 GROUP BY 的 COUNT_BIG。

标量表达式和用户定义的功能 (UDFs)。例如,给定一个表 T(a int, b int, c int) 和一个标量 UDF dbo.MyUDF(@x int),T 上定义的索引视图可包含一个计算列(比如:a+b 或 dbo.MyUDF(a))。

不精确的永久性列。不精确的列是一种浮型或实型的列,或者是一种派生自浮型或实型列的计算列。在 SQL Server 2000 中,如果不属于索引键的一部分,不精确的列就可用于索引视图的选择列表。不精确的列不能用于视图定义中的其他地方(比如:Where 或 FROM 子句)。如果不精确的列永久保存在基表中,那么 SQL Server 2005 允许其加入键或视图定义。永久性列包含常规列和标记为 PERSISTED 的计算列。

不精确的非永久性列无法加入索引或索引视图的根本原因是:必须使数据库脱离原计算机,然后再附加到另一台计算机。完成转移之后,保存在索引 或索引视图中的所有计算列值在新硬件上的派生方式必须与旧硬件完全相同,精确到每个位。否则,这些索引视图在新硬件上会遭到逻辑破坏。由于这种破坏,在新 硬件上,针对索引视图的查询会根据计划是否使用了索引视图或基表来派生视图数据,返回不同的应答。此外,无法在新计算机上正常维护索引视图。可惜,不同计 算机上的浮点硬件(即便采用相同制造商的相同处理器体系结构)在处理器的版本上并不总是完全相同。对于某些浮点值 a 和 b,固件升级可能导致新硬件上的 (a*b) 不同于旧硬件上的 (a*b)。例如,结果可能非常相近,但仍存在细微差别。在进行索引之前一直保留不精确的计算值可解决这种分离/附加的不一致性问题,因为在进行索引和索引视图的数据库更新和维护期间,在相同的计算机上评估了所有表达式。

通用语言运行时 (CLR) 类型。SQL Server 2005 的一个主要的新功能是支持基于 CLR 的用户定义的类型 (UDT) 和 UDF。假如列或表达式具有确定性或是永久且精确的,或者二者兼具,那么就可在 CLR UDT 列或从这些列派生而来的表达式上定义索引视图。不能在索引视图上使用 CLR 用户定义的聚合。

优化器匹配查询和索引视图(使之可在查询计划中使用)的功能经扩展包含:

新的表达式类型,位于查询或视图的 Select 列表或条件中,涉及:

标量表达式(比如 (a+b)/2)。

标量聚合。

标量 UDF。

间隔归入。优化器可检测索引视图定义中的间隔条件是否覆盖或“归入”查询中的间隔条件。例如,优化器可确定“a>10 and a<20”覆盖“a>12 and a<18”。

表达式等价。某些表达式虽然在语法上有所不同,但最终的结果却相同,那么可以将其视为等价。例如,“a=b and c<>10”与“10<>c and b=a”等价。

另外,如果数据库中存在大量索引视图,那么对比在其上定义视图的表的编译性能,SQL Server 2005 通常要比 SQL Server 2000 快很多。.

返回页首

设计注意事项

对数据库系统确定一组适当的索引可能很复杂。如果在设计一般索引时需要考虑众多可能性,那么对架构添加索引视图会大幅提高设计和潜在结果的复杂性。例如,索引视图可用于:

查询中引用的表的任何子集。

该表子集的查询中的条件的任何子集。

组合的列。

聚合函数(比如:SUM)。

应同时设计表和索引视图上的索引,以便从每个构造中获得最佳结果。由于索引和索引视图对给定查询可能都很有用,因此分开设计会导致多余的建 议,从而产生较高的存储和维护开销。优化数据库的物理设计时,必须权衡一组不同的查询和数据库系统必须支持的更新的性能要求。所以,对索引视图确定一项适 当的物理设计是一种富有挑战性的任务,应尽可能使用数据库优化顾问 (Database Tuning Advisor)。

如果为建立一个特殊的查询,查询优化器考虑了许多索引视图,那么查询优化成本就会显著增加。查询优化器可能会考虑在查询中的表的任何子集上定义的所有索引视图。在拒绝视图之前,必须调查每个视图以便进行替换。这可能要花一些时间,尤其当给定查询存在数百个这类视图时。

在其上创建一个唯一的聚集索引之前,视图必须满足几项要求。在设计阶段,考虑这些要求:

视图以及视图中引用的所有表必须在相同的数据库中,并具有相同的所有者。

索引视图不必包含查询中引用的供优化器使用的所有表。

在视图上创建任何其他的索引之前,必须先创建一个唯一的聚集索引。

在创建基表、视图和索引时,以及基表和视图中的数据被修改时,必须正确设置某些 SET 选项(在本文档后面所有详述)。此外,除非这些 SET 选项正确无误,否则查询优化器不会考虑索引视图。

必须使用架构绑定创建视图,并且还必须通过 SCHEMABINDING 选项创建该视图中引用的任何用户定义的函数。

需要额外的磁盘空间来保存索引视图所定义的数据。

设计方针

设计索引视图时考虑这些指导方针:

设计可供几个查询或多项操作使用的索引视图。

例如,包含列的 SUM 和 COUNT_BIG 的索引视图可供包含函数 SUM、COUNT、COUNT_BIG 或 AVG 的查询使用。查询的速度会更快,因为只需对视图中少量的行进行检索,而不必检索基表中所有的行,而且执行 AVG 函数所需的一部分计算已经完成。

使索引键保持简洁。

通过在索引键中尽可能使用最少的列和字节,可对索引视图的列实现更高效的访问,因为索引视图的列更窄,键比较的速度较更宽的键快一些。另外,在索引视图上定义的每个非聚集索引中,聚集索引键都被用作行定位器。较大的索引键的成本随视图上非聚集索引的数量成比例增长。

考虑最终索引视图的大小。

对于纯聚合,如果索引视图的大小与原始表的大小不相上下,可能就不会实现巨大的性能改善。

设计多个较小的索引视图来局部加速过程。

可能无法总对整个查询设计一个索引视图。如要怎么做,考虑创建若干个索引视图,各执行部分查询。

考虑这几个例子:

经常执行的查询会在一个数据库中聚合数据,并在另一个数据库中聚合数据,然后再联接结果。因为索引视图无法从多个数据库引用表,所以用户不 能设计一个视图来执行整个过程。但是,可以在每个数据库中创建一个索引视图来进行各个数据库的聚合操作。如果优化器可匹配索引视图和现有的查询,那么至少 聚合处理的速度会更快,同时不必对现有的查询进行重新编码。虽然联接处理不会加快,但整个查询将变快,因为其使用存储在索引视图中的聚合。

经常执行的查询聚合来自几个表的数据,然后使用 UNION 合并结果。索引视图不支持 UNION。可设计若干个视图来执行每个聚合操作。而后,优化器可选择索引视图来加快查询,而不必对查询进行重新编码。虽然未改进 UNION 处理,但改善了各个聚合过程。

有能帮助选择索引视图的工具吗?

数据库优化顾问 (DTA3) 是 SQL Server 2005 的一项功能,可帮助管理员优化物理数据库设计。除了建议使用基表上的索引以及表和索引分区策略外,DTA 还推荐使用索引视图。使用 DTA 可加强管理员确定索引、索引视图和分区策略(可优化对数据库执行的查询的典型组合的性能)的组合的能力。DTA 会向用户推荐广泛的索引视图。其中包括运用 SQL Server 2005 的索引视图的新功能(在“SQL Server 2005 的索引视图有哪些新增功能?”一节有所描述)的索引视图。DTA 并没有排除让数据库管理在设计物理存储结构时做出恰当判断的需要。但是,它可以简化物理数据库的设计过程。DTA 通过推荐一组假定的索引,索引视图和分区结果,与基于成本的查询优化器协同工作。DTA 使用优化器来估计当使用和不使用这些结构时的工作负荷成本,并推荐可提供较低的总成本的结构。

因为数据库优化顾问强制执行所有必须的 SET 选项(确保结果集正确无误),所以将成功完成索引视图的创建。然而,如果未能按要求设置选项,用户的应用程序可能无法运用这些视图。如果未按要求指定 SET 选项,对加入索引视图定义的表执行的插入、更新或删除操作就有可能失败。

更新数据时索引视图会有什么变化?

与其他任何索引一样,当基表数据变化时,SQL Server 会自动维护索引视图。对于一般索引,每个索引都直接与一个表相关联。随着在基础表上执行每一项 Insert、Update 或 Delete 操作,索引将被相应地更新,从而使保存在索引中的值总是与表保持一致。

索引视图也得到相同的维护;但是,如果视图引用了若干个表,那么更新任何一个表都需要更新索引视图。不同于一般索引,在任何参与的表中插 入一行都可能导致索引视图中发生多行更改。这是因为所插入的行可能与另一个表的多个行相联接。更新和删除行的情况也一样。因此,索引视图的维护成本可能比 维护表上的索引更高。相反,维护具有高选择性条件的索引视图的成本可能要比维护表上的索引低得多,因为多数对视图所引用的基表的插入、删除和更新操作不会 影响视图。不用访问其他数据库数据就可为索引视图筛选掉这些操作。

在 SQL Server 中,可更新某些视图。当某个视图可更新时,将使用 Insert、Update 和 Delete 语句通过视图直接修改底层基表。在视图上创建索引不会阻止视图的更新。索引视图的更新确实会导致视图下基表的更新。这些更新会作为索引视图维护的一部分自 动传播回索引视图。有关可更新的视图的详细信息,参阅面向 SQL Server 2005 的 SQL Server 联机丛书中的“通过视图修改数据”。

 

维护成本注意事项

设计索引视图时应考虑下面这几点:

索引视图的数据库需要附加存储。索引视图的结果集在物理上通过与典型表存储相似的方式保留在数据库中。

SQL Server 会自动维护视图;因此,对定义了视图的基表进行的任何更改都可能引发对索引视图进行一项或多项更改。所以,将产生额外的维护开销。

视图所获得的净性能提升为其所实现的总查询执行成本节约与存储和维护成本的差值。

比较容易获得接近于视图所需的存储。通过 SQL Server Management Studio 工具——显示预计的执行计划,评估视图定义所封装的 Select 语句。该工具将生成查询所返回的行数和行大小的近似值。通过将这两个值相乘,就可以获得接近于可能的视图大小;但是,只是近似。只有在视图定义中执行查询 或在视图上创建索引,才能确定视图上索引的实际大小。

从 SQL Server 所执行的自动维护注意事项的角度来看,显示预计的执行计划功能可能会让用户在一定程度上了解这一开销的影响。如果通过 SQL Server Management Studio 评估修改视图的语句(视图上的 Update、基表中的 Insert),对该语句显示的执行计划将包括该语句的维护操作。如果就该操作在生产环境中所要执行的次数考虑该成本,那么可能会产生视图维护成本。

通常建议尽可能对视图或其底下的基表成批(而非单独)执行任何修改或更新操作。这样就会降低视图维护开销。

返回页首

创建索引视图

创建索引视图所需的步骤对于视图的成功执行至关重要。

1.

针对将在视图中引用的所有现有表,确认 ANSI_NULLS 的设置正确无误。

2.

创建任何新表之前,确认对下表所示的当前会话正确设置了 ANSI_NULLS。

3.

创建任何新表之前,确认对下表所示的当前会话正确设置了 ANSI_NULLS 和 QUOTED_IDENTIFIER。

4.

确认视图定义具有确定性。

5.

使用 WITH SCHEMABINDING 选项创建视图。

6.

在视图上创建唯一的聚集索引之前,确认会话的 SET 选项的设置正确无误,如下图所示。

7.

在视图上创建唯一的聚集索引。

8.

可用 OBJECTPROPERTY 函数检查现有表或视图上 ANSI_NULLS 和 QUOTED_IDENTIFIER 的值。

使用 SET 选项获得一致的结果

如果在执行查询时对当前会话启用了不同的 SET 选项,评估相同的表达式可在 SQL Server 2005 中产生不同的结果。例如,SET 选项 CONCAT_NULL_YIELDS_NULL 被设为 ON 后,表达式 'abc' + NULL 会返回值 NULL。但当 CONCAT_NULL_YIEDS_NULL 被设为 OFF 后,相同的表达式会生成 'abc'。对于当前会话和视图所引用的对象,索引视图需要几个 SET 选项的固定值,以确保正确维护视图并返回一致的结果。

只要存在下列条件,就必须按下表中“必需的值”一列所示的值对当前会话设置 SET 选项:

创建了索引视图。

在加入索引视图的任何表上执行了任何 Insert、Update 或 Delete 操作。

查询优化器用索引视图生成查询计划。

SET 选项
必需的值
默认服务器值
OLE DB 和 ODBC 值
DB LIB 值

ANSI_NULLS

ON

OFF

ON

OFF

ANSI_PADDING

ON

ON

ON

OFF

ANSI_WARNINGS

ON

OFF

ON

OFF

CONCAT_NULL_YIELDS_NULL

ON

OFF

ON

OFF

NUMERIC_ROUNDABORT

OFF

OFF

OFF

OFF

QUOTED_IDENTIFIER

ON

OFF

ON

OFF

ARITHABORT4 选项必需被设为 ON,以便使当前会话创建索引视图,但是在 SQL Server 2005 中,只要 ANSI_WARNINGS 的值为 ON,该选项就会自动被设为 ON,所以不必对其进行设置。如果使用 OLE DB 或 ODBC 服务器连接,只需修改 ARITHABORT 设置的值。必须使用 sp_configure 在服务器级别或使用 SET 命令从应用程序正确设置所有 DB LIB 值。有关 SET 选项的详细信息,参阅 SQL Server 联机丛书中的“使用选项”。

使用具有确定性的功能

索引视图的定义必须具有确定性。如果选择列表中的所有表达式以及 Where 和 GROUP BY 子句都具有确定性,那么视图就具有确定性。具有确定性的表达式总是在通过一组特定的输入值对其进行评估时,返回相同的结果。只有具有确定性的函数才会加入 具有确定性的表达式。例如,DATEADD 函数具有确定性,因为对于任何给定的一组参数值,该函数总对它的三个参数返回相同的结果。GETDATE 不具有确定性,因为它总调用相同的参数,而其返回的值在每次执行时都会发生变化。详细信息,参阅面向 SQL Server 2005 的 SQL Server 联机丛书中的“具有和不具有确定性的函数”。

即使某个表达式具有确定性(如果其包含浮点表达式),确切的结果可能依处理器体系结构或微码的版本而定。为了在计算机间迁移数据库时确保 SQL Server 2005 中数据的完整性,这种表达式只能作为索引视图的非键列加入。不含浮点表达式的具有确定性的表达式被认为是精确的。只有永久和/或精确的具有确定性的表达式 才可加入键列以及索引视图的 Where 或 GROUP BY 子句。永久性表达式是对已保存列的引用,包括一般列和标为 PERSISTED 的计算列。

用 COLUMNPROPERTY 函数和 IsDeterministic 属性确定视图列是否具有确定性。用 COLUMNPROPERTY 函数和 IsPrecise 属性确定带有 SCHEMABINDING 的视图中的具有确定性的列是精确的。如果属性为 TRUE,COLUMNPROPERTY 将返回 1;如为 FALSE,则返回 0;而如果为 NULL,则表示无效输入。例如,在此脚本中

Create TABLE T(a int, b real, c as getdate(), d as a+b)
Create VIEW VT WITH SCHEMABINDING AS Select a, b, c, d FROM dbo.T
Select object_id('VT'), COLUMNPROPERTY(object_id('VT'),'b','IsPrecise')

Select 对 IsPrecise 返回 0,因为 b 列为实型。可通过 COLUMNPROPERTY 做一些实验,确认 T 的其他列是否具有确定性并是精确的。

其他要求

可索引的视图集合是可能的视图集合的一个子集。任何可索引的视图在有或没有索引的情况下都可存在。

除了设计方针(“使用 SET 选项获得一致的结果”和“使用具有确定性的函数”这两节)中所列的要求外,还必须满足下列要求,以便在视图上创建唯一的聚集索引。

有关基表的要求

视图所引用的基表必须具有在创建表时所设的 SET 选项 ANSI_NULLS 的正确的值。可用 OBJECTPROPERTY 函数检查现有表上的 ANSI_NULLS 的值。

有关函数的要求

必须使用 WITH SCHEMABINDING 选项创建视图所引用的用户定义的函数。

有关视图的要求

必须使用 WITH SCHEMABINDING 选项创建视图。

必须由使用双结构名称 (schemaname.tablename) 的视图引用表。

必须由使用双结构名称 (schemaname.functionname) 的视图引用用户定义的函数。

必须正确设置 SET 选项 ANSI_NULLS 和 QUOTED_IDENTIFIER。

视图限制

如要在 SQL Server 2005 中的视图上创建一个索引,相应的视图定义必须包含:

ANY、NOT ANY

OPENROWSET、OPENQUERY、OPENDATASOURCE

不精确的(浮型、实型)值上的算术

OPENXML

COMPUTE、COMPUTE BY

ORDER BY

CONVERT 生成一个不精确的结果

OUTER 联接

COUNT(*)

引用带有一个已禁用的聚集索引的基表

GROUP BY ALL

引用不同数据库中的表或函数

派生的表(FROM 列表中的子查询)

引用另一个视图

DISTINCT

ROWSET 函数

EXISTS、NOT EXISTS

自联接

聚合结果(比如:SUM(x)+SUM(x))上的表达式

STDEV、STDEVP、VAR、VARP、AVG

全文谓词 (CONTAINS、FREETEXT、CONTAINSTABLE、FREETEXTTABLE)

子查询

不精确的常量(比如:2.34e5)

可为空的表达式上的 SUM

内嵌或表值函数

表提示(比如:NOLOCK)

MIN、MAX

text、ntext、image、filestream 或 XML 列

不具有确定性的表达式

TOP

非 unicode 排序

UNION

SQL Server 2005 可检测到的矛盾情况表示视图将为空(比如,当 0=1 及 …)

注意 索引视图可能包含浮型和实型列;但是,如果这类列为非永久性的计算列,则不能包含在聚集索引键中。

GROUP BY 限制

如果存在 GROUP BY,VIEW 定义为:

一定包含 COUNT_BIG(*)。

一定不包含 HAVING、CUBE、ROLLUP 或 GROUPING()。

这些限制仅适用于索引视图定义。即便不能满足上述 GROUP BY 限制,查询也可以在其执行计划中使用索引视图。

有关索引的要求

执行 Create INDEX 语句的用户必须是视图所有者。

如果视图定义包含 GROUP BY 子句,唯一的聚集索引的键只能引用 GROUP BY 子句所指定的列。

一定不能在启用 IGNORE_DUP_KEY 选项的情况下创建索引。

返回页首

示例

本节中的例子阐述了如何结合两类主要的查询使用索引视图:聚合和联接。同时,说明了查询优化器在确定某个索引视图是否适用时所用的条件。有关完整的条件列表的信息,参阅“查询优化器如何使用索引视图”。

这些查询基于 AdventureWorks 中的表。AdventureWorks 是 SQL Server 2005 所提供的示例数据库,并可作为写入方式来执行。在创建视图前后,用户可能想用 SQL Server Management Studio 中显示预计的执行计划工具,来查看查询优化器所选择的计划。虽然这些例子说明了优化器选择低成本执行计划的方式,但是 AdventureWorks 示例由于太小而无法显示出性能方面的提升。

在开始运用这些示例之前,确保通过运行下列命令对会话设置正确的选项:

设置

SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET CONCAT_NULL_YIELDS_NULL ON
SET NUMERIC_ROUNDABORT OFF
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON

下列查询显示了两种方法用于从 Sales.SalesOrderDetail 表返回具有最大总折扣的五个产品。

查询 1

Select TOP 5 ProductID, Sum(UnitPrice*OrderQty) -
Sum(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS Rebate
FROM Sales.SalesOrderDetail
GROUP BY ProductID
ORDER BY Rebate DESC

查询 2

Select TOP 5 ProductID,
SUM(UnitPrice*OrderQty*UnitPriceDiscount) AS Rebate
FROM Sales.SalesOrderDetail
GROUP BY ProductID
ORDER BY Rebate DESC

查询优化器所选的执行计划包含:

一个聚集索引扫描,位于估计行数为 121,317 的 Sales.SalesOrderDetail 表上。

一个哈希匹配/聚合操作符,用于将所选的行放入基于 GROUP BY 列的哈希表,并计算每行的 SUM 聚合。

一个 TOP 5 分类操作符,基于 orDER BY 子句。

视图 1

添加包含 Rebate 列所需聚合的索引视图将更改“查询 1”的查询执行计划。在大型表(含数百万行)上,查询的性能也会得到大幅提升。

Create VIEW Vdiscount1 WITH SCHEMABINDING AS
Select SUM(UnitPrice*OrderQty) AS SumPrice,
SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS SumDiscountPrice,
COUNT_BIG(*) AS Count, ProductID
FROM Sales.SalesOrderDetail
GROUP BY ProductID
GO
Create UNIQUE CLUSTERED INDEX VDiscountInd ON Vdiscount1 (ProductID)

第一个查询的执行计划显示 Vdiscount1 视图被优化器所用。然而,该视图将不被第二个查询所用,因为其不包含 SUM(UnitPrice*OrderQty*UnitPriceDiscount) 聚合。可再创建一个索引视图,来同时应付这两个查询。

视图 2

Create VIEW Vdiscount2 WITH SCHEMABINDING AS
Select SUM(UnitPrice*OrderQty)AS SumPrice,
SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount))AS SumDiscountPrice,
SUM(UnitPrice*OrderQty*UnitPriceDiscount)AS SumDiscountPrice2,
COUNT_BIG(*) AS Count, ProductID
FROM Sales.SalesOrderDetail
GROUP BY ProductID
GO
Create UNIQUE CLUSTERED INDEX VDiscountInd ON Vdiscount2 (ProductID)

使用这个索引视图,在丢弃 Vdiscount1 后,这两个查询的查询执行计划现在包含:

一个聚集索引扫描,位于估计行数为 266 的 Vdiscount2 视图上

一个 TOP 5 分类函数,基于 orDER BY 子句

查询优化器选择了该视图,因为虽然没有在查询中引用该视图,但其提供了最低的执行成本。

查询 3

“查询 3”与上述查询类似,但 ProductID 被列 SalesOrderID (未包含在视图定义中)所替换。这违反了条件:视图定义中表上的选择列表中的所有表达式必须派生自视图选择列表,以便使用查询计划中的索引视图。

Select TOP 3 SalesOrderID,
SUM(UnitPrice*OrderQty*UnitPriceDiscount) orderRebate
FROM Sales.SalesOrderDetail
GROUP BY SalesOrderID
ORDER BY orderRebate DESC

必须用一个单独的索引视图来应付该查询。可修改 Vdiscount2 以包含 SalesOrderID;但是,结果视图将和原始表包含同样多的行,并不会通过使用基表提高性能。

查询 4

该查询可生成每个产品的平均价格。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-od.UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID=p.ProductID
GROUP BY p.Name, od.ProductID

复杂的聚合(比如:STDEV、VARIANCE、AVG)不能包含在索引视图的定义中。然而,通过包含(经组合)执行复杂聚合的一些简单的聚合函数,索引视图可用以执行含 AVG 的查询。

视图 3

该索引视图包含执行 AVG 函数所需的简单聚合函数。在创建“视图 3”后执行“查询 4”时,执行计划将显示所用的视图。优化器可从视图的简单聚合列 PriceCount 派生 AVG 表达式。

Create VIEW View3 WITH SCHEMABINDING AS
Select ProductID, SUM(UnitPrice*(1.00-UnitPriceDiscount)) AS Price,
COUNT_BIG(*) AS Count, SUM(OrderQty) AS Units
FROM Sales.SalesOrderDetail
GROUP BY ProductID
GO
Create UNIQUE CLUSTERED INDEX iv3 ON View3 (ProductID)

查询 5

该查询与“查询 4”相同,但包含一个附加的搜索条件。即使附加的搜索条件只从未包含在视图定义中的表引用列,“视图 3”也将作用于该查询。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID=p.ProductID AND p.Name like '%Red%'
GROUP BY p.Name, od.ProductID

查询 6

查询优化器无法对该查询使用“视图 3”。添加的搜索条件 od.UnitPrice>10 包含来自视图定义中表的列,但该列不显示在 GROUP BY 列表中,而搜索谓词也不显示在视图定义中。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID=p.ProductID AND p.Name like '%Red%'
GROUP BY p.Name, od.ProductID

查询 7

相反,查询优化器可对“查询 7”使用“视图 3”,因为新的搜索条件 od.ProductID in (1,2,13,41) 中定义的列包含在视图定义的 GROUP BY 子句中。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID = p.ProductID AND od.UnitPrice > 10
GROUP BY p.Name, od.ProductID

视图 4

通过包含视图定义中的 SumPrice 和 Count 列以便计算查询中的 AVG,该视图将满足“查询 6”的条件。

Create VIEW View4 WITH SCHEMABINDING AS
Select p.Name, od.ProductID,
SUM(od.UnitPrice*(1.00-UnitPriceDiscount)) AS SumPrice,
SUM(od.OrderQty) AS Units, COUNT_BIG(*) AS Count
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID = p.ProductID AND od.UnitPrice > 10
GROUP BY p.Name, od.ProductID
GO
Create UNIQUE CLUSTERED INDEX VdiscountInd on View4 (Name, ProductID)

查询 8

“视图 4”上相同的索引也将用于在其中添加对表 Sales.SalesOrderHeader 的联接的查询。该查询满足条件:查询 FROM 子句中所列的表是索引视图的 FROM 子句中的表的超集。

Select p.Name, od.ProductID,
AVG(od.UnitPrice*(1.00-UnitPriceDiscount)) AS AvgPrice,
SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p,
Sales.SalesOrderHeader AS o
Where od.ProductID = p.ProductID AND o.SalesOrderID = od.SalesOrderID
AND od.UnitPrice > 10
GROUP BY p.Name, od.ProductID

最后两个查询在“查询 8”的基础上进行了修改。每个修改后的查询都违反了优化器的条件之一,并且不同于“查询 8”,无法使用“视图 4”。

查询 8a

“查询 8a”(Q8a) 无法使用索引视图,因为 Where 子句无法将视图定义中的 UnitPrice > 10 与查询中的 UnitPrice > 25 相匹配,而且 UnitPrice 未出现在视图中。查询搜索条件谓词必须是视图定义中的搜索条件谓词的超集。

Select p.Name, od.ProductID, AVG(od.UnitPrice*(1.00-UnitPriceDiscount))
AvgPrice, SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p,
Sales.SalesOrderHeader AS o
Where od.ProductID = p.ProductID AND o.SalesOrderID = od.SalesOrderID
AND od.UnitPrice > 25
GROUP BY p.Name, od.ProductID

查询 8b

注意,表 Sales.SalesOrderHeader 不加入索引视图 V4 定义。尽管这样,在该表上添加一个谓词将不允许使用索引视图,因为所添加的谓词可能会更改或消除加入下方“查询 8b”所示的聚合的其他行。

Select p.Name, od.ProductID, AVG(od.UnitPrice*(1.00-UnitPriceDiscount))
AS AvgPrice, SUM(od.OrderQty) AS Units
FROM Sales.SalesOrderDetail AS od, Production.Product AS p,
Sales.SalesOrderHeader AS o
Where od.ProductID = p.ProductID AND o.SalesOrderID = od.SalesOrderID
AND od.UnitPrice > 10 AND o.OrderDate > '20040728'
GROUP BY p.Name, od.ProductID

视图 4a

“视图 4a”通过将 UnitPrice 列包含在选择列表和 GROUP BY 子句中,扩展了“视图 4”。“查询 8a”可使用“视图 4a”,因为将进一步筛选 UnitPrice 值(已知大于 10)以便只留下大于 25 的值。以下是间隔归入的一个例子。

Create VIEW View4a WITH SCHEMABINDING AS
Select p.Name, od.ProductID, od.UnitPrice,
SUM(od.UnitPrice*(1.00-UnitPriceDiscount)) AS SumPrice,
SUM(od.OrderQty) AS Units, COUNT_BIG(*) AS Count
FROM Sales.SalesOrderDetail AS od, Production.Product AS p
Where od.ProductID = p.ProductID AND od.UnitPrice > 10
GROUP BY p.Name, od.ProductID, od.UnitPrice
GO
Create UNIQUE CLUSTERED INDEX VdiscountInd
ON View4a (Name, ProductID, UnitPrice)

视图 5

“视图 5”在其选择和 GROUP BY 列表中包含一个表达式。请注意,LineTotal 是一个计算列,因此本身是一个表达式。反过来,该表达式嵌套在对 FLOOR 函数的调用中。

Create VIEW View5 WITH SCHEMABINDING AS
Select FLOOR(LineTotal) FloorTotal, COUNT_BIG(*) C
FROM Sales.SalesOrderDetail
GROUP BY FLOOR(LineTotal)
GO
Create UNIQUE CLUSTERED INDEX iView5 ON View5(FloorTotal)

查询 9

“查询 9”在其选择和 GROUP BY 列表中包含表达式 FLOOR(LineTotal)。通过对 SQL Server 2005 中表达式的视图匹配的新扩展,该查询使用“视图 5”上的索引。

Select TOP 5 FLOOR(LineTotal), Count(*)
FROM Sales.SalesOrderDetail
GROUP BY FLOOR(LineTotal)
ORDER BY COUNT(*) DESC

视图 6

“视图 6”存储月末三天中有关线项目的信息。这样可将这些行聚集在少量页面上,从而可以迅速应对这些天里对 Sales.SalesOrderDetail 的查询。

Create VIEW View6 WITH SCHEMABINDING AS
Select SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber, orderQty,
ProductID, SpecialOfferID, UnitPrice, UnitPriceDiscount, rowguid,
ModifiedDate
FROM Sales.SalesOrderDetail
Where ModifiedDate IN ( convert(datetime, '2004-07-31', 120),
convert(datetime, '2004-07-30', 120),
convert(datetime, '2004-07-29', 120) )
GO
Create UNIQUE CLUSTERED INDEX VEndJulyO4Ind
ON View6(SalesOrderID, SalesOrderDetailID)
GO

查询 10

下面的查询可匹配“视图 6”,同时系统可生成一个计划,用于扫描视图上的 VendJuly04Ind 索引,但不扫描整个 Sales.SalesOrderDetail 表。此查询还说明了表达式等价(由于查询中日期的顺序不同于视图,而且数据格式也不同)和谓词归入(由于查询要求将结果的子集保存在视图中)。

Select h.*, SalesOrderDetailID, CarrierTrackingNumber, orderQty,
ProductID, SpecialOfferID, UnitPrice, UnitPriceDiscount, d.rowguid,
d.ModifiedDate
FROM Sales.SalesOrderHeader as h, Sales.SalesOrderDetail as d
Where (d.ModifiedDate = '20040729' or d.ModifiedDate = '20040730')
and d.SalesOrderID=h.SalesOrderID

视图 7

开发人员有时还会发现使用索引视图强制专门的完整性约束很方便。例如,可通过索引视图强制约束:“除非列中存在多个 0 值,否则表 T 的列 a 就是唯一的”。下方索引视图“视图 7”就强制了这一约束。如果运行下面的脚本,其将成功运行直至最终的插入操作。该语句被禁止,因为其添加了一个非零重复值。

USE tempdb
GO
Create TABLE T(a int)
GO
Create VIEW View7 WITH SCHEMABINDING
AS Select a
FROM dbo.T
Where a <> 0
GO
Create UNIQUE CLUSTERED INDEX IV on View7(a)
GO
-- legal:
Insert INTO T VALUES(1)
Insert INTO T VALUES(2)
Insert INTO T VALUES(0)
Insert INTO T VALUES(0) -- duplicate 0
 
-- dissalowed:
Insert INTO T VALUES(2)

返回页首

有关索引视图的常见问题

问:为何对可创建索引的视图类型存在限制?

答:为了确保在逻辑上可对视图进行增量维护,限制创建维护成本较高的视图,并限制 SQL Server 实施的复杂性。较大的视图集不具有确定性并与内容相关;其内容的“更改”独立于 DML 操作。无法对这些内容进行索引。在其定义中调用 GETDATE 或 SUSER_SNAME 的任何视图就属于这类视图。

问:视图上的第一个索引为何必须为 CLUSTERED 和 UNIQUE?

答:必须为 UNIQUE 以便在维护索引视图期间,轻松地按键值查找视图中的记录,并阻止创建带有重复项目的视图(要求维护特殊的逻辑)。必须为 CLUSTERED,因为只有聚集索引才能在强制唯一性的同时存储行。

问:为何查询优化器不选取我的索引视图用于查询计划?

答:优化器不选取索引视图主要有三种原因:

(1) 使用 SQL Server Enterprise 或 Developer 版本之外的其他版本。只有 Enterprise 和 Developer 版本才支持自动的查询对索引视图匹配。按名称引用索引视图并包含 NOEXPAND 提示,让查询处理器使用所有其他版本中的索引视图。

(2) 使用索引视图的成本可能超出从基表获取数据的成本,或者查询过于简单,使得针对基表的查询的速度既快又容易查找。当在较小的表上定义索引视图时,经常会发 生这种情况。如要强制查询处理器使用索引视图,那么可使用 NOEXPAND 提示。如果最初不通过显式的方式引用视图,这样做就可能要求重新编写查询。您可获得带有 NOEXPAND 的查询的实际成本,并将之与不引用该视图的查询计划的实际成本相比较。如果两者的成本相近,那么您就可以认定用不用索引视图都不重要。

(3) 查询优化器不将查询与索引视图相匹配。重新检查视图和查询的定义,确保两者在结构上可相匹配。CASTS、converts 以及其他在逻辑上不会更改查询结果的表达式可能会阻止匹配。另外,表达式规范化和等价以及 SQL Server 执行的归入测试方面存在一些限制。可能无法显示某些等价表达式是相同的,或者逻辑上被其他表达式归入的表达式被真正归入,因此可能会错失匹配。

问:我每周更新一次数据仓库。索引视图使查询速度大大提升,却降低了每周更新的速度?该怎么办呢?

答:可以考虑在每周更新前丢弃索引视图,更新完后再重新创建。

问:我的视图存在重复项目,而我确实想对其进行维护。该怎么办呢?

答:可以考虑创建一个视图,按您所要的视图中的所有列和表达式进行分组,并添加一个 COUNT_BIG(*) 列,然后在组合的列上创建一个唯一的聚集索引。分组过程可确保唯一性。虽然不是完全相同的视图,但可以满足您的需要。

问:我在一个视图上定义了另一个视图。SQL Server 不让我索引顶级视图。该怎么办呢?

答:可以考虑手动将嵌套视图的定义扩展到顶级视图,然后对其进行索引(索引最低层的视图,或者不索引该视图)。

问:为何一定要对索引视图定义 WITH SCHEMABINDING?

答:为了

使用 schemaname.objectname 明确识别视图所引用的所有对象,而不管是哪个用户访问该视图,同时

不会以导致视图定义非法或强制 SQL Server 在该视图上重新创建索引的方式,更改视图定义中所引用的对象。

问:为何不能在索引视图中使用 OUTER JOIN?

答:当将数据插入基表时,行会在逻辑上从基于 OUTER JOIN 的索引视图上消失。这会使执行 OUTER JOIN 视图的增量更新变得相对复杂,而执行性能将比基于标准 (INNER) JOIN 的视图慢一些。

返回页首

如需获得更多信息

Microsoft SQL Server 2005 联机丛书包含更多有关索引视图的信息。如需其他信息,参见下列资源:

Microsoft SQL Server 网站http://www.microsoft.com/sql.

Microsoft SQL Server 开发中心:http://msdn.microsoft.com/sqlserver.

SQL Server 杂志http://www.sqlmag.com.

microsoft.public.SQLServer.server 和 microsoft.public.SQLServer.datawarehouse 新闻组:news://news.microsoft.com.

有关 SQL Server 的 Microsoft 官方课程。有关最新的课程信息,请访问http://www.microsoft.com/trainingandservices

1
该视图不一定非得被完全具体化。查询可包含其他可应用于该视图所引用的表和视图的谓词、联接或聚合。该视图不再需要被完全具体化

2
当优化器将两个 FROM 子句合并成一个(联接子查询或联接转换派生表)时,存在一些例外情况。如果发生这类情况,原始查询中涉及索引视图替换的 FROM 子句可能不止一个。

3
DTA 在 SQL Server 2000 的索引优化向导上进行了改进。

4
在 SQL Server 2000 中创建索引视图之前确保将 ARITHABORT 设为 ON。

[SQL]数据库查询优化

mikel阅读(949)

1         使用SET NOCOUNT ON 选项:

缺 省地,每次执行SQL语句时,一个消息会从服务端发给客户端以显示SQL语句影响的行数。这些信息对客户端来说很少有用。通过关闭这个缺省值,你能减少在 服务端和客户端的网络流量,帮助全面提升服务器和应用程序的性能。为了关闭存储过程级的这个特点,在每个存储过程的开头包含“SET NOCOUNT ON”语句。

2         正确使用UNION和UNION ALL:

    许 多人没完全理解UNION和UNION Select是怎样工作的,因此,结果浪费了大量不必要的SQLServer资源。当使用UNION时,它相当于在结果集上执行Select DISTINCT。换句话说,UNION将联合两个相类似的记录集,然后搜索重复的记录并排除。如果这是你的目的,那么使用UNION是正确的。但如果你 使用UNION联合的两个记录集没有重复记录,那么使用UNION会浪费资源,因为它要寻找重复记录,即使你确定它们不存在。

所以如果你知道你要联合的记录集里没有重复,那么你要使用UNION ALL,而不是UNION。UNION ALL联合记录集,但不搜索重复记录,这样减少SQLServer资源的使用,从而提升性能。

3         尽量不用Select * :

    绝 大多数情况下,不要用 * 来代替查询返回的字段列表,用 * 的好处是代码量少、就算是表结构或视图的列发生变化,编写的查询SQL语句也不用变,都返回所有的字段。但数据库服务器在解析时,如果碰到 *,则会先分析表的结构,然后把表的所有字段名再罗列出来。这就增加了分析的时间。

4         慎用Select DISTINCT:

    DISTINCT子句仅在特定功能的时候使用,即从记录集中排除重复记录的时候。这是因为DISTINCT子句先获取结果集然后去重,这样增加SQLServer有用资源的使用。当然,如果你需要去做,那就只有去做了。

当如果你知道Select语句将从不返回重复记录,那么使用DISTINCT语句对SQLServer资源不必要的浪费。

5         少用游标:

    任何一种游标都会降低SQLServer性能。有些情况不能避免,大多数情况可以避免。所以如果你的应用程序目前正在使用TSQL游标,看看这些代码是否能够重写以避免它们。如果你需要一行一行的执行操作,考虑下边这些选项中的一个或多个来代替游标的使用:

使用临时表

使用WHILE循环

使用派生表

使用相关子查询

使用CASE语句

使用多个查询

上面每一个都能取代游标并且执行更快。 如果你不能避免使用游标,至少试着提高它们的速度,找出加速游标的方法。

6         选择最有效率的表名顺序:

    SQLSERVER的 解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表driving table)将被最先处理,在FROM子句中包含多个表的情况下,必须选择记录条数最少的表作为基础表,当SQLSERVER处理多个表时,会运用排序及 合并的方式连接它们。首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行排序;然后扫描第二个表(FROM子句中最后第二个表);最后将所有 从第二个表中检索出的记录与第一个表中合适记录进行合并。

例如: 表 TAB1有 16384 条记录,表 TAB2 有5条记录,选择TAB2作为基础表 (最好的方法):

select count(*) from TAB1 a, TAB2 b

选择TAB1作为基础表 (不佳的方法):

select count(*) from TAB2 a, TAB1 b

如果有3个以上的表连接查询,那就需要选择交叉表(intersection table)作为基础表,交叉表是指那个被其他表所引用的表。

7         使用表的别名(Alias):

当在SQL语句中连接多个表时,请使用表的别名并把别名前缀于每个Column上,这样可以减少解析的时间并减少那些由Column歧义引起的语法错误。

8         SARG你的Where条件:

    ARGE来 源于"Search Argument"(搜索参数)的首字母拼成的"SARG",它是指Where子句里,列和常量的比较。如果Where子句是sargable(可 SARG的),这意味着它能利用索引加速查询的完成。如果Where子句不是可SARG的,这意味着Where子句不能利用索引(或至少部分不能利用), 执行的是全表或索引扫描,这会引起查询的性能下降。

在Where子句里不可 SARG的搜索条件如"IS NULL", "<>", "!=", "!>", "!<", "NOT", "NOT EXISTS", "NOT IN", "NOT LIKE"和"LIKE '%500'",通常(但不总是)会阻止查询优化器使用索引执行搜索。另外在列上使用包括函数的表达式、两边都使用相同列的表达式、或和一个列(不是常 量)比较的表达式,都是不可SARG的。

并不是每一个不可SARG的Where子句都注定要全表扫描。如果Where子句包括两个可SARG和一个不可SARG的子句,那么至少可SARG的子句能使用索引(如果存在的话)帮助快速访问数据。

大多数情况下,如果表上 有包括查询里所有Select、JOIN、Where子句用到的列的覆盖索引,那么覆盖索引能够代替全表扫描去返回查询的数据,即使它有不可SARG的 Where子句。但记住覆盖索引尤其自身的缺陷,如此经常产生宽索引会增加读磁盘I/O。某些情况下,可以把不可SARG的Where子句重写成可 SARG的子句。例如:

Where SUBSTRING(firstname,1,1) = 'm'

可以写成:

Where firstname like 'm%'

这两个Where子句有相同的结果,但第一个是不可SARG的(因为使用了函数)将运行得慢些,而第二个是可SARG的,将运行得快些。

如果你不知道特定的Where子句是不是可SARG的,在查询分析器里检查查询执行计划。这样做,你能很快的知道查询是使用了索引还是全表扫描来返回的数据。仔细分析,许多不可SARG的查询能写成可SARG的查询。下面分几点讲解Where条件的SARG。

8.1       Where子句中的连接顺序

SQLSERVER采用自下而上的顺序解析Where子句,根据这个原理,表之间的连接必须写在其他Where条件之前,那些可以过滤掉最大数量记录的条件必须写在Where子句的末尾。例如:

(低效)

Select *  FROM EMP E

Where SAL > 50000

AND JOB = ‘MANAGER’

AND 25 < (Select COUNT(*) FROM EMP Where MGR=E.EMPNO)

 

(高效)

Select * FROM EMP E

Where 25 < (Select COUNT(*) FROM EMP Where MGR=E.EMPNO)

AND SAL > 50000

AND JOB = ‘MANAGER’

8.2       避免困难的正规表达式:

MATCHES和LIKE关键字支持通配符匹配,技术上叫正规表达式。但这种匹配特别耗费时间。例如:

Select * FROM customer Where zipcode LIKE "98_ _ _"

即使在zipcode字段上建立了索引,在这种情况下也还是采用顺序扫描的方式。如果把语句改为Select * FROM customer Where zipcode >="98000",在执行查询时就会利用索引来查询,显然会大大提高速度。

另外,还要避免非开始的子串。例如语句:

Select * FROM customer Where zipcode[2,3] >"80"

在where子句中采用了非开始子串,因而这个语句也不会使用索引。

8.3       避免对大型表行数据的顺序存取:

在嵌套查询中,对表的顺 序存取对查询效率可能产生致命的影响。比如采用顺序存取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10亿行数据。避免这 种情况的主要方法就是对连接的列进行索引。例如,两个表:学生表(学号、姓名、年龄……)和选课表(学号、课程号、成绩)。如果两个表要做连接,就要在“ 学号”这个连接字段上建立索引。

还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的where子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作:

Select * FROM orders Where (customer_num=104 AND order_num>1001) or order_num=1008

虽然在customer_num和order_num上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:

Select * FROM orders Where customer_num=104 AND order_num>1001

UNION ALL

Select * FROM orders Where order_num=1008

这样就能利用索引路径处理查询。

8.4       EXISTS和IN的使用:

    在 许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接。   在这种情况下,使用EXISTS(或NOT EXISTS)通常将提高查询的效率。在子查询中,NOT IN子句将执行一个内部的排序和合并。无论在哪种情况下,NOT IN都是最低效的,因为它对子查询中的表执行了一个全表遍历。为了避免使用NOT IN,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS。

8.5       避免在索引列上使用IS NULL和IS NOT NULL:

    避免在索引中使用任何可以为空的列,SQLSERVER将无法使用该索引。对于单列索引,如果列包含空值,索引中将不存在此记录;对于复合索引,如果每个列都为空,索引中同样不存在此记录。如果至少有一个列不为空,则记录存在于索引中。   

  如果唯一性索引建立在表的A列和B列上,并且表中存在一条记录的A,B值为(123,null),SQLSERVER将不接受下一条具有相同A,B值(123,null)的记录插入。   

  如果所有的索引列都为空,SQLSERVER将认为整个键值为空,而空不可能 等于空,因此你可以插入1000条具有相同键值的记录,当然它们都是空!因为空值不存在于索引列中,所以Where子句中对索引列进行空值比较将使 SQLSERVER停用该索引。下面的代码将会很低效(索引失效):

Select … FROM DEPARTMENT Where DEPT_CODE IS NOT NULL

8.6       避免在索引列上使用计算:

    Where子句中,如果索引列是函数的一部分,优化器将不使用索引而使用全表扫描。   例如下面的语句低效 :

Select … FROM DEPT Where SAL * 12 > 25000

    而下面的语句将是高效的:

Select … FROM DEPT Where SAL > 25000/12

请务必注意,查询中不要对索引列进行处理,如:TRIM,substring,convert等等操作。

8.7       用Where子句替换HAVING子句:

    避免使用HAVING子句,HAVING只会在检索出所有记录之后才对结果集进行过滤,这个处理需要排序、统计等操作。如果能通过Where子句限制记录的数目,那就能减少这方面的开销。

9         避免或简化排序:

应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素:

l         索引中不包括一个或几个待排序的列;

l         group by或order by子句中列的次序与索引的次序不一样;

l         排序的列来自不同的表。

为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图简化它,如缩小排序的列的范围等。

10    临时表的使用:

    临 时表有很多特殊的用途,象用来替代游标,不过它们仍能引起性能问题,如果这个问题能消除,SQLServer将执行得更快。在永久表和临时表的数据行相同 的条件下,使用临时表没有永久表快。但有时还必须得使用临时表,如先从存储大量数据的永久表中提取符全条件的存放到临时表,然后在临时表上执行操作。如果 是直接在存储大量数据的永久表上执行操作(如:统计、循环等),其性能将大打折扣。所以,使不使用临时表,何时使用临时表,需要具体情况决定。

11    是否使用视图:

视图最大的用途是处理安 全相关的问题,而不是一些懒惰的开发人员用来存储经常使用的查询的方法。例如,如果你需要允许用户访问特定SQLServer的数据,那么你也许可以考虑 为用户(或组)创建一个视图,然后给用户访问视图而不是基表的权限。另一方面,在应用程序里,从视图选择数据没有好的理由,相反,绕过视图直接从需要的表 里获取数据。原因是许多视图(当然不是全部)返回比Select语句所需更多的数据,增加不必要的开销。

例如,假定有一个视图从 两个连接表里返回10列。你想要从视图里使用Select语句返回其中7列。实际上发生的情况是基于视图的查询先运行,返回数据,然后你的查询针对这些数 据运行。既然你仅需要7列,而不是视图返回的10列,更多不必要的数据被返回。浪费SQLServer的资源。

长久以来,大家在争论是查询视图速度快还是直接查询快,本人也不敢轻易下结论,因此作了多次试验,其结果是:基于视图查询,性能确实不会比直接写查询语句快,对于简单的查询,最多是在同一水平上。

当然,上面的测试是在没有为视图创建索引的情况下,SQLServer2000以上可以为视图创建索引,视图索引与表的索引在作用方式上非常相似。与表一样,视图可以有一个集簇索引(clustered index)和多个非集簇索引。创建视图索引后能够提高视图的性能。

如果视图不包含索引,则数据库中不保存视图返回的结果集。有的时候,我们可能要创建涉及大量记录或必须进行复杂计算的视图,比如要进行聚合分组处理或多重连接操作。如果每次引用这些视图的时候让sql server重新生成结果集,数据库开销将非常大。

12    让事务尽可能的短:

    保持TSQL事务尽可能的短。这会帮助减少锁(所有类型的锁)的数量,有助于全面提升SQLServer的性能。如果有经验,你也许要将长事务分成更小的事务组。

13    用存储过程代替直接写查询语句:

    存储过程为开发人员提供了很多好处,包括:

n         减少网络流量和响应时间,提升应用程序性能。例如,通过网络发送一个存储过程调用,而不是发送500行的TSQL将更快,资源使用更少。当每次执行SQL时,都会执行解析SQL语句、估算索引的利用率、绑定变量、读数据块等等工作。

n         存储过程执行计划能够重用,驻留在SQLServer内存的缓存里,减少服务器开销。

n         客 户端执行请求更有效率。例如,如果应用程序需要插入大量的二进制值到一个image数据列而不使用存储过程,它必须转化二进制为字符串(大小会增加一 倍),然后发送给SQLServer。当SQLServer接收到后,它必须把字符串值转回二进制格式。大量的浪费开销。存储过程能消除这个问题通过将应 用程序传给SQLServer的二进制格式作为参数,从而减少开销提升性能。

n         存储过程帮助提供代码重用。虽然这些不直接提升应用程序的性能,通过减少代码量和减少调试时间来提升开发人员的效率。

n         存储过程能封装逻辑。你能够改变存储过程代码而不影响客户端(假定你保持参数相同也不移除任何结果集的列)。这节约开发人员的时间。

n         存储过程为你的数据提供更好的安全性。如果你仅使用存储过程,你可以移除直接对表的Select、Insert、Update和Delete权限从而强迫开发人员使用存储过程访问数据。这会节约DBA的时间。

n         作为首要的常规,所有的TSQL代码都应该通过存储过程调用。

13.1   存储过程名不要以 sp_ 开头:

    对 这一准则,可能很多人会感觉纳闷,是的,我开始也纳闷过。如果创建的存储过程不是运行在Master数据库里,不要使用以sp_为前缀的名称。这个特别的 前缀是为系统存储过程保留的。尽管使用这个前缀不会禁止用户定义的存储过程的运行,但会稍微降低一些执行效率。这是因为SQLServer在执行以sp_ 为前缀的任何一个存储过程时缺省地首先试图在Master数据库里寻找,尽管那儿没有,这就浪费了寻找存储过程的时间。如果SQLServer在 Master数据库里不能找到存储过程,那么接下来会将存储过程的拥有者作为DBO去解析。如果存储过程在目前的数据库里,那么它会执行。为了避免不必要 的延迟,不要用前缀为sp_命名你的任何一个存储过程。

13.2   存储过程的拥有者要相同:

    为 了最好的性能,同一个存储过程里调用的所有对象的拥有者都应该相同,DBO更适宜。如果不是那样,即对象名相同而拥有者不同,那么SQLServer必须 执行名称判断。当发生这样的情形时,SQLServer不能使用存储过程里在内存里的执行计划,相反,它必须重新编译存储过程,从而影响性能。当从应用程 序里调用存储过程时,使用分隔符名称来调用也是重要的。如:

EXEC dbo.myProcedure

代替:

        EXEC myProcedure

这样做有两个原因,其中 一个和性能有关。首先,使用完全有分隔符的名称有助于消除那些和你要运行的存储过程有潜在的混淆,有助于禁止BUG和潜在的问题。但更重要的是,这样做 SQLServer能更直接的访问存储过程执行计划,而不是轮流访问,从而加速了存储过程的性能。当然性能提升很小,但如果你的服务器每小时要运行成千上 万或更多的存储过程,这些节约的小段时间加起来就很可观了。

14    完整性使用下的约束和触发器:

    数据库里不要执行多余的完整性特点。例如,如果你正使用主键和外键约束来强迫引用完整性,则不要添加触发器来实现相同的功能而增加不必要的开销。同样既使用约束又使用默认值或既使用约束又使用规则也会执行多余的工作。

15    在SQL中捕捉异常:

这一条准则应该不能算是优化方面的,只是编写要求。现在SQLServer2005中,新增了BEGIN TRY…END TRY BEGIN CATCH…END CATCH二个成对语句,用于捕捉运行时出现的异常。在Oracle中,可用 BEGIN…EXCEPTION…END 语句捕捉异常。

SQL代码块中加入捕捉异常的语句内,有二个好处:一是可以在SQL语句内部得到异常并作错误处理,如在错误代码块内返回自定义错误信息、ROLBACK等。这样可减少应用程序捕捉异常带来的资源开销;另外一个好处就是可以防止死锁情况的发生,当出现死锁时,SQLServer2005会抛出异常,我们就可捕捉到。

 

 

下面列出一些索引的概念,有助于设计表结构和编写SQL语句:

按照存储规则来分:

l         聚集索引:该索引中键值的逻辑顺序决定了表中相应行的物理顺序。因此一个表只能包含一个聚集索引,但该索引可以包含多个列(组合索引)。检索效率比普通索引高,但对数据新增/修改/删除的影响比较大。

l         非聚集索引:与聚集索引相对,不影响表中的数据存储顺序,检索效率比聚集索引低,对数据新增/修改/删除的影响很少。

按照维护与管理的角度来分:

l         唯一索引:惟一索引可以确保索引列不包含重复的值,可以用多个列,但是索引可以确保索引列中每个值组合都是唯一的。

l         主键索引:在数据库关系图中为表定义一个主键将自动创建主键索引,主键索引是唯一索引的特殊类型。主键索引要求主键中的每个值是唯一的。当在查询中使用主键索引时,它还允许快速访问数据。

l         普通索引:由关键字KEYINDEX定义的索引,唯一任务是加快对数据的访问速度。因此,应该只为那些最经常出现在查询条件或排序条件中的数据列创建索引。只要有可能,就应该选择一个数据最整齐、最紧凑的数据列(如整数类型的数据列)来创建索引。允许有重复的列存在。

l         复合索引:如果在两上以上的列上创建的索引,则称为复合索引。

[SQL]SQL Server 之 UNION(原创)

mikel阅读(985)

转载:http://www.cnblogs.com/netserver/archive/2008/12/25/1362409.html
UNION 将两个或更多查询的结果合并为单个结果集,该结果集包含联合查询中的所有查询的全部行。UNION 运算不同于使用联接合并两个表中的列的运算。
下面列出了使用 UNION 合并两个查询结果集的基本规则:
   1、所有查询中的列数和列的顺序必须相同。
   2、数据类型必须兼容(相同或可隐式转换)。
语法:
查询表达式1 UNION [ALL] 查询表达式2
select fields from tableA UNION select fields from tableB
查询表达式:
查询表达式或查询规范,用以返回与另一个查询规范或查询表达式所返回的数据合并的数据。作为 UNION 运算一部分的列定义可以不相同,但它们必须通过隐式转换实现兼容。如果数据类型不同,则根据数据类型优先顺序规则确定所产生的数据类型。如果类型相同,但 精度、小数位数或长度不同,则根据用于合并表达式的相同规则来确定结果。
UNION:
指定合并多个结果集并将其作为单个结果集返回。使用UNION会自动排除相同的记录,删除重复行。(类似与单一查询的DISTINCT。)
ALL:
将全部行并入结果中。其中包括重复行。如果未指定该参数,则删除重复行。

例表A:UA 例表B:UB

注意:SQL UNION会自动排除相同的记录,删除重复行。这里的相同记录指的是要用UNION合并两个查询结果的所有字段的值都相同。

select AID,ANAME,ASEX from UA union select BID,BNAME,BSEX from UB
说明:
1、UA表中有三个字段,UB表中有四个三字,但UNION考虑前后两个查询果(并不是实际表中的数据)的列数要一致,即都是三个字段,且字段的数据类型一致就行了,结果如左图所示。
如果用:select * from UA union select * from UB
或 select AID,ANAME,ASEX from UA union select BID,BNAME,BSEX,BAddress from UB
因为列数不一致,将抛出错误。
2、UNION会自动删除重复行。结果中姓名为"马艳艳"的,虽然性别、姓名都一样,但因为ID不同,则不认为是重复的记录,也就是说这里的重复记录指的是两个查询结果中的所有字段的值都是一样的。如:姓名为"邓事文"的记录则认为重复记录,只保留一条。

UNION会自动删除重复行(记录)的演示:
select AID from UA union select bid from UB
select ANAME from UA union select BNAME from UB
select ASEX from UA union select BSEX from UB
注意:因为前面的是主表,所以结果中的字段名都是UA表中的字段名。

再来看字段数据类型要一致的演示:
select AID,ANAME,ASEX from UA union select BID,BNAME,BAddress from UB
说明:
因为UA表中的ASEX与UB表中的BAddress都为varchar数据类型,所以结果如左图所示。
如果类型相同,但精度、小数位数或长度不同,则根据用于合并表达式的相同规则来确定结果。
select ANAME,ASEX from UA union select BNAME,BID from UB
select ANAME,AID from UA union select BNAME,BSEX from UB
错误提示:在将 varchar 值 '女' 转换成数据类型 int 时失败。
可以对数据类型进行转换后再UNION
select ANAME,cast(AID as varchar) from UA union select BNAME,BSEX from UB
如果用:select ANAME,AID from UA union select BNAME,BSEX from UB
不进行数据类型的转换,将会出现错误。
最后看看UNION ALL:
select AID,ANAME,ASEX from UA union all select BID,BNAME,BSEX from UB
说明:
UNION ALL 也是要将两个 SQL 语句的结果合并在一起。 UNION ALL 和 UNION 不同之处在于 UNION ALL 会将左右两个查询的结果的所有的资料都列出来,无论资料值有无重复。
左图结果中,姓名为"邓事文"的记录,在两个查询中是完全相同的。对比上面第一次试验的结果(9条记录),现在这次因为用了UNION ALL 姓名为"邓事文"的记录不管它是否重复,都分别从两个查询结果中取了出来(结果为10条记录)。

[SQL]SQL Server 2005中利用@@RowCount提高分页查询存储过程性能

mikel阅读(822)

最近发现现有框架的通用查询存储过程的性能慢,于是仔细研究了下代码:
Alter PROCEDURE [dbo].[AreaSelect]
    @PageSize int=0,
    @CurrentPage int=1,
    @Identifier int=NULL,
    @ParentId int=NULL,
    @AreaLevel int=NULL,
    @Children int=NULL,
    @AreaName nvarchar(50)=NULL,
    @Path nvarchar(MAX)=NULL,
    @Status int=NULL,
    @Alt int=NULL
AS
BEGIN
    SET NOCOUNT ON;
    IF (NOT @AreaName IS NULL)    SET @AreaName='%'+@AreaName+'%'
    IF (NOT @Path IS NULL)    SET @Path='%'+@Path+'%'
    IF (@PageSize>0)
    BEGIN
        DECLARE @TotalPage int
        Select @TotalPage=Count(Identifier) FROM Area Where
        (@Identifier IS NULL or Identifier=@Identifier)AND
        (@ParentId IS NULL or ParentId=@ParentId)AND
        (@AreaLevel IS NULL or AreaLevel=@AreaLevel)AND
        (@Children IS NULL or Children=@Children)AND
        (@AreaName IS NULL or AreaName Like @AreaName)AND
        (@Path IS NULL or Path Like @Path)AND
        (@Status IS NULL or Status=@Status)AND
        (@Alt IS NULL or Alt=@Alt)
        IF(@TotalPage%@PageSize=0)
        BEGIN
            SET @TotalPage=@TotalPage/@PageSize
        END
        ELSE
        BEGIN
            SET @TotalPage=Round(@TotalPage/@PageSize,0)+1
        END
        Select TOP (@PageSize) Identifier,ParentId,AreaLevel,Children,AreaName,Path,Status,Alt,@TotalPage as totalPage FROM Area Where
        Identifier NOT IN (Select Top (@PageSize*(@CurrentPage-1))Identifier FROM Area Where
        (@Identifier IS NULL or Identifier=@Identifier)AND
        (@ParentId IS NULL or ParentId=@ParentId)AND
        (@AreaLevel IS NULL or AreaLevel=@AreaLevel)AND
        (@Children IS NULL or Children=@Children)AND
        (@AreaName IS NULL or AreaName Like @AreaName)AND
        (@Path IS NULL or Path Like @Path)AND
        (@Status IS NULL or Status=@Status)AND
        (@Alt IS NULL or Alt=@Alt)
            order by AreaName asc)
        AND
        (@Identifier IS NULL or Identifier=@Identifier)AND
        (@ParentId IS NULL or ParentId=@ParentId)AND
        (@AreaLevel IS NULL or AreaLevel=@AreaLevel)AND
        (@Children IS NULL or Children=@Children)AND
        (@AreaName IS NULL or AreaName Like @AreaName)AND
        (@Path IS NULL or Path Like @Path)AND
        (@Status IS NULL or Status=@Status)AND
        (@Alt IS NULL or Alt=@Alt)
            order by AreaName asc
    END
    ELSE
    BEGIN
        Select Identifier,ParentId,AreaLevel,Children,AreaName,Path,Status,Alt FROM Area Where
        (@Identifier IS NULL or Identifier=@Identifier)AND
        (@ParentId IS NULL or ParentId=@ParentId)AND
        (@AreaLevel IS NULL or AreaLevel=@AreaLevel)AND
        (@Children IS NULL or Children=@Children)AND
        (@AreaName IS NULL or AreaName Like @AreaName)AND
        (@Path IS NULL or Path Like @Path)AND
        (@Status IS NULL or Status=@Status)AND
        (@Alt IS NULL or Alt=@Alt)
            order by AreaName asc
    END
END
发现每次查询都需要按条件查询依次Area表,性能太低,于是利用临时表将符合条件的记录取出来,然后针对临时表进行查询,代码修改如下:
Alter PROCEDURE [dbo].[AreaSelect]
    @PageSize int=0,
    @CurrentPage int=1,
    @Identifier int=NULL,
    @ParentId int=NULL,
    @AreaLevel int=NULL,
    @Children int=NULL,
    @AreaName nvarchar(50)=NULL,
    @Path nvarchar(MAX)=NULL,
    @Status int=NULL,
    @Alt int=NULL
AS
BEGIN
    SET NOCOUNT ON;
    IF (NOT @AreaName IS NULL)    SET @AreaName='%'+@AreaName+'%'
    IF (NOT @Path IS NULL)    SET @Path='%'+@Path+'%'
   
   
    IF (@PageSize>0)
    BEGIN
        –创建临时表
        Select
        Identifier,ParentId,AreaLevel,Children,AreaName,Path,Status,Alt
        INTO #temp_Area
        FROM Area Where
        (@Identifier IS NULL or Identifier=@Identifier)AND
        (@ParentId IS NULL or ParentId=@ParentId)AND
        (@AreaLevel IS NULL or AreaLevel=@AreaLevel)AND
        (@Children IS NULL or Children=@Children)AND
        (@AreaName IS NULL or AreaName Like @AreaName)AND
        (@Path IS NULL or Path Like @Path)AND
        (@Status IS NULL or Status=@Status)AND
        (@Alt IS NULL or Alt=@Alt)
        order by AreaName asc
        DECLARE @TotalPage int
        DECLARE @SumCount int
       
        –取总数
        Select @SumCount=Count(Identifier) FROM #temp_Area
       
        IF(@SumCount%@PageSize=0)
        BEGIN
            SET @TotalPage=@SumCount/@PageSize
        END
        ELSE
        BEGIN
            SET @TotalPage=Round(@SumCount/@PageSize,0)+1
        END
        Select TOP (@PageSize) Identifier,ParentId,AreaLevel,Children,AreaName,
        Path,Status,Alt,@TotalPage as totalPage,@SumCount as SumCount
        FROM #temp_Area
        Where
        Identifier NOT IN (Select Top (@PageSize*(@CurrentPage-1))Identifier FROM #temp_Area))
    END
    ELSE
    BEGIN
        Select Identifier,ParentId,AreaLevel,Children,AreaName,Path,Status,Alt FROM Area Where
        (@Identifier IS NULL or Identifier=@Identifier)AND
        (@ParentId IS NULL or ParentId=@ParentId)AND
        (@AreaLevel IS NULL or AreaLevel=@AreaLevel)AND
        (@Children IS NULL or Children=@Children)AND
        (@AreaName IS NULL or AreaName Like @AreaName)AND
        (@Path IS NULL or Path Like @Path)AND
        (@Status IS NULL or Status=@Status)AND
        (@Alt IS NULL or Alt=@Alt)
            order by AreaName asc
    END
END
经过使用临时表的确提高性能,不过有发现一个问题,就是count(Identifier)的确很耗性能,于是又进行修改了

Alter PROCEDURE [dbo].[AreaSelect]
    @PageSize int=0,
    @CurrentPage int=1,
    @Identifier int=NULL,
    @ParentId int=NULL,
    @AreaLevel int=NULL,
    @Children int=NULL,
    @AreaName nvarchar(50)=NULL,
    @Path nvarchar(MAX)=NULL,
    @Status int=NULL,
    @Alt int=NULL
AS
BEGIN
    SET NOCOUNT ON;
    IF (NOT @AreaName IS NULL)    SET @AreaName='%'+@AreaName+'%'
    IF (NOT @Path IS NULL)    SET @Path='%'+@Path+'%'
   
   
    IF (@PageSize>0)
    BEGIN
        –创建中记录数
        DECLARE @SumCount int
       
        –创建临时表
        Select
        Identifier,ParentId,AreaLevel,Children,AreaName,Path,Status,Alt
        INTO #temp_Area
        FROM Area Where
        (@Identifier IS NULL or Identifier=@Identifier)AND
        (@ParentId IS NULL or ParentId=@ParentId)AND
        (@AreaLevel IS NULL or AreaLevel=@AreaLevel)AND
        (@Children IS NULL or Children=@Children)AND
        (@AreaName IS NULL or AreaName Like @AreaName)AND
        (@Path IS NULL or Path Like @Path)AND
        (@Status IS NULL or Status=@Status)AND
        (@Alt IS NULL or Alt=@Alt)
        order by AreaName asc
        –设置总记录数为刚操作的记录数
        SET @SumCount=@@RowCount
       
        DECLARE @TotalPage int
       
        IF(@SumCount%@PageSize=0)
        BEGIN
            SET @TotalPage=@SumCount/@PageSize
        END
        ELSE
        BEGIN
            SET @TotalPage=Round(@SumCount/@PageSize,0)+1
        END
        Select TOP (@PageSize) Identifier,ParentId,AreaLevel,Children,AreaName,
        Path,Status,Alt,@TotalPage as totalPage,@SumCount as SumCount
        FROM #temp_Area
        Where
        Identifier NOT IN (Select Top (@PageSize*(@CurrentPage-1))Identifier FROM #temp_Area))
    END
    ELSE
    BEGIN
        Select Identifier,ParentId,AreaLevel,Children,AreaName,Path,Status,Alt FROM Area Where
        (@Identifier IS NULL or Identifier=@Identifier)AND
        (@ParentId IS NULL or ParentId=@ParentId)AND
        (@AreaLevel IS NULL or AreaLevel=@AreaLevel)AND
        (@Children IS NULL or Children=@Children)AND
        (@AreaName IS NULL or AreaName Like @AreaName)AND
        (@Path IS NULL or Path Like @Path)AND
        (@Status IS NULL or Status=@Status)AND
        (@Alt IS NULL or Alt=@Alt)
        order by AreaName asc
    END
END

[SQL]利用@@RowCount优化性能

mikel阅读(853)

每次我们在使用查询分析器调试SQL语句的时候,通常会看到一些信息,提醒我们当前有多少个行受到了影响,这是些什么信息?在我们调用的时候这些信息有用吗?是否可以关闭呢?

答案是这些信息在我们的客户端的应用程序中是没有用的,这些信息是存储过程中的每个语句的DONE_IN_PROC 信息。

我们可以利用SET NOCOUNT 来控制这些信息,以达到提高程序性能的目的。

MSDN中帮助如下:
 
SET NOCOUNT
使返回的结果中不包含有关受 Transact-SQL 语句影响的行数的信息。

语法
SET NOCOUNT { ON | OFF }

注释
当 SET NOCOUNT 为 ON 时,不返回计数(表示受 Transact-SQL 语句影响的行数)。当 SET NOCOUNT 为 OFF 时,返回计数。

即使当 SET NOCOUNT 为 ON 时,也更新 @@ROWCOUNT 函数。

当 SET NOCOUNT 为 ON 时,将不给客户端发送存储过程中的每个语句的 DONE_IN_PROC 信息。当使用 Microsoft SQL Server 提供的实用工具执行查询时,在 Transact-SQL 语句(如 Select、Insert、Update 和 Delete)结束时将不会在查询结果中显示"nn rows affected"。

如果存储过程中包含的一些语句并不返回许多实际的数据,则该设置由于大量减少了网络流量,因此可显著提高性能。

SET NOCOUNT 设置是在执行或运行时设置,而不是在分析时设置。

权限
SET NOCOUNT 权限默认授予所有用户。

结论:我们应该在存储过程的头部加上SET NOCOUNT ON 这样的话,在退出存储过程的时候加上 SET NOCOUNT OFF这样的话,以达到优化存储过程的目的。

多说两句:

1:在查看SQLServer的帮助的时候,要注意“权限”这一节,因为某些语句是需要一定的权限的,而我们往往忽略。

2:@@ROWCOUNT是返回受上一语句影响的行数,包括找到记录的数目、删除的行数、更新的记录数等,不要认为只是返回查找的记录数目,而且@@ROWCOUNT要紧跟需要判断语句,否则@@ROWCOUNT将返回0。

3:如果使用表变量,在条件表达式中要使用别名来替代表名,否则系统会报错。

4:在CUD类的操作中一定要有事务处理。

5:使用错误处理程序,用来检查 @@ERROR 系统函数的 T-SQL 语句 (IF) 实际上在进程中清除了 @@ERROR 值,无法再捕获除零之外的任何值,必须使用 SET 或 Select 立即捕获错误代码。

[SQL]排名函数是SQL Server2005新加的功能。在SQL Server2005中有如下四

mikel阅读(907)

1. row_number

2. rank

3. dense_rank

4. ntile   
    下面分别介绍一下这四个排名函数的功能及用法。在介绍之前假设有一个t_table表,表结构与表中的数据如图1所示:


图1

其中field1字段的类型是intfield2字段的类型是varchar

一、row_number

    row_number函数的用途是非常广泛,这个函数的功能是为查询出来的每一行记录生成一个序号。row_number函数的用法如下面的SQL语句所示:

 

select row_number() over(order by field1) as row_number,* from t_table

    上面的SQL语句的查询结果如图2所示。


图2

    其中row_number列是由row_number函数生成的序号列。在使用row_number函数是要使用over子句选择对某一列进行排序,然后才能生成序号。

    实际上,row_number函数生成序号的基本原理是先使用over子句中的排序语句对记录进行排序,然后按着这个顺序生成序号。over子句中的order by子句与SQL语句中的order by子句没有任何关系,这两处的order by 可以完全不同,如下面的SQL语句所示:

 

select row_number() over(order by field2 descas row_number,* from t_table order by field1 desc

    上面的SQL语句的查询结果如图3所示。


图3

    我们可以使用row_number函数来实现查询表中指定范围的记录,一般将其应用到Web应用程序的分页功能上。下面的SQL语句可以查询t_table表中第2条和第3条记录:

 

with t_rowtable
as
(
    
select row_number() over(order by field1) as row_number,* from t_table
)
select * from t_rowtable where row_number>1 and row_number < 4 order by field1

    上面的SQL语句的查询结果如图4所示。


图4

    上面的SQL语句使用了CTE,关于CTE的介绍将读者参阅《SQL Server2005杂谈(1):使用公用表表达式(CTE)简化嵌套SQL》
    另外要注意的是,如果将row_number函数用于分页处理,over子句中的order by 与排序记录的order by 应相同,否则生成的序号可能不是有续的。
    当然,不使用row_number函数也可以实现查询指定范围的记录,就是比较麻烦。一般的方法是使用颠倒Top来实现,例如,查询t_table表中第2条和第3条记录,可以先查出前3条记录,然后将查询出来的这三条记录按倒序排序,再取前2条记录,最后再将查出来的这2条记录再按倒序排序,就是最终结果。SQL语句如下:

 

select * from (select top 2 * fromselect top 3 * from t_table order by field1) a order by field1 desc) b order by field1

    上面的SQL语句查询出来的结果如图5所示。


图5

    这个查询结果除了没有序号列row_number,其他的与图4所示的查询结果完全一样。

二、rank

    rank函数考虑到了over子句中排序字段值相同的情况,为了更容易说明问题,在t_table表中再加一条记录,如图6所示。


图6

    在图6所示的记录中后三条记录的field1字段值是相同的。如果使用rank函数来生成序号,这3条记录的序号是相同的,而第4条记录会根据当前的记录 数生成序号,后面的记录依此类推,也就是说,在这个例子中,第4条记录的序号是4,而不是2。rank函数的使用方法与row_number函数完全相 同,SQL语句如下:

select rank() over(order by field1),* from t_table order by field1

    上面的SQL语句的查询结果如图7所示。


图7

三、dense_rank

    dense_rank函数的功能与rank函数类似,只是在生成序号时是连续的,而rank函数生成的序号有可能不连续。如上面的例子中如果使用dense_rank函数,第4条记录的序号应该是2,而不是4。如下面的SQL语句所示:

select dense_rank() over(order by field1),* from t_table order by field1

    上面的SQL语句的查询结果如图8所示。


图8

    读者可以比较图7和图8所示的查询结果有什么不同

四、ntile
    ntile函数可以对序号进行分组处理。这就相当于将查询出来的记录集放到指定长度的数组中,每一个数组元素存放一定数量的记录。ntile函数为每条记 录生成的序号就是这条记录所有的数组元素的索引(从1开始)。也可以将每一个分配记录的数组元素称为“桶”。ntile函数有一个参数,用来指定桶数。下 面的SQL语句使用ntile函数对t_table表进行了装桶处理:

select ntile(4over(order by field1) as bucket,* from t_table

    上面的SQL语句的查询结果如图9所示。


图9

    由于t_table表的记录总数是6,而上面的SQL语句中的ntile函数指定了桶数为4

    也许有的读者会问这么一个问题,SQL Server2005怎么来决定某一桶应该放多少记录呢?可能t_table表中的记录数有些少,那么我们假设t_table表中有59条记录,而桶数是5,那么每一桶应放多少记录呢?

    实际上通过两个约定就可以产生一个算法来决定哪一个桶应放多少记录,这两个约定如下:

1. 编号小的桶放的记录不能小于编号大的桶。也就是说,第1捅中的记录数只能大于等于第2桶及以后的各桶中的记录。

2. 所有桶中的记录要么都相同,要么从某一个记录较少的桶开始后面所有捅的记录数都与该桶的记录数相同。也就是说,如果有个桶,前三桶的记录数都是10,而第4捅的记录数是6,那么第5桶和第6桶的记录数也必须是6

    根据上面的两个约定,可以得出如下的算法:

    // mod表示取余,div表示取整 
    if(记录总数 mod 桶数 == 0)
    {
        recordCount 
= 记录总数 div 桶数;
        将每桶的记录数都设为recordCount
    } 
    
else
    {
        recordCount1 
= 记录总数 div 桶数 + 1;
        
int n = 1;  //  n表示桶中记录数为recordCount1的最大桶数
        m = recordCount1 * n;
        
while(((记录总数  m)  mod  (桶数   n))  != 0 )
        {
            n
++;
            m 
= recordCount1 * n;
        } 
        recordCount2 
= (记录总数  m) div  (桶数  n);
        将前n个桶的记录数设为recordCount1
        将n 
+ 1个至后面所有桶的记录数设为recordCount2
    }

    根据上面的算法,如果记录总数为59,桶数为5,则前4个桶的记录数都是12,最后一个桶的记录数是11

    如果记录总数为53,桶数为5,则前3个桶的记录数为11,后2个桶的记录数为10

    就拿本例来说,记录总数为6,桶数为4,则会算出recordCount1的值为2,在结束while循环后,会算出recordCount2的值是1,因此,前2个桶的记录是2,后2个桶的记录是1

[SQL]SQL Server 2005对海量数据的处理

mikel阅读(912)

 超大型数据库的 大小常常达到数百GB,有时甚至要用TB来计算。而单表的数据量往往会达到上亿的记录,并且记录数会随着时间而增长。这不但影响着数据库的运行效率,也增 大数据库的维护难度。除了表的数据量外,对表不同的访问模式也可能会影响性能和可用性。这些问题都可以通过对大表进行合理分区得到很大的改善。当表和索引 变得非常大时,分区可以将数据分为更小、更容易管理的部分来提高系统的运行效率。如果系统有多个CPU或是多个磁盘子系统,可以通过并行操作获得更好的性能。所以对大表进行分区是处理海量数据的一种十分高效的方法。本文通过一个具体实例,介绍如何创建和修改分区表,以及如何查看分区表。

1、SQL Server 2005

  SQL Server 2005是微软在推出SQL Server 2000后时隔五年推出的一个数据库平台,它的数据库引擎为关系型数据和结构化数据提供了更安全可靠的存储功能,使用户可以构建和管理用于业务的高可用和高性能的数据应用程序。此外,SQL Server 2005结合了分析、报表、集成和通知功能。这使得企业可以构建和部署经济有 效的BI解决方案,帮助团队通过记分卡、Dashboard、Web Services和移动设备将数据应用推向业务的各个领域。无论是开发人员、数据库管理员、信息工作者还是决策者,SQL Server 2005都可以提供出创新的解决方案,并可从数据中获得更多的益处。

  它所带来的新特性,如T-SQL的增强、数据分区、服务代理和与.Net Framework的集成等,在易管理性、可用性、可伸缩性和安全性等方面都有很大的增强。

2、表分区的具体实现方法

  表分区分为水平分区和垂直分区。水平分区将表分为多个表。每个表包含的列数相同,但是行更少。例如,可以将一个包含十亿行的表水平分区成 12 个表,每个小表表示特定年份内一个月的数据。任何需要特定月份数据的查询只需引用相应月份的表。而垂直分区则是将原始表分成多个只包含较少列的表。水平分 区是最常用分区方式,本文以水平分区来介绍具体实现方法。

  水平分区常用的方法是根据时期和使用对数据进行水平分区。例如本文例子,一个短信发送记录表包含最近一年的数据,但是只定期访问本季度的数据。在这种情况下,可考虑将数据分成四个区,每个区只包含一个季度的数据。

2.1、创建文件组

  建立分区表先要创建文件组,而创建多个文件组主要是为了获得好的 I/O 平衡。一般情况下,文件组数最好与分区数相同,并且这些文件组通常位于不同的磁盘上。每个文件组可以由一个或多个文件构成,而每个分区必须映射到一个文件 组。一个文件组可以由多个分区使用。为了更好地管理数据(例如,为了获得更精确的备份控制),对分区表应进行设计,以便只有相关数据或逻辑分组的数据位于同一个文件组中。使用 Alter DATABASE,添加逻辑文件组名:
Alter DATABASE [DeanDB] ADD FILEGROUP [FG1]
  DeanDB为数据库名称,FG1文件组名。创建文件组后,再使用 Alter DATABASE 将文件添加到该文件组中:
Alter DATABASE [DeanDB] ADD FILE ( NAME = N'FG1',
FILENAME = N'C:DeanDataFG1.ndf' , SIZE = 3072KB ,
FILEGROWTH = 1024KB ) TO FILEGROUP [FG1]
  类似的建立四个文件和文件组,并把每一个存储数据的文件放在不同的磁盘驱动器里。

2.2、创建分区函数

  创建分区表必须先确定分区的功能机制,表进行分区的标准是通过分区函数来决定的。创建数据分区函数有RANGE “LEFT | / RIGHT”两种选择。代表每个边界值在局部的哪一边。例如存在四个分区,则定义三个边界点值,并指定每个值是第一个分区的上边界 (LEFT) 还是第二个分区的下边界 (RIGHT)。代码如下:
Create PARTITION FUNCTION [SendSMSPF](datetime) 
AS RANGE RIGHT FOR VALUES ('20070401', '20070701', '20071001')
2.3、创建分区方案

  创建分区函数后,必须将其与分区方案相关联,以便将分区指向至特定的文件组。就是定义实际存放数据的媒体与各数据块的对应关系。多个数据表可以共用相同的 数据分区函数,一般不共用相同的数据分区方案。可以通过不同的分区方案,使用相同的分区函数,使不同的数据表有相同的分区条件,但存放在不同的媒介上。创 建分区方案的代码如下:
Create PARTITION SCHEME [SendSMSPS] AS PARTITION [SendSMSPF]
TO ([FG1], [FG2], [FG3], [FG4])
2.4、创建分区表

  建立好分区函数和分区方案后,就可以创建分区表了。分区表是通过定义分区键值和分区方案相联系的。插入记录时,SQL SERVER会根据分区键值的不同,通过分区函数的定义将数据放到相应的分区。从而把分区函数、分区方案和分区表三者有机的结合起来。创建分区表的代码如 下:
Create TABLE SendSMSLog
([ID] [int] IDENTITY(1,1) NOT NULL,
[IDNum] [nvarchar](50) NULL,
[SendContent] NULL
[SendDate] [datetime] NOT NULL,
) ON SendSMSPS(SendDate)
2.5、查看分区表信息

  系统运行一段时间或者把以前的数据导入分区表后,我们需要查看数据的具体存储情况,即每个分区存取的记录数,那些记录存取在那个分区等。我们可以通过$partition.SendSMSPF来查看,代码如下:
Select $partition.SendSMSPF(o.SendDate)
AS [Partition Number]
, min(o.SendDate) AS [Min SendDate]
, max(o.SendDate) AS [Max SendDate]
, count(*) AS [Rows In Partition]
FROM dbo.SendSMSLog AS o
GROUP BY $partition.SendSMSPF(o.SendDate)
ORDER BY [Partition Number]
在查询分析器里执行以上脚本。

2.6、维护分区

  分区的维护主要设计分区的添加、减少、合并和在分区间转换。可以通过Alter PARTITION FUNCTION的选项SPLIT,MERGE和Alter TABLE的选项SWITCH来实现。SPLIT会多增加一个分区,而MEGRE会合并或者减少分区,SWITCH则是逻辑地在组间转换分区。

3、性能对比

  我们对2650万数据,存储空间占用约4G的单表进行性能对比,测试环境为IBM365,CPU 至强2.7G*2、内存 16G、硬盘 136G*2,系统平台为Windows 2003 SP1+SQL Server 2005 SP1。

说明:

1、根据时间检索某一天记录所耗时间
2、单条记录插入所耗时间
3、根据时间删除某一天记录所耗时间
4、统计每月的记录数所需时间

  从表1可以看出,对分区表进行操作比未分区的表要快,这是因为对分区表的操作采用了CPU和I/O的并行操作,检索数据的数据量也变小了,定位数据所耗时间变短。

4、结束语

  对海量数据的处理一直是一个令人头痛的问题。分离的技术是所有设计者们首先考虑的问题,不管是分离应用程序功能还是分离数据访问,如果加以了合理规划,都 能十分有效的解决大数据表的运行效率低和维护成本高等问题。SQL Server 2005新增的表分区功能,可以对数据进行合理分区,当用户在访问部分数据时,SQL Server最佳化引擎可以根据数据的实体存放,找出最佳的执行方案,而不至于大海捞针。