当前位置: 首页 > 科技观察

优化SQL查询:如何编写高性能SQL语句_0

时间:2023-03-12 22:38:31 科技观察

1。首先我们要了解什么是执行计划?执行计划是数据库根据SQL语句和相关表的统计信息制定的查询计划。该计划由查询优化器自动分析。记录,查询优化器将选择“索引查找”方法。如果表被归档并且只剩下5000条记录,查询优化器将改变计划并使用“全表扫描”方法。可见执行计划并不是固定的,是“个性化”的。生成正确的“执行计划”有两个要点:(1)SQL语句是否清楚地告诉查询优化器它要做什么?(2)查询优化器得到的数据库统计信息是否准确无误?2、统一SQL语句的写法对于下面的两条SQL语句,程序员认为它们是一样的,但是数据库查询优化器却认为它们是不同的。select*fromdualselect*Fromdual实际上是大小写不同,查询分析器认为是两条不同的SQL语句,必须解析两次。生成2个执行计划。所以作为程序员,应该保证同一个查询语句处处一致,多一个空格都不行!3、SQL语句不要太复杂。经常看到从数据库抓取的一条SQL语句,打印出来有两张A4纸那么长。一般来说,这种复杂的陈述通常是有问题的。我拿着2页长的SQL语句去问原作者,他说太长了,一时半会儿也看不懂。可想而知,即使是原作者看到的SQL语句也会乱,数据库也会乱。一般比较常见的是将一条Select语句的结果作为一个子集,然后从这个子集开始查询。但是根据经验,如果超过3层以上的嵌套,查询优化器很容易给出错误的执行计划。因为惊呆了。像人工智能这样的事情最终比人类的解决方案更糟糕。人晕了,我敢保证数据库也会晕。另外,执行计划是可以复用的,SQL语句越简单,被复用的可能性就越高。而复杂的SQL语句只要改变一个字符就必须重新解析,然后这堆垃圾就塞进了内存。可以想象数据库将是多么低效。4、使用“临时表”临时存放中间结果。简化SQL语句的一个重要方法是使用临时表来临时存储中间结果。然而,临时表的好处远不止这些。临时结果临时存放在临时表中,后续查询会在tempdb中,这样可以避免程序中多次扫描主表,也大大减少程序执行时“共享锁”阻塞“更新锁”,减少阻塞和提高并发性能。5、OLTP系统SQL语句必须使用绑定变量select*fromorderheaderwherechangetime>'2010-10-2000:00:01'select*fromorderheaderwherechangetime>'2010-09-2200:00:01'或者上面的查询优化器认为这两条语句是不同的SQL语句,需要解析两次。如果使用绑定变量select*fromorderheaderwherechangetime>@chgtime@chgtime,可以传入任意值,这样大量相似的查询可以复用执行计划,可以大大减轻数据库解析的负担SQL语句。一次解析,多次复用是提高数据库效率的原则。6.使用绑定变量监视事物有两个方面。绑定变量适用于大多数OLTP流程,但也有例外。例如,当where条件中的字段为“倾斜字段”时。“倾斜字段”是指该列中的大部分值都是相同的。例如,在一份人口普查表格中,“种族”一栏中90%以上是汉族。所以如果一条SQL语句要查询30岁的汉族人口,那么where条件中就必须要有“ethnicity”这一列。这时候如果使用绑定变量@nation,就会出现很大的问题。试想一下,如果@nation传入的第一个值是“汉”,那么整个执行计划必然会选择表扫描。然后,第二个值作为“Boyei”传入。按理说,“博奕”的比例可能只有万分之一,应该用索引搜索。但是,由于复用了第一次解析的“汉族”的执行计划,所以第二次也会使用表扫描的方式。这个问题就是著名的“bindvariablesnooping”,建议不要对“slantedfields”使用绑定变量。7.仅在必要时才使用begintranSQL。SQLServer中一条SQL语句默认是一个事务,语句执行后默认是提交的。实际上,这是begintran的一种最小化形式,就像在每个句子的开头隐含了begintran,在末尾隐含了commit。在某些情况下,我们需要显式声明begintran。例如,在执行“插入、删除、修改”操作时,需要同时修改几个表。begintran可以起到这样的作用,它可以把几条SQL语句一起执行,最后一起commit。好处是保证了数据的一致性,但没有什么是完美的。Begintran付出的代价是,所有被SQL语句锁定的资源,直到commit被commit后才能释放。可见,如果被Begintran困住的SQL语句过多,数据库的性能就会很差。在大事务提交之前,其他的语句必然会被阻塞,从而产生大量的块。Begintran使用的原理是在保证数据一致性的前提下,begintran困住的SQL语句越少越好!在某些情况下,可以使用触发器来同步数据,而begintran不是必须的。8、一些SQL查询语句要加nolock。在SQL语句中加入nolock是提高SQLServer并发性能的重要手段。在oracle中没有必要这样做,因为oracle的结构比较合理,有undo表空间保存“数据伏笔”,如果修改的时候数据还没有commit,那你读的就是copy在它被修改之前,副本被放置在撤消表空间中。这样oracle的读写就可以相互独立了,这也是oracle被广泛推崇的原因。SQLServer的读写会互相阻塞。为了提高并发性能,可以对一些查询加nolock,这样读的时候可以允许写,但是缺点是可能读到未提交的脏数据。使用nolock有3个原则。(1)“插入、删除、修改”的查询结果不能加Nolock!(2)查询表分页频繁,慎用nolock!(3)临时表也可以用来保存“数据伏笔”,类似于oracle的undo表空间。如果使用临时表可以提高并发性能,就不要使用nolock。9、聚簇索引没有建立在表的order字段上,表容易出现页分裂。比如订单表有一个订单号orderid和一个客户号contactid,那么聚簇索引应该加到哪个字段呢?对于此表,顺序添加订单号。如果在orderid上加了聚簇索引,新的行会加在最后,这样就不会经常出现分页的情况。但是,由于大多数查询都基于客户ID,因此只有在contactid上添加聚簇索引才有意义。对于订单表,contactid不是顺序字段。比如“张三”的“contactid”是001,那么“张三”的订单信息一定要放在这个表的第一个数据页。如果“张三”??今天下了一个新订单,那应该是订单信息不能放在表格的最后一页,而是放在第一页!如果第一页已满怎么办?抱歉,必须向后移动此表中的所有数据,以便为这条记录腾出空间。SQLServer的索引与Oracle的索引不同。SQLServer的聚集索引实际上是按照聚集索引字段的顺序对表进行排序,相当于Oracle的索引组织表。SQLServer的聚集索引是表本身的一种组织形式,所以效率非常高。也正是因为如此,在插入一条记录的时候,它的位置并不是随便放的,而是必须放在本该按顺序放置的数据页中。如果该数据页没有空间,则会导致页面拆分。所以很明显,聚簇索引不是建立在表的顺序字段上的,表很容易出现页分裂。曾经遇到过一个情况,小伙伴对某张表重建索引后,插入效率明显下降。估计大概是这样的。表的聚集索引可能不是建立在表的顺序字段上的,而且表经常被归档,所以表的数据以稀疏状态存在。比如张三下了20单,但是最近3个月只有5单。归档策略是保留3个月的数据。那么张三过去的15个命令都被存档了,还剩下15个空位,可以插进去,等出现的时候再用。在这种情况下,不会发生页面拆分,因为有可用的空缺。但是查询性能会比较低,因为查询必须扫描那些没有数据的空槽。重建聚簇索引后情况发生了变化,因为重建聚簇索引就是对表中的数据进行重新排列,原来的空位没有了,而且页面的填充率非常高,插入数据时经常出现分页,所以性能显着下降。你想给聚集索引不是建立在顺序字段上的表一个比较低的页面填充率吗?您想避免重建聚簇索引吗?是值得思考的问题!10、加nolock后可以查询分页频繁,容易跳读或重复读的表。加了nolock之后,就可以在“插入、删除、修改”的同时进行查询了。在某些情况下,一旦数据页满了,分页是不可避免的,此时就发生了nolock查询。例如,在第100页读取的记录可能由于分页而被分到第101页。这可能会导致nolock查询在读取101页时重复读取数据,造成“重复读取”。同理,如果100页的数据还没有被读取就赋值给了99页,那么nolock查询可能会漏掉记录,导致“跳读”。上面提到的哥们,加了nolock之后,有些操作报错。估计是因为nolock查询可能会出现重复读。如果将两条相同的记录插入到其他表中,当然会发生主键冲突。11、使用like进行模糊查询时,需要注意的是,有时候需要进行一些模糊查询,比如select*fromcontactwhereusernamelike'%yue%'keyword%yue%,因为"%"是用在yue前面,所以这个查询是需要扫描全表的,除非必要,关键字前不要加%,12、数据类型隐式转换对查询效率的影响在sqlserver2000的数据库中,我们的程序是这样做的提交sql语句时不要使用强类型提交该字段的值,sqlserver2000会自动进行数据类型转换,这样会导致传入的参数与主键字段的类型不一致。这时候sqlserver2000可能会使用全表扫描。Sql2005上没有发现这种问题,但还是要注意一下。13、SQLServer表的三种连接方式(1)MergeJoin(2)NestedLoopJoin(3)HashJoinSQLServer2000只有一种连接方式——NestedLoopJoin。如果A的结果集较小,则作为默认值从表面上看,A中的每条记录都要在B中扫描,实际扫描的行数等于A结果集中的行数xB结果集中的行数。因此,如果两个结果集都很大,则Join的结果不好。SQLServer2005添加了MergeJoin。如果表A和表B的join字段刚好是聚簇索引所在的字段,那么表的顺序已经排好了,只要把两边放在一起,这次join的开销就相当于A表的结果集的行数加上B表的结果集的行数,一为加法,一为乘法。可以看出mergejoin的效果比NestedLoopJoin好很多。如果join的字段上没有索引,SQL2000的效率是相当低的,但是SQL2005提供了Hashjoin,相当于给A表和B表的结果集临时加了索引,所以SQL2005的效率相比与SQL2000。我认为这是一个重要的原因。综上所述,连接表时需要注意以下几点:(1)连接字段尽量选择聚簇索引所在的字段(2)慎重考虑where条件,最小化A和的结果集B表(3)如果join很多连接字段缺少索引,你还在用SQLServer2000,赶紧升级吧。