上图显示了一个SQL命令的执行过程,为了能使每个语句能高效的执行,我们应该尽量在关系层来完成所有的操作。举个很简单的例子:
SELECT TOP 10 P.Name,P.Color,PSC.Name AS SubcategoryName,PC.Name AS CategoryName,D.DocumentSummary,
PP.LargePhoto,SUM(LineTotal) LineTotal
FROM Production.Product P
JOIN Production.ProductSubcategory PSC
ON P.ProductSubcategoryID = PSC.ProductSubcategoryID
JOIN Production.ProductCategory PC
ON PSC.ProductCategoryID = PC.ProductCategoryID
JOIN Production.ProductProductPhoto PPP
ON P.ProductID = PPP.ProductID
JOIN Production.ProductPhoto PP
ON PP.ProductPhotoID = PPP.ProductPhotoID
JOIN Sales.SalesOrderDetail OD
ON OD.ProductID = P.ProductID
LEFT JOIN Production.ProductDocument PD
ON PD.ProductID = P.ProductID
LEFT JOIN Production.Document D
ON D.DocumentID = PD.DocumentID
GROUP BY P.Name,P.Color,PSC.Name,PC.Name,D.DocumentSummary,
PP.LargePhoto
ORDER BY LineTotal DESC
此语句违反了尽量在关系层操作的宗旨,是把所有的结果取出后,再根据LineTotal进行排序。此语句因为排序后只取前10条结果,但是排序所针对的结果集是整个的查询结果。因此所操作的内存是很大的,因此我们应该减少排序结果集的大小。下面的查询首先在一个子查询中把前10个订单查出后,再和其它的表进行关系,减少了排序操作的内存量。两者的查询开销比例为99:1,可见我们在编写分页过程时首先要根据排序字段筛选出要关联的字段键值后再进行相关表的连接。
SELECT P.Name,P.Color,PSC.Name AS SubcategoryName,PC.Name AS CategoryName,D.DocumentSummary,
PP.LargePhoto,OD.LineTotal
FROM Production.Product P
JOIN Production.ProductSubcategory PSC
ON P.ProductSubcategoryID = PSC.ProductSubcategoryID
JOIN Production.ProductCategory PC
ON PSC.ProductCategoryID = PC.ProductCategoryID
JOIN Production.ProductProductPhoto PPP
ON P.ProductID = PPP.ProductID
JOIN Production.ProductPhoto PP
ON PP.ProductPhotoID = PPP.ProductPhotoID
JOIN
(
SELECT TOP 10 ProductID,SUM(LineTotal) LineTotal
FROM Sales.SalesOrderDetail
GROUP BY ProductID
ORDER BY LineTotal DESC
) OD
ON OD.ProductID = P.ProductID
LEFT JOIN Production.ProductDocument PD
ON PD.ProductID = P.ProductID
LEFT JOIN Production.Document D
ON D.DocumentID = PD.DocumentID
另外,在分页过程中会有一个记录总数,有的过程中会执行两次查询,一次返回记录数,一次查询根据筛选条件及当前需要返回其中哪些记录再进行一次查询。如果筛选条件不够精确,进行表扫描就很很耗时。因此不如一次查询把结果放入一个表变量中,此表变量中有个自增字段,根据当前第几页再筛选当前页的记录。使用@@rowcount得到总记录数。
关于@@rowcount的使用:对一个表,如果此记录存在就更新,如果不存在就插入记录。过程中可能会先使用if exists(select * from table where key='aa') update table Else insert into table.
上面的查询其实多了一次操作。如果直接update后根据@@rowcount为0时。就直接插入,则避免了判断存在的查询。Update table if @@rowcount=0 insert into
获取远程数据
在对链接数据库操作时,应该尽量减少操作的次数,这和从程序中发送命令到数据库一个道理。你在本机直接操作远程机的表时,所有的筛选条件都不会直接应用到远程机。而是把结果返回到本机后,再对所有远程表的数据进行筛选。因此,你只能调用远程机的存储过程才能高效的执行相关的命令。
多数据文件
我发现所有的项目里都是一个主文件组,一个数据文件,然后就是一个日志文件。因为日志是顺序写入的,所以增加日志文件的数量是不会改善性能的。但是如果数据库被应用了镜像或日志传送等需要读取日志文件时,增加日志文件数会稍微改善一下read的性能。但是数据文件不是顺序写入的。我们应该把一些读写过热的表,分别保存到不同的物理磁盘文件中。这在磁盘被条带化时,可能改善不了多少性能。但是多文件,在数据库被还原时,可以把不同的文件分别存放到不同的磁盘,以解决存储空间不足时的尴尬。现在2005有了数据分区的功能,可能我们才会想到创建多文件。但是使用分区其实是个很危险的操作,尤其是在写操作过频繁的系统中,很容易造成资源争用问题。
慎用触发器
我们知道索引除了占用磁盘空间,在索引字段数据被修改时,还会进行额外的索引维护操作。每加多一个索引,记录插入的速度就会慢一倍多。触发器其实比索引更厉害,它的开销是一个索引的两倍还多,因此除非不得以,不应该在表上定义过多的触发器。
过程中返回一条记录时的处理办法
很多过程只返回一条记录,然后作为一个结果集返回给程序,程序里不管使用datareader还是adapter,都是在操作一个结果集。这个结果集中包括一些元数据的信息,这些元数据就造成了额外的字节传送。因此,这样的过程中直接使用输出参数能减少传送的字节数。