怎么优化SQL查询语句

54次阅读
没有评论

共计 4832 个字符,预计需要花费 13 分钟才能阅读完成。

这篇文章主要讲解了“怎么优化 SQL 查询语句”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着丸趣 TV 小编的思路慢慢深入,一起来研究和学习“怎么优化 SQL 查询语句”吧!

1、首先要搞明白什么叫执行计划?

执行计划是数据库根据 SQL 语句和相关表的统计信息作出的一个查询方案,这个方案是由查询优化器自动分析产生的,比如一条 SQL 语句如果用来从一个  10 万条记录的表中查 1 条记录,那查询优化器会选择“索引查找”方式,如果该表进行了归档,当前只剩下 5000 条记录了,那查询优化器就会改变方案,采用  “全表扫描”方式。

可见,执行计划并不是固定的,它是“个性化的”。产生一个正确的“执行计划”有两点很重要:

(1) SQL 语句是否清晰地告诉查询优化器它想干什么?

(2) 查询优化器得到的数据库统计信息是否是 *** 的、正确的?

2、统一 SQL 语句的写法

对于以下两句 SQL 语句,程序员认为是相同的,数据库查询优化器认为是不同的。

select*from dual select*From dual

其实就是大小写不同,查询分析器就认为是两句不同的 SQL 语句,必须进行两次解析。生成 2 个执行计划。所以作为程序员,应该保证相同的查询语句在任何地方都一致,多一个空格都不行!

3、不要把 SQL 语句写得太复杂

我经常看到,从数据库中捕捉到的一条 SQL 语句打印出来有 2 张 A4 纸这么长。一般来说这么复杂的语句通常都是有问题的。我拿着这 2 页长的 SQL 语句去请教原作者,结果他说时间太长,他一时也看不懂了。可想而知,连原作者都有可能看糊涂的 SQL 语句,数据库也一样会看糊涂。

一般,将一个 Select 语句的结果作为子集,然后从该子集中再进行查询,这种一层嵌套语句还是比较常见的,但是根据经验,超过 3 层嵌套,查询优化器就很容易给出错误的执行计划。因为它被绕晕了。像这种类似人工智能的东西,终究比人的分辨力要差些,如果人都看晕了,我可以保证数据库也会晕的。

另外,执行计划是可以被重用的,越简单的 SQL 语句被重用的可能性越高。而复杂的 SQL 语句只要有一个字符发生变化就必须重新解析,然后再把这一大堆垃圾塞在内存里。可想而知,数据库的效率会何等低下。

4、使用“临时表”暂存中间结果

简化 SQL 语句的重要方法就是采用临时表暂存中间结果,但是,临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在 tempdb 中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。

5、OLTP 系统 SQL 语句必须采用绑定变量

select*from orderheader where changetime  2010-10-20 00:00:01  select*from orderheader where changetime  2010-09-22 00:00:01

以上两句语句,查询优化器认为是不同的 SQL 语句,需要解析两次。如果采用绑定变量

select*from orderheader where changetime  @chgtime

@chgtime 变量可以传入任何值,这样大量的类似查询可以重用该执行计划了,这可以大大降低数据库解析 SQL 语句的负担。一次解析,多次重用,是提高数据库效率的原则。

6、绑定变量窥测

事物都存在两面性,绑定变量对大多数 OLTP 处理是适用的,但是也有例外。比如在 where 条件中的字段是“倾斜字段”的时候。

“倾斜字段”指该列中的绝大多数的值都是相同的,比如一张人口调查表,其中“民族”这列,90% 以上都是汉族。那么如果一个 SQL 语句要查询 30 岁的汉族人口有多少,那“民族”这列必然要被放在 where 条件中。这个时候如果采用绑定变量 @nation 会存在很大问题。

试想如果 @nation 传入的 *** 个值是“汉族”,那整个执行计划必然会选择表扫描。然后,第二个值传入的是“布依族”,按理说“布依族”占的比例可能只有万分之一,应该采用索引查找。但是,由于重用了 *** 次解析的“汉族”的那个执行计划,那么第二次也将采用表扫描方式。这个问题就是著名的“绑定变量窥测”,建议对于“倾斜字段”不要采用绑定变量。

7、只在必要的情况下才使用 begin tran

SQL Server 中一句 SQL 语句默认就是一个事务,在该语句执行完成后也是默认 commit 的。其实,这就是 begin  tran 的一个最小化的形式,好比在每句语句开头隐含了一个 begin tran,结束时隐含了一个 commit。

有些情况下,我们需要显式声明 begin tran,比如做“插、删、改”操作需要同时修改几个表,要求要么几个表都修改成功,要么都不成功。begin tran   可以起到这样的作用,它可以把若干 SQL 语句套在一起执行,*** 再一起 commit。好处是保证了数据的一致性,但任何事情都不是 *** 无缺的。Begin  tran 付出的代价是在提交之前,所有 SQL 语句锁住的资源都不能释放,直到 commit 掉。

可见,如果 Begin tran 套住的 SQL 语句太多,那数据库的性能就糟糕了。在该大事务提交之前,必然会阻塞别的语句,造成 block 很多。

Begin tran 使用的原则是,在保证数据一致性的前提下,begin tran   套住的 SQL 语句越少越好! 有些情况下可以采用触发器同步数据,不一定要用 begin tran。

8、一些 SQL 查询语句应加上 nolock

在 SQL 语句中加 nolock 是提高 SQL  Server 并发性能的重要手段,在 oracle 中并不需要这样做,因为 oracle 的结构更为合理,有 undo 表空间保存“数据前影”,该数据如果在修改中还未 commit,那么你读到的是它修改之前的副本,该副本放在 undo 表空间中。这样,oracle 的读、写可以做到互不影响,这也是 oracle   广受称赞的地方。SQL Server   的读、写是会相互阻塞的,为了提高并发性能,对于一些查询,可以加上 nolock,这样读的时候可以允许写,但缺点是可能读到未提交的脏数据。使用  nolock 有 3 条原则。

(1) 查询的结果用于“插、删、改”的不能加 nolock !

(2) 查询的表属于频繁发生页分裂的,慎用 nolock !

(3) 使用临时表一样可以保存“数据前影”,起到类似 oracle 的 undo 表空间的功能,

能采用临时表提高并发性能的,不要用 nolock。

9、聚集索引没有建在表的顺序字段上,该表容易发生页分裂

比如订单表,有订单编号 orderid,也有客户编号 contactid,那么聚集索引应该加在哪个字段上呢? 对于该表,订单编号是顺序添加的,如果在 orderid 上加聚集索引,新增的行都是添加在末尾,这样不容易经常产生页分裂。然而,由于大多数查询都是根据客户编号来查的,因此,将聚集索引加在 contactid 上才有意义。而 contactid 对于订单表而言,并非顺序字段。

比如“张三”的“contactid”是 001,那么“张三”的订单信息必须都放在这张表的 *** 个数据页上,如果今天“张三”新下了一个订单,那该订单信息不能放在表的 *** 一页,而是 *** 页! 如果 *** 页放满了呢? 很抱歉,该表所有数据都要往后移动为这条记录腾地方。

SQL Server 的索引和 Oracle 的索引是不同的,SQL  Server 的聚集索引实际上是对表按照聚集索引字段的顺序进行了排序,相当于 oracle 的索引组织表。SQL  Server 的聚集索引就是表本身的一种组织形式,所以它的效率是非常高的。也正因为此,插入一条记录,它的位置不是随便放的,而是要按照顺序放在该放的数据页,如果那个数据页没有空间了,就引起了页分裂。所以很显然,聚集索引没有建在表的顺序字段上,该表容易发生页分裂。

曾经碰到过一个情况,一位哥们的某张表重建索引后,插入的效率大幅下降了。估计情况大概是这样的。该表的聚集索引可能没有建在表的顺序字段上,该表经常被归档,所以该表的数据是以一种稀疏状态存在的。比如张三下过 20 张订单,而最近 3 个月的订单只有 5 张,归档策略是保留 3 个月数据,那么张三过去的  15 张订单已经被归档,留下 15 个空位,可以在 insert 发生时重新被利用。在这种情况下由于有空位可以利用,就不会发生页分裂。但是查询性能会比较低,因为查询时必须扫描那些没有数据的空位。

重建聚集索引后情况改变了,因为重建聚集索引就是把表中的数据重新排列一遍,原来的空位没有了,而页的填充率又很高,插入数据经常要发生页分裂,所以性能大幅下降。

对于聚集索引没有建在顺序字段上的表,是否要给与比较低的页填充率? 是否要避免重建聚集索引? 是一个值得考虑的问题!

10、加 nolock 后查询经常发生页分裂的表,容易产生跳读或重复读

加 nolock 后可以在“插、删、改”的同时进行查询,但是由于同时发生“插、删、改”,在某些情况下,一旦该数据页满了,那么页分裂不可避免,而此时 nolock 的查询正在发生,比如在第 100 页已经读过的记录,可能会因为页分裂而分到第 101 页,这有可能使得 nolock 查询在读 101 页时重复读到该条数据,产生“重复读”。同理,如果在 100 页上的数据还没被读到就分到 99 页去了,那 nolock 查询有可能会漏过该记录,产生“跳读”。

上面提到的哥们,在加了 nolock 后一些操作出现报错,估计有可能因为 nolock 查询产生了重复读,2 条相同的记录去插入别的表,当然会发生主键冲突。

11、使用 like 进行模糊查询时应注意

有的时候会需要进行一些模糊查询比如

select*from contact where username like  lsquo;%yue% rsquo;

关键词 %yue%,由于 yue 前面用到了“%”,因此该查询必然走全表扫描,除非必要,否则不要在关键词前加 %,

12、数据类型的隐式转换对查询效率的影响

sql server2000 的数据库,我们的程序在提交 sql 语句的时候,没有使用强类型提交这个字段的值,由 sql server  2000 自动转换数据类型,会导致传入的参数与主键字段类型不一致,这个时候 sql server  2000 可能就会使用全表扫描。Sql2005 上没有发现这种问题,但是还是应该注意一下。

13、SQL Server 表连接的三种方式

(1) Merge Join

(2) Nested Loop Join

(3) Hash Join

SQL Server 2000 只有一种 join 方式 mdash; mdash;Nested Loop  Join,如果 A 结果集较小,那就默认作为外表,A 中每条记录都要去 B 中扫描一遍,实际扫过的行数相当于 A 结果集行数 x  B 结果集行数。所以如果两个结果集都很大,那 Join 的结果很糟糕。

SQL Server 2005 新增了 Merge  Join,如果 A 表和 B 表的连接字段正好是聚集索引所在字段,那么表的顺序已经排好,只要两边拼上去就行了,这种 join 的开销相当于 A 表的结果集行数加上 B 表的结果集行数,一个是加,一个是乘,可见 merge  join 的效果要比 Nested Loop Join 好多了。

如果连接的字段上没有索引,那 SQL2000 的效率是相当低的,而 SQL2005 提供了 Hash  join,相当于临时给 A,B 表的结果集加上索引,因此 SQL2005 的效率比 SQL2000 有很大提高,我认为,这是一个重要的原因。

总结一下,在表连接时要注意以下几点:

(1) 连接字段尽量选择聚集索引所在的字段

(2) 仔细考虑 where 条件,尽量减小 A、B 表的结果集

(3) 如果很多 join 的连接字段都缺少索引,而你还在用 SQL Server 2000,赶紧升级吧。

感谢各位的阅读,以上就是“怎么优化 SQL 查询语句”的内容了,经过本文的学习后,相信大家对怎么优化 SQL 查询语句这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是丸趣 TV,丸趣 TV 小编将为大家推送更多相关知识点的文章,欢迎关注!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-01发表,共计4832字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)