MSSQL性能优化T-SQL篇
引用出处
1 、赵睿编写的T-SQL性能优化
2、老大的培训和原则性指导以及和同事的交流沟通、 自身经验等等
3、 http://www.cnblogs.com/ziyiFly/archive/2008/12/24/.html SQL优化原则
4、 http://www.cnblogs.com/wxj1020/archive/2008/04/27/.html SQL语句优化技术分析
5、 http://kb.cnblogs.com/page//SQL Server优化50法
6、 http://www.cnblogs.com/lyhabc/archive/2013/06/16/.html即席查询
问题的提出
对于查询SQL语句复杂视图的的编写等体会不出SQL语句各种写法的性能优劣但是如果将应用系统提交实际应用后随着数据库中数据的增加系统的响应速度就成为目前系统需要解决的最主要的问题之一。系统优化中一个很重要的方面就是SQL语句的优化。对于海量数据劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍可见对于一个系统不是简单地能实现其功能就可而是要写出高质量的SQL语句提高系统的可用性。
在多数情况下MSSQL使用索引来更快地遍历表优化器主要根据定义的索引来提高性能。但是如果在SQL语句的where子句中写的SQL代码不合理就会造成优化器删去索引而使用全表扫描一般就这种SQL语句就是所谓的劣质SQL语句。在编写SQL语句时我们应清楚优化器根据何种原则来删除索引这有助于写出高性能的SQL语句。
T-SQL优化推荐
1、不要访问多于你需要的数据
这个听起来是多余的但是确实是非常必要的做起来也不大容易。其中包括不要返回给客户端不需要的列和行。比如在SELECT语句中不要使用SELECT* 否则会经常返回给客气端多于它们所需要的数据这样可以有效减轻网络传输压力减少不必要的I/O以及减少内存耗费并能够使查询优化器最优化我们的查询执行计划从而减少潜在的性能问题。
2、所有者.对象名ObjectOwnerName.ObjectName
建议在所有对表、视图和存储过程和函数引用时加上它们的所有者架构 schema 前缀。下面讨论一下为什么使用前缀可以改进查询性能。
例子即席查询测试
SELECT ProID,ProName,Suppl ierID,Suppl ier
FROM ERP_Products
当用户Lucy执行以上语句时查询优化器必须决定是检索Lucy ERP_Products还是检索dboERP_Products。然后当用户Li ly调用同一个TSQL语句或存储过程时查询优化器必须对查询计划进行重新编译以决定用户是需要Li ly ERP_Products还是需要dbo ERP_Products。 但是如果把上面的SELECT语句修改如下
SELECT ProID,ProName,Suppl ierID,Suppl ier
FROM dbo ERP_Products
查询优化器将不会遇到任何模糊性从而避免重新编译达到提升性能的目的。
3、 Union&UnionAl l
首先讲一下UNION的工作原理当使用UNION语句时它的功能与在结果集上SELECTDISTINCT类似也就是说使用UNION时它会首先合并两个结果集然后执行一个类似于SELECT DISTINCT的操作以避免重复行的出现。这个过程在两上结果集没有任何重复行的情况下也会进行DISTINCT处理所以如果我们确认在UNION的两个结果集确实存在重复行并且要消除重复行的出现时就可以使用UNION从另一方面来说如果我们知道在结果集中并不会存在重复行或者说出现重复行对我们的应用程序没有什么影响时我们应该使用UNIONALL语句来替代UNION语句UNIONALL和UNION相比的优点在于它并不会对两个结果集进行SELECT DISTINCT操作这样可以节省SQLServer的资源使用。
例子简单测试
--UNIONALL
SELECT 1 AS Number,2 ProID
UNIONALL
SELECT 1 AS Number,2 ProID
--UNION
SELECT 1 AS Number,2 ProID
UNION
SELECT 1 AS Number,2 ProID
查询结果如下
下面来看性能方面的对比分析下面的执行计划我们可以看到第二个操作非常消耗资源如果两个查询一块执行时第二个查询几乎使用了所有的数据库资源主要消耗在Agg regate中的消除重复行部分
结论在对UNION和UNIONALL进行选择时除非必要推荐使用UNIONALL
4、 Union&Jion
在比较UNION和UNIONALL的基础上下面来看UNION合并结果集和使用JION查询得到结果集之间的差别。
例子使用UNION来合并多个结果集的情况
USE upay;
GO
SELECT * FROM dbo.ERP_ProductSKUs WHERE UnitsInStock < 1000
UNION ALL
SELECT * FROM dbo.ERP_ProductSKUs WHERE UnitsInStock > 2000
上面的查询可以被重写为下面的查询这样一般就会有性能提升
USE upay;
GO
SELECT DISTINCT * FROM dbo.ERP_ProductSKUs
WHERE UnitsInStock < 1000 OR UnitsInStock > 2000
NOTE如果知道结果集中并不存在重复的数据行我们也可以使用UNIONALL来提高性能。但是UnionAl l性能一般来说比Union要好但是比Join要差。
查看它们的运行结果可以发现运行结果是相同的现在来分析一下它们的执行计划和性能差别
可见使用UNION时执行两次CLUSTERED索引的扫描并且要把结果使用DISTINCT合并起来而第二个查询只要进行一次CLUSTERED索引进行扫描然后直接展现出来从而大大提高了性能。
但是如果能够使用JOIN达到目的的话我们建议不要使用Union和UnionALL而是直接使用JOIN来取我们需要的数据。
5、 Distinct
有开发人员在写查询的时候不管是否需要都习惯性的在查询中写上DISTINCT,这是一个很不好的习惯特别是当我们的查询中包含很大数据量的时候更会大大消耗有限的数据库资源并降低的应用程序性能。
DISTINCT应该在确认结果集中存在重复行并且这些重复的确数据并不是我们所需要的数据时才能够使用。这是因为DISTINCT在数据库上执行了很多额外的操作从而消耗了很多数据库资源这样会减少别的查询在执行时所能够使用的资源增加数据库出现性能问题的几率。例子
USE upay;
GO
SELECT DISTINCT PerName
FROM dbo.Person20 AS T
INNER JOIN dbo.ERP_POrders AS T2 ON T.PerID = T2.SellerID
这是一个非常简单的SELECT DISTINCT查询语句的示例但是当我们执行一个复杂的语句时就应该考虑重新编码以达到性能的要求如下面的语句当返回ERP_POrders表中已经下了订单的用户很多人会像下面这样写
其实我们可以重写这个语句来提升我们查询语句的性能
USE upay;
GO
SELECT PerName
FROM dbo.Person20 AS T
WHERE EXISTS(SELECT 1 FROM dbo.ERP_POrders WHERE SellerID = T.PerID )
上面的查询能够提升性能是因为如果某个用户下了多个订单当查到这个用户的第个订单时就会停止对这个用户的处理。
我们要慎重考虑是不是真的需要DISTINCT
6、 TOP N
如果我们的应用程序要返回上千行乃至上万行数据的时候,我们要考虑是不是真正的需要这么多数据是不是可以使用TOP操作符来限制返回给客户端的行数或者返回给客户端结果集行的百分比这样可以减少资源的使用提高数据库性能并且有效节省带宽
下面说一下关于减少网络传输压力的内容。如果我们每次多返回给客户端10行数据每行多返回200个字节每天10000次被执行这就是一笔不小的网络传输成本(10*200*10000 Byte) 另外如果再加上传输图片等将要占用更多的带宽。
例子
USE upay;
GO
SELECT TOP 1 PerID, PerName FROM dbo.Person20
WHERE PerName LIKE '赵%'
这时如果有100,000行数据符合WHERE条件也只返回限制的1行结果集。 因为SQLServer在处理时当结果集的行数达到TOP中指定的数目时所有的处理都将停止这样就可以潜在的提高SQLServer的负载增加性能。
此外TOP操作还可以让我们指定返回给客户端结果集行数的百分比如:
USE upay;
GO
SELECT TOP 1 PERCENT PerID, PerName FROM dbo.Person20
WHERE PerName LIKE '赵%'
NOTE如果一个SELECT语句既包含TOP又包含ORDER BY子句那么返回的行将会从排序好的结果集中选择并且只返回已排好序结果集的前n行或者前百分之N行 。
7、 In And Exist
当我们能够在我们的查询中选择使用IN和Exist语句时推荐使用EXISTS因为EXISTS一般更加高效(EXIST只遇到附合条件的很第一个结果时此记录就退出处理)。
用IN写出来的SQL的优点是比较容易写及清晰易懂这比较适合现代软件开发的风格。但是用IN的SQL性能总是比较低的从MSSQL执行的步骤来分析用IN的SQL与不用IN的SQL有以下区别
MSSQL试图将其转换成多个表的连接如果转换不成功则先执行IN里面的子查询再查询外层的表记录如果转换成功则直接采用多个表的连接方式查询。 由此可见用IN的SQL至少多了一个转换的过程。一般的SQL都可以转换成功但对于含有分组统计等方面的SQL就不能转换了。
在业务密集的SQL当中尽量不采用IN操作符用EXISTS方案代替
7.1、 NOTIN操作符
此操作是强列不推荐使用的因为它不能应用表的索引。
我们使用NOT EXISTS代替NOT IN
8、 In and Between
在查询语句中选择使用IN和BETWEEN时建议使用BETWEEN,因为它在很多场合下更高效。例子假设dbo.ERP_ProductSKUs表在ProID列上有一个非聚集索引进行如下查询SELECT * FROM dbo.ERP_ProductSKUs WHERE [ProID] IN(600,601, 602,603)
SELECT * FROM dbo.ERP_ProductSKUs WHERE [ProID] BETWEEN 600 AND 603
查询优化器可以使我们下面使用BETWEEN的语句比使用IN的语句性能更高效:
9、 Li ke
当在WHERE中使用Like时尽可能的在Like语句中使用一个或者多个前导字符比如使用
L I KE 'm%'
而不是:
如果在Like中使用一个前导字符此时查询优化器就会自动使用相应索引如果有合适索引存在的话来处理这个查询但是如果我们Li ke语句中的前导字符使用通配符查询优化器不会
使用索引而会对表进行扫描从而大大降低性能。这点在SQL Server2005上有所提升在SQLServer2005中查询优化器可以为类似于LIKE'%m'语句使用索引。
NOTE:使用的前导字符越多查询优化器就越有可能找到合适的索引从而加快查询。
另外如果应用程序的查询中有很多对CHAR和VARCHAR列进行Like操作时我们可能要考虑使用SQLServer的全文索引全文索引对于处理这方面查询会大大提升性能。
10、 OR
如果一个查询语句中包括多个OR子句为提升性能一般情况下可以重写为一系列查询并使用UNIONALL来合并结果集。
例子
SELECT *
FROM dbo.ERP_POrders
WHERE RecvRegion = '辽宁省'
OR RecvCity = '保定市'
OR AreaFrom LIKE '内蒙古赤峰市阿鲁科尔沁旗'
这个语句在WHERE中有三个独立的条件为了使用到索引我们要在这三列上创建组合索引。像这样的语句可能被重写为多个查询并使用UNIONALL来合并结果集从而替代OR操作。如下
SELECT * FROM dbo.ER= '辽宁省'
UNION ALL
SELECT * FROM dbo.ER= '保定市'
UNION ALL
SELECT * FROM dbo.ER'内蒙古赤峰市阿鲁科尔沁旗'
_每一个查询都得出一样的结果集元数据相同 如果在这个表中只有Re cvRegi on列上存在索引此时第一个SELECT就会使用此索引而第二个第三个SELECT语句会进行表的扫描而不是全部进行表的扫描。
这只是一个非常简单的例子只是示范了如何重写查询来提高性能如果这个查询比较复杂那么使用UNIONALL就会大大提高性能因为这样我们可以对每个语句进行调整而如果是全在一个OR子句中这些我们是做不到的。
NOTE:注意在这里我们是使用UNIONALL而不是UNION原因是UNION不光会对结果集进行合并也会对结果集进行排序并移除所有的重复行所以性能会大大降低,当然对于这样的一个查询也比较特殊之所以这个比较能成功是来源元数据在数据库中的密度影响假设RecvRegion,RecvCity,AreaFrom字段的密度很低那么UNION ALL任然没有使用OR的性能高所以建议在使用时慎用UNION ALL。
10.1、 >及<操作符大于或小于操作符
大于或小于操作符一般情况下是不用调整的因为它有索引就会采用索引查找但有的情况下可以对它进行优化如一个表有100万记录一个数值型字段A 30万记录的A=0 30万记录的A=1 39万记录的A=2 1万记录的A=3。那么执行A>2与A>=3的效果就有很大的区别了因为A>2时MSSQL会先找出为2的记录索引再进行比较而A>=3时MSSQL则直接找到=3的记录索引。
11、 Order by/sorting
ORDER BY要占用很多额外的资源所以除非在真正需要的情况下否则不要在SELECT语句中使用ORDERBY。我们可以考虑这些数据在客户端进行排序是否会更好或者客户端是否需要我们对结果集进行排序。
S O RT一般是伴随着以下T-S Q L语句出现
ORDERBY
GROUP BY
SELECTDISTINCT
UNION
CREATE INDEX 此时也需要对数据进行排序
一般情况下这些命令不可能避免但是另一方面我们可能想办法减少这些语句对资源的使用这包括
尽可能减少被排序的行数,就是只对必要排序的数据行集进行排序。
尽可能减少结果集中列的数目。
尽可能减少所有行的物理宽度
尽可能对I NT的列进行排序而不是Character或者Char列。
当使用S O RT操作时一定要考虑上面的建议并保持下面的原则“除非必要否则不要来对结果集进行排序”。
12、 Exist and count(*)
对数据库进行更新时很多时候要首先判断被更新的记录是否存在或者对删除对象之前判断对象是否存在此时不要使用SELECTCOUNT(*)来标识因为它的性能是非常低的。为了提升性能我们可以使用IF EXISTS来实现相关的操作。 IF EXISTS能提高性能是因为当有一条记录为真是处理就会立即退出但是COUNT(*)会检查表中的每一条记录的情况不管符合条件的数据有一条还是上万条。
例子
USE upay
--COUNT(*) :
IF (SELECT COUNT(*) FROM dbo.Person20) > 0
BEGIN
PRINT '111'
END
--下面是使用EXIST的语句也是性能较好的语句
IF EXISTS (SELECT * FROM DBO.AUTHORS )
BEGIN
PRINT '222'
END
虽然这两个查询语句都使用了一样的索引但是在COUNT(*)时返回的行的数据是表中符合条件的所有行然后才对这些行进行Aggregate 这也就是为什么数据越多使用COU NT(*)性能越低的原因。
而在SQL2012或SQL2008中
执行计划一致看来SQL2012/SQL2008更聪明了不过还是建议使用EXISTS来做判断更好。
13、使用Case
当对一个表进行多次更新时尽量合并到一个更新语句中。
例子假设要对数据库表tb中的val字段根据name类型进行调高价格至原来的2或5
官方网站:点击访问青云互联官网优惠码:五折优惠码:5LHbEhaS (一次性五折,可月付、季付、半年付、年付)活动方案:的套餐分为大带宽限流和小带宽不限流两种套餐,全部为KVM虚拟架构,而且配置都可以弹性设置1、洛杉矶cera机房三网回程cn2gia 洛杉矶cera机房  ...
昨天有在"盘点2021年主流云服务器商家618年中大促活动"文章中整理到当前年中大促618活动期间的一些国内国外的云服务商的促销活动,相对来说每年年中和年末的活动力度还是蛮大的,唯独就是活动太过于密集,而且商家比较多,导致我们很多新人不懂如何选择,当然对于我们这些老油条还是会选择的,估计没有比我们更聪明的进行薅爆款新人活动。有网友提到,是否可以整理一篇当前的这些活动商家中的促销产品。哪些商家哪款产...
CloudCone商家在前面的文章中也有多次介绍,他们家的VPS主机还是蛮有特点的,和我们熟悉的DO、Linode、VuLTR商家很相似可以采用小时时间计费,如果我们不满意且不需要可以删除机器,这样就不扣费,如果希望用的时候再开通。唯独比较吐槽的就是他们家的产品太过于单一,一来是只有云服务器,而且是机房就唯一的MC机房。CloudCone 这次四周年促销活动期间,商家有新增独立服务器业务。同样的C...