Sql Server 优化 SQL 查询:如何写出高性能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 ‘%yue%’

 

关键词%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方式——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,赶紧升级吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/287882.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Ubuntu】ubuntu物理机安装方法:wubi

在之前的文章里我们曾经详细介绍了如何在虚拟机里安装ubuntu操作系统,但是一些小伙伴希望在自己的电脑里安装一个ubuntu,来感受一下ubuntu的硬体验。下面给大家介绍一种通过wubi的安装方法。 wubi是 Windows Ubuntu-Based Installer 缩写,是一…

Java之ThreadLocal

1 ThreadLocal接口出现原因 使用ThreadLocal保存当前线程的变量值,这样你想获取该变量的值的时候,获取到的都是本线程的变量值,不会获取到其他线程设置的值,早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLo…

server如何调用 thrift_Thrift总结(二)如何快速创建自己的RPC服务

前面介绍了thrift 基础的东西,怎么写thrift 语法规范编写脚本,如何生成相关的语言的接口。不清楚的可以看这个《Thrift总结(一)介绍》。做好之前的准备工作以后,下面就开始如何用Thrift写RPC接口。如何用Thrift写RPC接口1. 打开之前下载的thr…

Hello Playwright:(2)简化部署方式

前言上次的文章Hello Playwright:(1)从开发到部署发表后,有网友留言问,有不有简化部署的方式:下面,提供 2 种可行的方案。方案 1. Copy其实 Playwright 默认是到C:\Users\用户名\AppData\Local\ms-playwright文件夹下查…

ArcGIS实验教程——实验十六:空间数据查询

ArcGIS实验视频教程合集:《ArcGIS实验教程从入门到精通》(附配套实验数据) 一、实验描述 空间数据库查询定义:从空间数据库中找出所有满足属性约束条件和空间约束条件的地理对象。 二、实验内容 (一)属性查询 1、简单查询 2、SQL查询 (二)图形查询 1、点查询 2…

C++入门指南及实战 第一步 概述及经典HelloWorld

本系列文章环境及学习门槛 编程基础:无年龄:具有一定的逻辑思维英文:不要求数学:不要求学习时长:每天一篇,一周4-5篇即可,每篇最多1小时死记硬背:不需要,理解至上本机环…

【数学题】男女的比例

在一个重男轻女的国家里,每家每户都想生男孩。若一户人家生了一个男孩,就不会再生了。若一家生了一个女孩,便会再生一个,直到生下男孩为止。请问这个国家(有无限多的人民)的男女比例是多少?答案:1比1。在某一户人家中…

为什么云服务器没西南的_去年“双11“我买的那台云服务器

一、为什么会买云服务器为什么初学者需要一台云服务器?从我自己那仅有的一点经验来看,重点无非这一个词:实践。细数一下初学者拥有一台云服务器的好处:学习操作系统、熟悉环境大部分初学者在本地使用的是windows系统,在…

Java线程安全以及线程安全的实现方式和内存模型(JMM)

一、了解几个概念 1)临界区: 临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程或是进…

animate 实现滑动切换效果

今天和大家分享一下用 animate 实现滑动切换效果的小例子 ------- 来自<一只有梦想的前端小白> 大家都知道jQuery 提供的有一下几种方法能够实现滑动效果&#xff1a; slideDown()slideUp()slideToggle()但是以上的滑动不太方便控制其滑动的方向&#xff0c;所以我们还是…

[不一样的依赖注入]通过递归实现容器里依赖注入

递归实现依赖注入创建所需的依赖服务类1public class Test2{3 public void PrintTest()4 {5 Console.WriteLine("Hello World");6 }7}89public class Test2 10{ 11 private readonly Test _test; 12 13 public Test2(Test test) 14 { 15 …

ArcGIS实验教程——实验十七:缓冲区分析(Buffer Analysis)

ArcGIS实验视频教程合集:《ArcGIS实验教程从入门到精通》(附配套实验数据) 【实验描述】 缓冲区(Buffer)是为了识别某一地理实体对周围地物的影响而在其周围建立的一定宽度多边形区域,缓冲区分析(Buffer Analysis)是用来确定不同地理要素的空间临近性或接近程度的一种分…

Java之jdk和CGLib实现动态代理

1 jdk实现动态代理源码实现 这里需要用到InvocationHandler接口 public interface Hello {public void sayHello(); } public class HelloImpl implements Hello {Overridepublic void sayHello() {System.out.println("hello word");} }import java.lang.reflect…

从Visual Studio中生成Linux设备

本文讲的是从Visual Studio中生成Linux设备&#xff0c;【IT168 云计算频道】近日Novell发布了SUSE Studio&#xff1a;一个用于创建Linux设备&#xff08;appliance&#xff09;的工具。与此同时&#xff0c;Mono小组创建了一个插件以从Visual Studio中生成支持SUSE的设备。 …

C++入门指南及实战 第二步 HelloWorld及扩展详解

回顾 在上一节中&#xff0c;我们编写了如下代码&#xff0c;完成了 HelloWorld程序的编写&#xff1a; #include<iostream> using namespace std;int main(){cout <<"Hello World";return 0; }本小节将会对该代码进行讲解&#xff0c;并且解释一下专业…

2560x1600分辨率高吗_做设计还弄不清分辨率和像素之间的关系,来了解下他们是怎么换算...

许多同学都在问我关于像素的问题&#xff0c;为什么印刷时要300分辨率以上&#xff1f;网页为什么72就够了&#xff1f;做户外喷绘30&#xff0c;甚至巨幅画面20就上了。关于这些还是很多人不知道的&#xff0c;要不也不会被卖手机的忽悠&#xff0c;各大手机推销员拿着手机大声…

使用 fixture 机制重构 appium_helloworld

一、前置说明 在 pytest 基础讲解 章节,介绍了 pytest 的特性和基本用法,现在我们可以使用 pytest 的一些机制,来重构 appium_helloworld 。 appium_helloworld 链接: 编写第一个APP自动化脚本 appium_helloworld ,将脚本跑起来 代码目录结构: pytest.ini 设置: [pyt…

linux程序调试命令strace

strace命令用法详解: strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界&#xff0c;进程不能直接访问硬件设备&#xff0c;当进程需要访问硬件设备(比如读取磁盘文件&#xff0c;接收网络数据等等)时&#xff0c;必须由用户态模式切换至内核态模式&#xff0…

Tomcat相关 -- 内存设置

java内存溢出详解 一、常见的java内存溢出 1、java.lang.OutOfMemmoryError : Java heap space -- JVM Heap &#xff08;jvm 堆溢出&#xff09; JVM启动时自动设置JVM Heap的值&#xff0c;其初始空间(即 -Xms)是物理内存的1/64&#xff0c;最大空间(-Xms)不可超过物理内存。…

CoreWCF 1.0 正式发布,支持 .NET Core 和 .NET 5+ 的 WCF

CoreWCF 项目组正式发布 1.0 版本的 CoreWCF, 这是面向 .NET Core 平台的 WCF 移植版本。它支持 SOAP、NetTCP 和 WSDL 的相同实现。在代码中的使用方式于 WCF 相同&#xff0c;但是升级到使用 ASP.NET Core 作为服务宿主&#xff0c;并工作在 .NET Core 平台上。这是该项目的第…