首页 理论教育 SQL语句优化技巧与方法-提升项目开发效率!

SQL语句优化技巧与方法-提升项目开发效率!

时间:2023-11-02 理论教育 版权反馈
【摘要】:避免出现SELECT*FROM table语句,要明确查出的字段。在判断有无符合条件的记录时建议不要用SELECT COUNT(*)和select top 1语句。使用内层限定原则,在拼写SQL语句时,将查询条件分解、分类,并尽量在SQL语句的最里层进行限定,以减少数据的处理量。建议集合中的数据不超过200个。对查询进行优化,应尽量避免全表扫描,首先应考虑在where及order by涉及的列上建立索引。

SQL语句优化技巧与方法-提升项目开发效率!

(1)避免出现SELECT*FROM table语句,要明确查出的字段。

(2)查询时尽可能使用索引覆盖。即对SELECT的字段建立复合索引,这样查询时只进行索引扫描,不读取数据块。

(3)在判断有无符合条件的记录时建议不要用SELECT COUNT(*)和select top 1语句。

(4)使用内层限定原则,在拼写SQL语句时,将查询条件分解、分类,并尽量在SQL语句的最里层进行限定,以减少数据的处理量。

(5)应绝对避免在order by子句中使用表达式。

(6)如果需要从关联表读数据,关联的表一般不要超过7个。

(7)应尽量避免在where子句中使用or和IN来连接条件,否则将导致引擎放弃使用索引而进行全表扫描。需要注意In集合中的数据量。建议集合中的数据不超过200个。如:

select id from t where num=10 or num=20

可以这样查询:

select id from t where num=10

union all

select id from t where num=20

(8)应尽量避免在where子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。<>用<、>代替,>用>=代替,<用<=代替,这样可以有效的利用索引。以大于操作符和大于等于操作符为例,它们能实现相同的功能,但是对它们优化之后会有不同的效果。如一个表有100万记录,有一个数值型字段“A”,A=0的记录有30万行,A=1的记录30万行,A=2的记录有39万行,A=3的记录有1万行。那么执行A>2与A>=3的效果就有很大的区别了,因为A>2时数据库会先找出为2的记录再进行比较,而A>=3时数据库则直接找到等于3的记录,所以选择好的比较条件会提高数据库的执行效率

(9)在查询时尽量减少对多余数据的读取包括多余的列与多余的行。

(10)对于复合索引要注意,例如在建立复合索引时列的顺序是F1,F2,F3,则在where或order by子句中这些字段出现的顺序要与建立索引时的字段顺序一致,且必须包含第一列。只能是F1或F1,F2或F1,F2,F3。否则不会用到该索引。

(11)多表关联查询时,写法必须遵循以下原则,这样做有利于建立索引,提高查询效率。格式如下:select sum(table1.je)from table1,table2,table3 where(table1的等值条件(=))and(table1的非等值条件)and(table2与table1的关联条件)and(table2的等值条件)and(table2的非等值条件)and(table3与table2的关联条件)and(table3的等值条件)and(table3的非等值条件)。

(12)子查询问题。对于能用连接方式或者视图方式实现的功能,不要用子查询。例如:select name from customer where customer_id in(select customer_id from order where money>1000)。应该用如下语句代替:select name from customer inner join order on customer.customer_id=order.customer_id where order.money>100。

(13)在WHERE子句中,避免对列的四则运算或其他表达式运算,特别是where条件的左边,严禁使用运算与函数对列进行处理,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2=100

应改为:

select id from t where num=100*2

又如:

select id from t where substring(name,1,3)='abc'--name以abc开头的id

select id from t where datediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id

应改为:

select id from t where name like'abc%'='2005-11-30'and createdate<'2005-12-1'

(14)如果在语句中有not in(in)操作,最好的办法是使用外连接实现。如:

select id from t where num in(1,2,3),对于连续的数值,能用between就不要用in了:

select id from t where num between 1 and 3

(15)对一个业务过程的处理,应该使事物的开始与结束之间的时间间隔越短越好,原则上做到数据库的读操作在前面完成,数据库写操作在后面完成,避免交叉。

(16)请小心不要对过多的列使用列函数和order by,group by等。

(17)用union all代替union,数据库执行union操作,首先分别执行union两端的查询,将其放在临时表中,然后在对其进行排序,过滤重复的记录。当已知的业务逻辑决定query A和query B中不会有重复记录时,应该用union all代替union,以提高查询效率。

(18)对查询进行优化,应尽量避免全表扫描,首先应考虑在where及order by涉及的列上建立索引。

(19)应尽量避免在where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描。NULL值即空值的意思,根据数据库的特性,是不能使用包含NULL值的字段作为索引的,即使对该字段建立了索引,只要某行上面有NULL值,该字段就不能使用索引。但是我们可以使用变相方法来表示NULL值,如将某行上的NULL值用0表示,这样WHERE条件语句“a is null”就可以改成“a='0'”来实现。

如:select id from t where num is null

可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

select id from t where num=0

另外还可以设置字段不允许为空,而用一个缺省值代替空值,如一个时间类型的字段,可以将默认时间设为“1900-01-01”来表示空值。

(20)如果在where子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num=@num

可以改为强制查询使用索引:

select id from t with(index(索引名))where num=@num

(21)注意WHERE子句后面的条件顺序。WHERE子句后面的条件顺序对大数据量表的查询会产生直接的影响。在一个SQL语句中,如果一个where条件过滤的数据库记录越多,定位越准确,则该where条件越应该前移。以下面两条语句为例:Select id From Emp Where empno<=2000 and sal>=1000;Select id From Emp Where sal>=1000 and empno<=2000;假设以上两个SQL语句中“empno”及“sal”两个字段都没有索引,所以执行的时候都是全表扫描,其中的“sal>=1000”条件在记录集内比率为99%,而“empno<=2000”的比率只为0.5 %。在执行第一条SQL语句的时候,查询先得到符合条件sal>=1000的记录M条,接着处理条件empno<=2000这一次只需要在第一次查询的结果集中进行查询即可,得出N条,所以总共查询了M+N条记录。而在进行第二条SQL语句的时候,查询先得到符合条件empno<=2000的记录为A条,接着处理条件sal>=1000,同样只要在第一次查询的结果集中进行查询即可,得出B条,所以总共查询了A+B条记录。可以看出数据库扫描的数据量A+B<M+N,因此第二条语句的执行效率更高。

(22)适当的使用过渡表。把表的一个子集进行排序并创建临时表,有时能加速查询。它有助于避免多重排序操作,而且在其他方面还能简化优化器的工作。以查询在院病人费用超过1万元的医嘱明细为例,如下所示:

Select a.BLH,b.SFKS,b.SFRQ,b.YZM,b.ZFJE,……(www.xing528.com)

From zy_brzl a,zy_dxsf b

where a.blh=b.blh and a.zt=’在院’and a.zfje>=10000

Order by a.blh;

如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个临时文件中,并按客户的名字进行排序。

SQL语句可以改为:

Select a.BLH,b.SFKS,b.SFRQ,b.YZM,b.ZFJE,……

Into zy_brzl_zy

From zy_brzl a,zy_dxsf b

Where a.blh=b.blh and a.zt=’在院’

Order by a.blh;

然后以下面的方式在临时表中查询:Select*From zy_brzl_zy Where zfje>=10000;

由于临时表中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘的I/0操作,所以查询工作量可以得到大幅减少。但是需要注意临时表创建后不会反映主表的修改,在主表中数据频繁修改的情况下,不要丢失数据。

(23)在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能地让字段顺序与索引顺序相一致。

(24)不要写一些没有意义的查询,如需要生成一个空表结构:

select col1,col2 into #t from t where 1=0

这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:

create table #t(...)

(25)根据情况选择用in或exists。我们在调用数据的时候会将一列和一系列值相比较,最简单的办法就是在WHERE子句中使用子查询,而在WHERE子句中有两种方式的子查询。下面以zy_dxsfls,zy_brzlls两个表为例,如下所示:

第一种方式使用IN操作符,

Select a.blh,a.sfks,a.sfrq,a.yzm,a.zfje,a.xzys

From zy_dxsfls a

where a.blh in(select b.blh from zy_brzlls b where b.csrq>='1970-1-1');

第二种方式使用EXIST操作符,

Select a.blh,a.sfks,a.sfrq,a.yzm,a.zfje,a.xzys

From zy_dxsfls a

where exists(select 1 from zy_brzlls b where b.blh=a.blh and b.csrq>='1970-1-1')

虽然两种方法得出的结果是一样的,但是使用的时候却是有区别的。IN操作符适合于zy_brzlls表大而Zy_dxsfls表小的情况,而EXISTS操作符适合于zy_brzlls表小而zy_dxsfls表大的情况。因此使用的时候应视情况而定,不能盲目使用,NOT IN操作符和NOT EXISTS操作符也是一样的。

(26)并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。

(27)索引并不是越多越好,索引固然可以提高相应的select的效率,但同时也降低了insert及update的效率,因为insert或update时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

(28)应尽可能地避免更新clustered索引数据列,因为clustered索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新clustered索引数据列,那么需要考虑是否应将该索引建为clustered索引。

(29)尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

(30)尽可能地使用varchar/nvarchar代替char/nchar,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

(31)尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

(32)避免频繁创建和删除临时表,以减少系统表资源的消耗。

(33)临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

(34)在新建临时表时,如果一次性插入数据量很大,那么可以使用select into代替create table,避免造成大量log,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

(35)如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先truncate table,然后drop table,这样可以避免系统表的较长时间锁定。

(36)使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

(37)与临时表一样,游标并不是不可使用。对小型数据集使用FAST_FORWARD游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

(38)在所有的存储过程和触发器的开始处设置SET NOCOUNT ON,在结束时设置SET NOCOUNT OFF。无须在执行存储过程和触发器的每个语句后向客户端发送DONE_IN_PROC消息。

(39)尽量避免大事务操作,提高系统并发能力。

(40)尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

(41)在一个事物中,对同一个表的多个insert语句应该集中在一起执行。

(42)在一个业务过程中,尽量地使insert,update,delete语句在业务结束前执行,以减少死锁的可能性。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈