我们向数据库提交了一个查询,假如我们的语句是正确的,我们理所当然的期望的到一个正确的结果。
如果要牛角尖一下,怎样才是一个正确的查询呢?
显然首先你查询的对象是存在的,不管是表还是视图,函数或者存储过程。你指定的条件是合法,不管是Jion的条件,Where中的条件,还是Having中的条件。你希望展现的列是存在的,如果一个表里存放的机器的话,你不大可能从里面选出一个性别的字段。
那么我再问你,如果你的查询要1天才能完成,而稍作改动就可以在1分钟内完成,你的查询还是正确的吗?
如果你查询让数据库引擎占用的内存暴涨,让服务器几乎陷于停顿,而这个查询本可以让1000个人同时使用而畅通无阻,你的查询还是正确的吗?
可见要写出一个正确的查询,绝不仅仅需要知道SQL的语法,还需要了解这个查询语句是如何被处理,如何从硬盘上那一堆0或1中组合成我们需要的结果的。本文就试着回答这个问题。
首先让我们来看一个完整的SQL语句,它包含所有可能的元素:
(8) SELECT (9) DISTINCT (11) <TOP_specification> <select_list>
(1) FROM <left_table>
(3) <join_type> JOIN <right_table>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) WITH {CUBE | ROLLUP}
(7) HAVING <having_condition>
(10) ORDER BY <order_by_list>
请注意那些写在括号中的数字,它们代表的是各个部分被处理的顺序,每一步生成一个虚拟表,该表作为下一步的输入并生成一个新的虚拟表。
做一个简单的说明把:
首先,From后的两个表做笛卡尔积,插入VT1中;
接着,使用On的条件,将VT1中符合的行放入VT2中;
然后,将VT2中符合Where条件的放入VT3中;
随后,Group by 将VT3分组放入VT4中;
With Cube|RollUp进一步产生分组的方法,新的行和VT4加入VT5中;
后面的您就可以明白了。
虽然所有的SQL都遵循这一个过程,过程的每一步的代价是不同的, 甚至访问表的顺序也会导致性能的巨大差异。作为数据库引擎中最重要的组件,查询优化器的功能便是为查询语句找到一个最优的执行计划。
查询优化器有两个输入,一个是我们的查询语句,另一个是存放在数据引擎的统计信息,例如表格有多少行,索引树有多深,每个索引也多少键值等。基于这两个输入,它输出的查询计划包括源表被访问的顺序,以及从源表中提取数据的方法,可以是扫描表的所有Page,也可以是查找索引树。
关于执行计划您可以通过Management Studio的查询窗口上右键菜单:Include actual execution plan 来查看。它显示的执行计划包含了SQL被分解成的所有操作,以及各个操作的代价。我们在此不详细讨论这些操作。
正如上面所讲的,翻译查询计划并不是一个容易的工作,尤其是在面对复杂的查询的时候,因为要比较众多的可能路径的代价。我们显然不希望这样一个宝贵的执行计划被随便的抹掉,这样复杂的过程不必要的重复。
数据库引擎也不愿意,所以它用一块专门的内容缓冲区来存放执行计划。
在数据库引擎里,执行计划由QueryPlan和执行上下文组成。QueryPlan都是一个只读的,可重入的数据结构,并且从来不会超过两份,一份是顺序执行,另一份是并行执行。执行上下文存放专用于一次调用的内容,例如参数,用户权限等。
执行计划在缓冲区中并不会一直存在,它有一个参数表示它的生命值。该值在执行计划每次被调用时就会增加,每一个脉冲,生命值减少1.因此产生该计划的代价越大,它留在在缓冲区的时间就越长。
当数据库执行一个查询语句,为之生成执行计划之前,它会首先扫描存储过程的缓冲区,以寻找匹配的执行计划。在匹配执行计划时,可能采用全字符匹配,也可能采用简单参数化的方法。如果用户将数据库的Parameterization选项设置成Forced的话,执行计划的源查询语句会被全面参数化,即所有常数会被替换成占位符,这个占位符可以和任何其他常数匹配,这样就会导致更多的查询语句被匹配到该查询计划。
如果数据库服务器是多CPU的,数据库引擎还会产生并行执行计划。一类特殊的操作:Exchange负责处理并发的相关操作,成员包括Distribute Streams, Repartition Streams以及Gather Streams。查询计划的并发度受查询语句本身和查询对象属性的影响。通常来说系统闲散线程越多,CPU越多,查询对象行数越多,并发可能性就越大,并发度也越高。另外我们可以配置并发查询的起始值和并发查询的最大并发度。有时候并发度太高就意味着占用更多的资源,着在多用户环境中并不总是友好的。
如果查询的数据分布在不同的数据库服务器上,我们需要借助于连接装置进行访问,最简单的连接方式是Linked Server,如下所示:
SELECT Title, HireDate
FROM DeptSQLSrvr.AdventureWorks.HumanResources.Employee
或者通过OpenRowSet和OpenDataSource
SELECT *
FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'c:/MSOffice/Access/Samples/Northwind.mdb';'Admin';'';
Employees)
前者适用于较经常的访问,后者适用于不那么频繁的访问。事实上,SQL Server查理查询的两个关键组件是查询解析和存储管理,她们之间使用OLE DB来连接。当访问远程Server的数据时,本地的解析器将查询分解成具体的操作,通过OLE DB调用远端server的存储管理组件,后者处理这些操作并返回结果。