数据库SQL相关
SQL语句性能优化策略
-
+
首页
SQL语句性能优化策略
1.对查询进行优化,应及能量避免全表扫描,首先应考虑在WHERE和ORDER BY 涉及的列上建立索引。 2.应尽量避免在WHERE子句中对字段进行NULL判断,创建表时NULL是默认值,但大多数时候应该使用NOT NULL,或者使用一个特殊的值,如0,-1作为默认值。 3.应尽量避免在WHERE子句中使用!=或<>操作符。 4.应尽量避免在WHERE子句中使用OR来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,可以使用UNION合并查询: ```sql select id from t where num=10 union all select id from t where num=20 ``` 5.IN 和NOT IN 也要慎用,否则会导致全表扫描。对于连续的数值,能用BETWEEN就不要用IN: ```sql Select id from t where num between 1 and 3 ``` 6.下面的查询也将导致全表扫描: ```sql Select id from t where name like ‘%abc%’ ``` 或者 ```sql Select id from t where name like ‘%abc’ ``` 若要提高效率,可以考虑全文检索。而 ```sql select id from t where name like ‘abc%’ ``` 才用到索引。 7.如果在WHERE 子句中使用参数,也会导致全表扫描。 8.应尽量避免在WHERE 子句中对字段进行表达式操作,应尽量避免在WHERE子句中对字段进行函数操作。 9.很多时候用EXISTS 代替IN是一个好的选择: ```sql select num from a where num in (select num from b)。 ``` 用下面的语句替换: ```sql select num from a where exists(select 1 from b where num=a.num)。 ``` 10.索引固然可以提高相应的 SELECT 的效率,但同时也降低了 INSERT 及 UPDATE 的效。因为 INSERT 或 UPDATE 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过 6 个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。 11.应尽可能的避免更新 clustered 索引数据列, 因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。 12.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。 13.尽可能的使用 varchar, nvarchar 代替 char, nchar。因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。 14.最好不要使用返回所有:select from t ,用具体的字段列表代替 “*”,不要返回用不到的任何字段。 15.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。 16.使用表的别名(Alias):当在 SQL 语句中连接多个表时,请使用表的别名并把别名前缀于每个 Column 上。这样一来,就可以减少解析的时间并减少那些由 Column 歧义引起的语法错误。 17.使用“临时表”暂存中间结果 : 简化 SQL 语句的重要方法就是采用临时表暂存中间结果。但是临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在 tempdb 中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。 18.一些 SQL 查询语句应加上 nolock。读、写是会相互阻塞的,为了提高并发性能。对于一些查询,可以加上 nolock,这样读的时候可以允许写,但缺点是可能读到未提交的脏数据。 使用 nolock 有3条原则: ● 查询的结果用于“写、删、改”的不能加 nolock; ● 查询的表属于频繁发生页分裂的,慎用 nolock ; ● 能采用临时表提高并发性能的,不要用 nolock。 19.常见的简化规则如下: ● 不要有超过 5 个以上的表连接(JOIN),考虑使用临时表或表变量存放中间结果。 ● 少用子查询,视图嵌套不要过深,一般视图嵌套不要超过 2 个为宜。 20.将需要查询的结果预先计算好放在表中,查询的时候再Select。 21.用 OR 的字句可以分解成多个查询,并且通过 UNION 连接多个查询。他们的速度只同是否使用索引有关,如果查询需要用到联合索引,用 UNION all 执行的效率更高。多个 OR 的字句没有用到索引,改写成 UNION 的形式再试图与索引匹配。一个关键的问题是否用到索引。 22.在IN后面值的列表中,将出现最频繁的值放在最前面,出现得最少的放在最后面,减少判断的次数。 23.尽量将数据的处理工作放在服务器上,减少网络的开销,如使用存储过程。 存储过程是编译好、优化过、并且被组织到一个执行规划里、且存储在数据库中的 SQL 语句,是控制流语言的集合,速度当然快。反复执行的动态 SQL,可以使用临时存储过程,该过程(临时表)被放在 Tempdb 中。 24.当服务器的内存够多时,配制线程数量 = 最大连接数+5,这样能发挥最大的效率;否则使用配制线程数量< 最大连接数,启用 SQL SERVER 的线程池来解决,如果还是数量 = 最大连接数+5,严重的损害服务器的性能。 25.查询的关联同写的顺序 : ```sql select a.personMemberID, * from chineseresume a,personmember b where personMemberID = b.referenceid and a.personMemberID = 'JCNPRH39681' (A = B, B = '号码') ``` ```sql select a.personMemberID, * from chineseresume a,personmember b where a.personMemberID = b.referenceid and a.personMemberID = 'JCNPRH39681' and b.referenceid = 'JCNPRH39681' (A = B, B = '号码', A = '号码') ``` ```sql select a.personMemberID, * from chineseresume a,personmember b where b.referenceid = 'JCNPRH39681' and a.personMemberID = 'JCNPRH39681' (B = '号码', A = '号码') ``` 26.尽量使用 EXISTS 代替 select count(1) 来判断是否存在记录。count 函数只有在统计表中所有行数时使用,而且 count(1) 比 count(*) 更有效率。 27.尽量使用 “>=”,不要使用 “>”。 28.索引的使用规范: ● 索引的创建要与应用结合考虑,建议大的 OLTP 表不要超过 6 个索引; ● 尽可能的使用索引字段作为查询条件,尤其是聚簇索引,必要时可以通过 index index_name 来强制指定索引; ● 避免对大表查询时进行 table scan,必要时考虑新建索引; ● 在使用索引字段作为条件时,如果该索引是联合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用; ● 要注意索引的维护,周期性重建索引,重新编译存储过程。 29.下列 SQL 条件语句中的列都建有恰当的索引,但执行速度却非常慢: ```sql SELECT * FROM record WHERE substring(card_no, 1, 4) = '5378' --13秒 SELECT * FROM record WHERE amount/30 < 1000 --11秒 SELECT * FROM record WHERE convert(char(10), date, 112) = '19991201' --10秒 ``` 分析: WHERE 子句中对列的任何操作结果都是在 SQL 运行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引。 如果这些结果在查询编译时就能得到,那么就可以被 SQL 优化器优化,使用索引,避免表搜索,因此将 SQL 重写成下面这样: ```sql SELECT * FROM record WHERE card_no like '5378%' -- < 1秒 SELECT * FROM record WHERE amount < 1000*30 -- < 1秒 SELECT * FROM record WHERE date = '1999/12/01' -- < 1秒 ``` 30.有一批处理的插入或更新时,用批量插入或批量更新,绝不会一条条记录的去更新。 31.提高 GROUP BY 语句的效率,可以通过将不需要的记录在 GROUP BY 之前过滤掉。下面两个查询返回相同结果,但第二个明显就快了许多。 低效: ```sql SELECT JOB, AVG(SAL) FROM EMP GROUP BY JOB HAVING JOB = 'PRESIDENT' OR JOB = 'MANAGER' ``` 高效: ```sql SELECT JOB, AVG(SAL) FROM EMP WHERE JOB = 'PRESIDENT' OR JOB = 'MANAGER' GROUP BY JOB ``` 32.别名的使用,别名是大型数据库的应用技巧,就是表名、列名在查询中以一个字母为别名,查询速度要比建连接表快 1.5 倍。 33.避免死锁,在你的存储过程和触发器中访问同一个表时总是以相同的顺序;事务应尽可能地缩短,在一个事务中应尽可能减少涉及到的数据量;永远不要在事务中等待用户输入。 34.避免使用临时表,除非却有需要,否则应尽量避免使用临时表,相反,可以使用表变量代替。大多数时候(99%),表变量驻扎在内存中,因此速度比临时表更快,临时表驻扎在 TempDb 数据库中,因此临时表上的操作需要跨数据库通信,速度自然慢。 35.最好不要使用触发器: ● 触发一个触发器,执行一个触发器事件本身就是一个耗费资源的过程; ● 如果能够使用约束实现的,尽量不要使用触发器; ● 不要为不同的触发事件(Insert、Update 和 Delete)使用相同的触发器; ● 不要在触发器中使用事务型代码。 36.索引创建规则: ● 表的主键、外键必须有索引; ● 数据量超过 300 的表应该有索引; ● 经常与其他表进行连接的表,在连接字段上应该建立索引; ● 经常出现在 WHERE 子句中的字段,特别是大表的字段,应该建立索引; ● 索引应该建在选择性高的字段上; ● 索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引; ● 复合索引的建立需要进行仔细分析,尽量考虑用单字段索引代替; ● 正确选择复合索引中的主列字段,一般是选择性较好的字段; ● 复合索引的几个字段是否经常同时以 AND 方式出现在 WHERE 子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引; ● 如果复合索引中包含的字段经常单独出现在 WHERE 子句中,则分解为多个单字段索引; ● 如果复合索引所包含的字段超过 3 个,那么仔细考虑其必要性,考虑减少复合的字段; ● 如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引; ● 频繁进行数据操作的表,不要建立太多的索引; ● 删除无用的索引,避免对执行计划造成负面影响; ● 表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大。 ● 尽量不要对数据库中某个含有大量重复的值的字段建立索引。 37.查询缓冲并不自动处理空格,因此,在写SQL语句时,应尽量减少空格的使用,尤其是在SQL首和尾的空格(因为查询缓冲并不自动截取首尾空格)。 38.member用mid做标准进行分表方便查询么?一般的业务需求中基本上都是以 username为查询依据,正常应当是username做hash取模来分表。 39.我们应该为数据库里的每张表都设置一个ID做为其主键,并设置上自动增加的AUTO_INCREMENT标志。 40.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON,在结束时设置 SET NOCOUNT OFF。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。 41.优化表的数据类型,选择合适的数据类型: 原则:更小通常更好,简单就好,所有字段都得有默认值,尽量避免NULL。 例如:数据库表设计时候更小的占磁盘空间尽可能使用更小的整数类型。比如时间字段:datetime和timestamp。datetime占用8个字节,timestamp 占用4个字节,只用了一半。而timestamp表示的范围是1970-2037适合做更新时间。 因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。 例如:在定义邮政编码这个字段时,如果将其设置为CHAR(255),显然给数据库增加了不必要的空间。甚至使用VARCHAR 这种类型也是多余的,因为CHAR(6)就可以很好的完成任务了。 同样的,如果可以的话,我们应该使用MEDIUMINT而不是BIGIN来定义整型字段,应该尽量把字段设置为NOT NULL,这样在将来执行查询的时候,数据库不用去比较NULL 值。 42.字符串数据类型:char, varchar, text 选择区别。 43.任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。 44.Where语句书写及效率影响 示例【1】: 在下面两条select语句中: ```sql select * from table1 where field1<=10000 and field1>=0; select * from table1 where field1>=0 and field1<=10000; ``` 说明:如果数据表中的数据field1都>=0,则第一条select语句要比第二条select语句效率高的多,因为第二条select语句的第一个条件耗费了大量的系统资源。 原则:在where子句中应把最具限制性的条件放在最前面。 示例【2】: 在下面两条select语句中: ```sql select * from table where a=…and b=…and c=…; ``` 说明:若有索引Index(a,b,c),则where子句中字段的顺序应和索引中字段顺序一致。 原则:where子句中字段的顺序应和索引中字段顺序一致。 示例【3】: 以下假设在 field1 上有唯一索引 Ix_1,在 field2 上有非唯一索引 Ix_2。 ```sql select field3,field4 from tb where field1='sdf' --快 select * from tb where field1='sdf' --慢 ``` 说明:因为后者在索引扫描后要多一步ROWID表访问 示例【4】: 在下面两条select语句中: ```sql select field3,field4 from tb where field1>='sdf' --快 select field3,field4 from tb where field1>'sdf' --慢 ``` 说明:因为前者可以迅速定位索引。 示例【5】: 在下面select语句: ```sql select field3,field4 from tb where upper(field2)='RMN' --不使用索引。 ``` 说明:如果一个表有两万条记录,建议不使用函数;如果一个表有五万条以上记录,严格禁止使用函数!两万条记录以下没有限制。例如:Subtring、Like、Datepart函数会降低搜索效率。 示例【6】: ```sql select field3,field4 from tb where field2!='TOM' --不使用索引。 ``` 说明【6】: 条件中尽量不用!=, Not in, Like, OR, NOT EXISTS的条件语句。 示例【7】: ```sql select field3,field4 from tb where field2 is not null --不使用索引。 ``` 说明【7】: 空值不在索引中存储,所以上面语句执行不会使用索引检索。 45.避免多表关联 1) 对单条记录检索时,请对SQL语句进行拆分,减少表关联。 示例: 修改前 ```sql SELECT Lot.LotSN FROM MO INNER JOIN Lot ON MO.MOId = Lot.MOId WHERE (MO.MOName = 'S20100408001-900G') ``` 修改后 ```sql Declare @MOId char(12) --先获取MOid SELECT @MOId = MOId FROM MO WHERE (MOName = 'S20100408001-900G') --单独查询Lot表,避免表关联 SELECT Lot.LotSN FROM Lot WHERE (Lot.MOId = @MOId) ``` 2) 利用表变量来替换大表关联,表变量的作用域为一个批处理,批处理完了,表变量也会随之失效,比起临时表有它独特的优点:不用手动去删除表变量以释放内存。 示例: ```sql declare @t table( CardNo int ) insert @t(CardNo) select CardNo from Member where in(‘a-01’,’代理号二’) select 字段 from Order inner join @t on Order.CardNo=@t.CardNo ``` 3) 利用索引视图来提高大表关联的性能. 46.临时表和表变量的灵活使用 表变量的好处: 1) 避免重编译:表标量可以避免临时表引起的重编译。 2) 没有事务日志开销:表变量不执行事务日志活动,临时表执行该活动。 3) 没有锁开销:表变量被看作局部变量,不错在临时表的锁开销。 4) 没有回滚开销:没有事务日志就不存在回滚事务。 临时表的好处: 1) 临时表空间一般利用虚拟内存,大大减少了硬盘的I/O次数,因此也提高了系统效率。 2) 事务完毕或会话完毕数据自动清空,不必记得用完后删除数据。 3) 数据当前会话期可见,其它的会话只能看到其结构,只能看到自己的数据,各会话的数据互不干扰。 **47.防范死锁问题** 1)一定要在Delete前加上If Existed(select XID from youtable where删除的条件), 判断之后再进行删除。以减少表锁的机会 示例: ```sql If Exists(select LotId from Lot where LotSN = 'FG00000012') begin Delete Lot where LotSN = 'FG00000012' end ``` 2) 地方一定要在自已写的存储过程前加上 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED –允许脏读 3) 客户化程序在ADO.NET编写时,数据库操作完成后,一定需要释放connection 连接对象,不要用全局的connection对象不释放。 48.允许脏读 示例: ```sql ALTER PROCEDURE [dbo].[MetadataQueryTreeView] AS BEGIN SET NOCOUNT ON; --以下语句为允许脏读 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED SELECT * from lot return 0 END ``` 49.尽量避免存储过程嵌套使用 说明:为了使存储过程简便,容易理解,所以尽量避免存储过程嵌套使用。 50.游标的谨慎使用 (1) 尽管使用游标比较灵活,可以实现对数据集中单行数据的直接操作,但游标会在下面几个方面影响系统的性能: 使用游标会导致页锁与表锁的增加 导致网络通信量的增加 增加了服务器处理相应指令的额外开销 (2) 使用游标时的优化问题: 明确指出游标的用途:for read only或for update,在for update后指定被修改的列。 示例: 定义一个标准游标: ```sql declare mycursor cursor for select * from yuangong ``` 定义一个只读游标: ```sql declare mycursor cursor for select * from yuangong for read only ``` 定义一个可写游标 ```sql declare mycursor cursor for select * from yuangong for udpate of '列名' ``` 51.选择合理的自动发送邮件方式 避免在MES事物的存储过程中加入发送邮件功能,因为如果发送不成功将导致事物等待。 尽量使用作业方式的邮件发送。 52.合理使用索引 1) 索引并不是越多越好,索引固然可以提高相应的select 的效率,但同时也降低了insert及update效率。合理使用索引。 2) 并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引。 53.查询合理使用强制索引 1) 强制使用主键 ```sql Select * from table index(PRI) ``` 2) 强制使用索引 Ix_table_name ```sql Select * from table index(IX_table_Id) ``` 3) 强制使用多个索引 Ix_table_Id,Ix_table_name ```sql Select * from table index(Ix_table_Id,Ix_table_name) ``` 54.慎用char/nchar 字段类型 尽可能的使用varchar/nvarchar 代替char/nchar。 55.尽量使用数字型字段 尽量使用数字型字段,若只含数据值信息的字段尽量不要设计维字符型,这会降低查询和连接的性能,并会增加存储开销。
HB0005
2021年2月20日 18:06
分享
上一篇
下一篇
目录
微信扫一扫
复制链接
手机扫一扫进行分享
复制链接
下载Markdown文件