1、数据库优化一

来源:互联网 发布:商城商品详情页面源码 编辑:程序博客网 时间:2024/04/30 13:40

一、数据库三范式

 

说到软件开发 就不能不说数据库 说到数据库 就不能不说三范式 其实数据库范式一共有六个 除了第一 二 三范式以外还有第四 第五范式和一个BC范式 

为什么要用范式

范式是为了消除重复数据减少冗余数据,从而让数据库内的数据更好的组织,让磁盘空间得到更有效利用的一种标准化标准,满足高等级的范式的先决条件是满足低等级范式。

 

第一范式(1NF)

第一范式是指数据库表的每一列都是不可分割的基本数据项 同一列中不能有多个值 即实体中的某个属性不能有多个值或者不能有重复的属性

这就是第一范式的基本概念 什么意思呢 举个例子吧 在一个表里有一个“电话”字段 其值既有手机号 又有座机号 那么这样就不符合第一范式的要求了 此时应该将“电话”拆分为“手机”跟“座机” 这样一来就变成了一个符合第一范式的数据表了

总结:具有主键、主键唯一、主键不为空、字段不能再分

 

第二范式(2NF)

第二范式是在第一范式的基础上建立起来的,即满足第二范式必须先满足第一范式,第二范式要求数据库表中的每个实例或行必须可以被惟一的区分,为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识

要求实体的属性完全依赖于主键,要讨论一个表符不符合第二范式的前提是这个表的主键为组合主键,如果不是组合主键那就谈不上符不符合第二范式了

总结:不能部分依赖,就是说当一个表有组合主键时,其他非主键的字段必须完全依赖于主键。

 

第三范式(3NF)

第三范式是要符合第二范式,第三范式就是一个数据表中不能存在其他数据表中的非主键字段,也就是说如果存在其他表中的字段就一定得是那个表的主键

假如一个表有有a b c三个字段 a—>b b—>c 那么c与a就是传递依赖这样就不符合第三范式 而且不管a能不能直接确定c都属于传递依赖所以只有b跟c不依赖于a以外的任何字段才符合第三范式

总结:不可以有传递依赖,就是说主键以外的字段必须依赖主键 而不能依赖其他字段

 

二、为什么要用存储过程

存储过程概念

存储过程是一些sql语句和控制语句组成的被封装起来的过程,它驻留在数据库中,可以被客户应用程序通过存储过程名字调用,也可以从另一个存储过程或触发器调用。

它的参数可以被传递和返回,与应用程序中的函数过程类似,存储过程可以通过名字来调用,具有输入参数和输出参数。

存储过程分类

1) 返回记录集的存储过程              ----从数据库返回符合一个或几个条件的记录

2) 返回数值的存储过程(标量存储过程)----在数据库执行一个有返回值的函数或命令

3) 行为存储过程                      -----在数据库中的更新和删除操作

为什么要是用存储过程?

(1)执行速度快——存储过程只在创建时进行编译,以后每次执行存储过程都不需要重新编译,而一般SQL语句没执行一次就需编译一次,所以使用存储过程可提高数据库的执行速度。

减少网络通信量——当对数据库进行复杂操作时,(如对多个表进行insert、update、select、delete时)可将这些复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。这些操作,如果用程序完成就是多条SQL语句,可能要多次连接数据库,而换成存储过程只需一次连接。更强的适应性与复用性——存储过程可以重复使用,提高了可重用性,减少数据库开发人员的工作量。

(4)可维护性高——更新存储过程通常比更改、测试以及重新部署程序集需要较少的时间和精力。

(5)安全性高,可设定只有某用户才能对指定存储过程的使用权,且存储过程比多条sql稳定,只要数据库不出现问题,基本上是不会出现什么问题的。

(6)分布式工作——应用程序和数据库的编码工作可以单独进行,减少耦合度。

(7)更好的版本控制,通过svn等源代码控制工具可以轻松恢复或引用旧版本的存储过程。

 

存储过程的缺点

1)开发调试差:无良好的IDE开发工具,存储过程的调试比一般SQL要复杂的多。

2)可移植性差:由于存储过程将应用程序绑定到数据库上,因此使用存储过程封装业务逻辑将限制应用程序的可移植性。

3)重新编译问题,因为后端代码是运行前编译的,如果带有引用关系的对象发生改变时,受影响的存储过程、包将需要重新编译(不过也可以设置成运行时刻自动编译)。

4)如果在一个程序系统中大量的使用存储过程,到程序交付使用的时候随着用户需求的增加会导致数据结构的变化,接着就是系统的相关问题了,最后如果用户想维护该系统可以说是很难很难、而且代价是空前的,维护起来更麻烦。

 

三、数据库优化一

1.实践中如何优化MySQL

1) SQL语句及索引的优化

2) 数据库表结构的优化

3) 系统配置的优化

4) 硬件优化

 

2.索引的底层实现原理和优化

 DB2 数据库中索引采用的是 B+ 树的结构,索引的叶子节点上包含索引键的值和一个指向数据地址的指针。DB2 先查询索引,然后通过索引里记录的指针,直接访问表的数据页。

B+树。B+树是应数据库所需而出现的一种B树的变形树。

B+树的特点:

(1)所有叶节点包含全部关键字及指向相应记录的指针,而且叶节点中将关键字按大小顺序排列,并且相邻叶节点按大小顺序相互链接起来。

(2)所有分支节点(可看做索引的索引)中仅包含它的各个子节点(即下一级的索引块)中关键字的最大值即指向其子节点的指针。

(3)B+树中,叶节点包含信息,所有非叶结点仅起到索引作用,非叶节点中的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有该关键字对应记录的存储地址。

(4)叶节点包含了所有的关键字,即在非叶节点出现的关键字也会出现在叶子节点中。

B+树有两个头指针,一个指向根节点,另一个指向关键字最小的叶节点。B+树进行两种查找运算:从最小关键字开始的顺序查找,另一种从根节点开始的多路查找。

原理:叶子节点是按关键字大小顺序排列,且增加了指向下一个叶子节点的指针。

优化:InnoDB建议大部分表使用默认的自增的主键作为索引

MsSqlDB2使用的是B+Tree,Oracle及Sysbase使用的是B-Tree

SQL语句的优化

1) 尽量避免耗时操作。

带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句会启动SQL执行,耗费资源的排序(SORT)功能。DISTINCT需要一次排序操作, 而其他的至少需要执行两次排序

2) 如果无需排除重复值或是操作集无重复则用UNION ALL UNION更费事(因为要比较)

UNION因为会将各查询子集的记录做比较,故比起UNION ALL ,通常速度都会慢上许多。一般来说,如果使用UNION ALL能满足要求的话,务必使用UNION ALL。还有一种情况大家可能会忽略掉,就是虽然要求几个子集的并集需要过滤掉重复记录,但由于脚本的特殊性,不可能存在重复记录,这时便应该使用UNION ALL,如xx模块的某个查询程序就曾经存在这种情况,见,由于语句的特殊性,在这个脚本中几个子集的记录绝对不可能重复,故可以改用UNION ALL)连接操作

3) 避免在WHERE子句中使用in,not  in,or 或者having。
可以使用 exist 和not exist代替 in和not in。
可以使用表链接代替 exist。
Having可以用where代替,如果无法代替可以分两步处理。
例子

[java] view plain copy 

SELECT * FROM ORDERS WHERE CUSTOMER_NAME NOT IN  

(SELECT CUSTOMER_NAME FROM CUSTOMER)  

优化

[java] view plain copy 

SELECT * FROM ORDERS WHERE CUSTOMER_NAME not exist  

(SELECT CUSTOMER_NAME FROM CUSTOMER)  

4) 不要在建立的索引的数据列上进行下列操作:
1)避免对索引字段进行计算操作

2)避免在索引字段上使用not,<>,!=

3)避免在索引列上使用IS NULL和IS NOT NULL

4)避免在索引列上出现数据类型转换

5)避免在索引字段上使用函数

例如:where trunc(create_date)=trunc(:date1)
虽然已对create_date 字段建了索引,但由于加了TRUNC,使得索引无法用上。此处正确的写法应该是
where create_date>=trunc(:date1) and create_date

6)避免建立索引的列中使用空值。

5) 查询的模糊匹配

尽量避免在一个复杂查询里面使用 LIKE '%parm1%'—— 红色标识位置的百分号会导致相关列的索引无法使用,最好不要用。

解决办法:

其实只需要对该脚本略做改进,查询速度便会提高近百倍。改进方法如下:

a、修改前台程序——把查询条件的供应商名称一栏由原来的文本输入改为下拉列表,用户模糊输入供应商名称时,直接在前台就帮忙定位到具体的供应商,这样在调用后台程序时,这列就可以直接用等于来关联了。

b、直接修改后台——根据输入条件,先查出符合条件的供应商,并把相关记录保存在一个临时表里头,然后再用临时表去做复杂关联

6) 避免使用临时表
(1)除非却有需要,否则应尽量避免使用临时表,相反,可以使用表变量代替;
(2)大多数时候(99%),表变量驻扎在内存中,因此速度比临时表更快,临时表驻扎在TempDb数据库中,因此临时表上的操作需要跨数据库通信,速度自然慢。

可以使用联合(UNION)来代替手动创建的临时表

MySQL  4.0 的版本开始支持 UNION 查询,它可以把需要使用临时表的两条或更多的 SELECT 查询合并的一个查询中。在客户端的查询会话结束的时候,临时表会被自动删除,从而保证数据库整齐、高效。使用 UNION 来创建查询的时候,我们只需要用UNION作为关键字把多个SELECT语句连接起来就可以了,要注意的是所有 SELECT 语句中的字段数目要想同。下面的例子就演示了一个使用 UNION的查询。

代码如下:

[java] view plain copy 

SELECT Name, Phone FROM client UNION SELECT Name, BirthDate FROM author  

UNION  

SELECT Name, Supplier FROM product  

7) 尽量少做重复的工作
尽量减少无效工作,但是这一点的侧重点在客户端程序,需要注意的如下:
A、 控制同一语句的多次执行,特别是一些基础数据的多次执行是很多程序员很少注意的
B、减少多次的数据转换,也许需要数据转换是设计的问题,但是减少次数是程序员可以做到的。
C、杜绝不必要的子查询和连接表,子查询在执行计划一般解释成外连接,多余的连接表带来额外的开销。
D、合并对同一表同一条件的多次UPDATE,比如
UPDATE EMPLOYEE SET FNAME='HAIWER' WHERE EMP_ID=' VPA30890F'
UPDATE EMPLOYEE SET LNAME='YANG' WHERE EMP_ID=' VPA30890F'
这两个语句应该合并成以下一个语句
UPDATE EMPLOYEE SET FNAME='HAIWER',LNAME='YANG'
WHERE EMP_ID=' VPA30890F'
EUPDATE操作不要拆成DELETE操作+INSERT操作的形式,虽然功能相同,但是性能差别是很大的。
F、不要写一些没有意义的查询,比如
SELECT * FROM EMPLOYEE WHERE 1=2

Where后面的原则

第一个原则:在where子句中应把最具限制性的条件放在最前面。

第二个原则:where子句中字段的顺序应和索引中字段顺序一致。

select field3,field4 from tb where upper(field2)='RMN'不使用索引。
如果一个表有两万条记录,建议不使用函数;如果一个表有五万条以上记录,严格禁止使用函数!两万条记录以下没有限制。

 

3.什么情况下设置了索引但无法使用,索引无效

1) 以”%”开头的LIKE语句,模糊匹配:红色标识位置的百分号会导致相关列的索引无法使用

2) Or语句前后没有同时使用索引

3) 数据类型出现隐式转化(如varchar不加单引号的话可能会自动转换为int型,会使索引无效,产生全表扫描。)

4) 在索引列上使用IS NULL IS NOT NULL操作。索引是不索引空值的,所以这样的操作不能使用索引,可以用其他的办法处理,例如:数字类型,判断大于0,字符串类型设置一个默认值,判断是否等于默认值即可

5) 在索引字段上使用not,<>,!=eg<> 操作符(不等于):不等于操作符是永远不会用到索引的,因此对它的处理只会产生全表扫描。 用其它相同功能的操作运算代替,如 a<>0 改为 a>0 or a<0

6) 对索引字段进行计算操作

7) 在索引字段上使用函数

 

4.如何设计一个高并发的系统

1) 数据库的优化,包括合理的事务隔离级别、SQL语句优化、索引优化

2) 使用缓存、尽量减少数据库IO

3) 分布式数据库、分布式缓存

4) 服务器的负载均衡

 

四、数据库优化二:数据库索引

索引使用规则:

1)让限制条件更大的索引放在前面(根据“匹配索引扫描”效率更高)

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

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

 

什么是索引 

在数据库中,索引的含义与日常意义上的“索引”一词并无多大区别(想想小时候查字典),它是用于提高数据库表数据访问速度的数据库对象 
A)索引可以避免全表扫描。多数查询可以仅扫描少量索引页及数据页,而不是遍历所有数据页。
B)对于非聚集索引,有些查询甚至可以不访问数据页
C)聚集索引可以避免数据插入操作集中于表的最后一个数据页
D)一些情况下,索引还可用于避免排序操作。

当然,众所周知,虽然索引可以提高查询速度,但是它们也会导致数据库系统更新数据的性能下降,因为大部分数据更新需要同时更新索引。

2.索引的存储 

一条索引记录中包含的基本信息包括:键值(即你定义索引时指定的所有字段的值)+逻辑指针(指向数据页或者另一索引页)。

当你为一张空表创建索引时,数据库系统将为你分配一个索引页,该索引页在你插入数据前一直是空的。此页此时既是根结点,也是叶结点。每当你往表中插入一行数据,数据库系统即向此根结点中插入一行索引记录。当根结点满时,数据库系统大抵按以下步骤进行分裂:
A)创建两个儿子结点
B)将原根结点中的数据近似地拆成两半,分别写入新的两个儿子结点
C)根结点中加上指向两个儿子结点的指针 

通常状况下,由于索引记录仅包含索引字段值(以及4-9字节的指针),索引实体比真实的数据行要小许多,索引页相较数据页来说要密集许多。一个索引页可以存储数量更多的索引记录,这意味着在索引中查找时在I/O上占很大的优势,理解这一点有助于从本质上了解使用索引的优势。 

3.索引的类型 

A)聚集索引,表数据按照索引的顺序来存储的。对于聚集索引,叶子结点即存储了真实的数据行,不再有另外单独的数据页。
B)非聚集索引,表数据存储顺序与索引顺序无关。对于非聚集索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针,该层紧邻数据页,其行数量与数据表行数据量一致。 

在一张表上只能创建一个聚集索引,因为真实数据的物理顺序只可能是一种。如果一张表没有聚集索引,那么它被称为“堆集”(Heap)。这样的表中的数据行没有特定的顺序,所有的新行将被添加的表的末尾位置。 

4.聚集索引 

在聚集索引中,叶结点也即数据结点,所有数据行的存储顺序与索引的存储顺序一致。 

1)聚集索引与查询操作 

如上图,我们在名字字段上建立聚集索引,当需要在根据此字段查找特定的记录时,数据库系统会根据特定的系统表查找的此索引的根,然后根据指针查找下一个,直到找到。例如我们要查询Green”,由于它介于[Bennet,Karsen],据此我们找到了索引页1007,在该页中“Green”介于[Greane, Hunter]间,据此我们找到叶结点1133(也即数据结点),并最终在此页中找以了目标数据行。 

此次查询的IO包括3个索引页的查询(其中最后一次实际上是在数据页中查询)。这里的查找可能是从磁盘读取(Physical Read)或是从缓存中读取(Logical Read),如果此表访问频率较高,那么索引树中较高层的索引很可能在缓存中被找到。所以真正的IO可能小于上面的情况。 

5.非聚集索引 

非聚集索引与聚集索引相比:
A)叶子结点并非数据结点
B)叶子结点为每一真正的数据行存储一个“键-指针”对
C)叶子结点中还存储了一个指针偏移量,根据页指针及指针偏移量可以定位到具体的数据行。
D)类似的,在除叶结点外的其它索引结点,存储的也是类似的内容,只不过它是指向下一级的索引页的。

聚集索引是一种稀疏索引,数据页上一级的索引页存储的是页指针,而不是行指针。而对于非聚集索引,则是密集索引,在数据页的上一级索引页它为每一个数据行存储一条索引记录。

对于根与中间级的索引记录,它的结构包括:
A)索引字段值
BRowId(即对应数据页的页指针+指针偏移量)。在高层的索引页中包含RowId是为了当索引允许重复值时,当更改数据时精确定位数据行。
C)下一级索引页的指针

对于叶子层的索引对象,它的结构包括:
A)索引字段值
BRowId

1)非聚集索引与查询操作

针对上图,如果我们同样查找Green”,那么一次查询操作将包含以下IO3个索引页的读取+1个数据页的读取。同样,由于缓存的关系,真实的IO实际可能要小于上面列出的。

6.索引覆盖 

索引覆盖是这样一种索引策略:当某一查询中包含的所需字段皆包含于一个索引中,此时索引将大大提高查询性能。

包含多个字段的索引,称为复合索引。索引最多可以包含31个字段,索引记录最大长度为600B。如果你在若干个字段上创建了一个复合的非聚集索引,且你的查询中所需Select字段及Where,Order By,Group By,Having子句中所涉及的字段都包含在索引中,则只搜索索引页即可满足查询,而不需要访问数据页。由于非聚集索引的叶结点包含所有数据行中的索引列值,使用这些结点即可返回真正的数据,这种情况称之为“索引覆盖”。

在索引覆盖的情况下,包含两种索引扫描:
A)匹配索引扫描


B)非匹配索引扫描

1)匹配索引扫描

此类索引扫描可以让我们省去访问数据页的步骤,当查询仅返回一行数据时,性能提高是有限的,但在范围查询的情况下,性能提高将随结果集数量的增长而增长。

针对此类扫描,索引必须包含查询中涉及的的所有字段,另外,还需要满足:Where子句中包含索引中的“引导列”(Leading Column),例如一个复合索引包含A,B,C,D四列,则A为“引导列”。如果Where子句中所包含列是BCD或者BD等情况,则只能使用非匹配索引扫描。

2)非配置索引扫描

正如上述,如果Where子句中不包含索引的导引列,那么将使用非配置索引扫描。这最终导致扫描索引树上的所有叶子结点,当然,它的性能通常仍强于扫描所有的数据页。

 

五、数据库优化三:数据库语句分析

们在使用SQL时往往会陷入一个误区,即太关注于所得的结果是否正确,而忽略了不同的实现方法之间可能存在的性能差异,这种性能差异在大型的或是复杂的数据库环境中(如联机事务处理OLTP或决策支持系统DSS)中表现得尤为明显。

笔者在工作实践中发现,不良的SQL往往来自于不恰当的索引设计、不充份的连接条件和不可优化的where子句。

在对它们进行适当的优化后,其运行速度有了明显地提高!

下面将从这三个方面分别进行总结:

为了更直观地说明问题,所有实例中的SQL运行时间均经过测试,不超过1秒的均表示为(< 1秒)。----

测试环境主机:HP LH II---- 主频:330MHZ---- 内存:128----

操作系统Operserver5.0.4----

数据库:Sybase11.0.3

 

一、不合理的索引设计----

例:表record620000行,试看在不同的索引下,下面几个 SQL的运行情况:

---- 1.date上建有一非个群集索引

[java] view plain copy 

select count(*) from record where date >'19991201' and date < '19991214'and amount >2000 (25)  

select date ,sum(amount) from record group by date(55)  

select count(*) from record where date >'19990901' and place in ('BJ','SH') (27)  

---- 分析:----

date上有大量的重复值,在非群集索引下,数据在物理上随机存放在数据页上,在范围查找时,必须执行一次表扫描才能找到这一范围内的全部行。

---- 2.date上的一个群集索引

[java] view plain copy 

select count(*) from record where date >'19991201' and date < '19991214' and amount >2000 14秒)  

select date,sum(amount) from record group by date28秒)  

select count(*) from record where date >'19990901' and place in ('BJ','SH')14秒)  

---- 分析:---- 在群集索引下,数据在物理上按顺序在数据页上,重复值也排列在一起,因而在范围查找时,可以先找到这个范围的起末点,且只在这个范围内扫描数据页,避免了大范围扫描,提高了查询速度。

---- 3.placedateamount上的组合索引

[java] view plain copy 

select count(*) from record where date >'19991201' and date < '19991214' and amount >2000 26秒)  

select date,sum(amount) from record group by date27秒)  

select count(*) from record where date >'19990901' and place in ('BJ, 'SH')< 1秒)  

---- 分析:---- 这是一个不很合理的组合索引,因为它的前导列是place,第一和第二条SQL没有引用place,因此也没有利用上索引;第三个SQL使用了place,且引用的所有列都包含在组合索引中,形成了索引覆盖,所以它的速度是非常快的。

---- 4.dateplaceamount上的组合索引

[java] view plain copy 

select count(*) from record where date >'19991201' and date < '19991214' and amount >2000(< 1)  

select date,sum(amount) from record group by date11秒)  

select count(*) from record where date >'19990901' and place in ('BJ','SH')< 1秒)  

---- 分析:---- 这是一个合理的组合索引。它将date作为前导列,使每个SQL都可以利用索引,并且在第一和第三个SQL中形成了索引覆盖,因而性能达到了最优。

---- 5.总结:----

缺省情况下建立的索引是非群集索引,但有时它并不是最佳的;合理的索引设计要建立在对各种查询的分析和预测上。

一般来说:

.有大量重复值、且经常有范围查询(between, >,<>=,< =)和order bygroup by发生的列,可考虑建立群集索引;

.经常同时存取多列,且每列都含有重复值可考虑建立组合索引;

.组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。

二、不充份的连接条件:

例:表card7896行,在card_no上有一个非聚集索引,表account191122行,在account_no上有一个非聚集索引,试看在不同的表连接条件下,两个SQL的执行情况:

[sql] view plain copy 

select sum(a.amount) from account a,card b where a.card_no = b.card_no20秒)  

select sum(a.amount) from account a,card b where a.card_no = b.card_no and a.account_no=b.account_no< 1秒)  

----分析:----在第一个连接条件下,最佳查询方案是将account作外层表,card作内层表,利用card上的索引,其I/O次数可由以下公式估算为:

外层表account上的22541+(外层表account191122*内层表card上对应外层表第一行所要查找的3页)=595907I/O

在第二个连接条件下,最佳查询方案是将card作外层表,account作内层表,利用account上的索引,其I/O次数可由以下公式估算为:外层表card上的1944+(外层表card7896*内层表account上对应外层表每一行所要查找的4页)= 33528I/O

可见,只有充份的连接条件,真正的最佳方案才会被执行。

总结:

1.多表操作在被实际执行前,查询优化器会根据连接条件,列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表;内外表的选择可由公式:外层表中的匹配行数*内层表中每一次查找的次数确定,乘积最小为最佳方案。

2.查看执行方案的方法-- set showplanon,打开showplan选项,就可以看到连接顺序、使用何种索引的信息;想看更详细的信息,需用sa角色执行dbcc(3604,310,302)

 

三、不可优化的where子句

21.例:下列SQL条件语句中的列都建有恰当的索引,但执行速度却非常慢:避免在索引字段上使用函数

[sql] view plain copy 

select * from record where substring(card_no,1,4)='5378'(13)  

select * from record where amount/30< 100011秒)  

select * from record where convert(char(10),date,112)='19991201'10秒)  

分析:

where子句中对列的任何操作结果都是在SQL运行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引;

如果这些结果在查询编译时就能得到,那么就可以被SQL优化器优化,使用索引,避免表搜索,因此将SQL重写成下面这样:

[sql] view plain copy 

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秒)  

你会发现SQL明显快起来!

2.例:表stuff200000行,id_no上有非群集索引,请看下面这个SQL

[sql] view plain copy 

select count(*) from stuff where id_no in('0','1')23秒)  

分析:---- where条件中的'in'在逻辑上相当于'or',所以语法分析器会将in ('0','1')转化为id_no ='0' or id_no='1'来执行。

我们期望它会根据每个or子句分别查找,再将结果相加,这样可以利用id_no上的索引;

但实际上(根据showplan,它却采用了"OR策略",即先取出满足每个or子句的行,存入临时数据库的工作表中,再建立唯一索引以去掉重复行,最后从这个临时表中计算结果。因此,实际过程没有利用id_no上索引,并且完成时间还要受tempdb数据库性能的影响。

实践证明,表的行数越多,工作表的性能就越差,当stuff620000行时,执行时间竟达到220秒!还不如将or子句分开:

[sql] view plain copy 

select count(*) from stuff where id_no='0'select count(*) from stuff where id_no='1'  

得到两个结果,再作一次加法合算。因为每句都使用了索引,执行时间只有3秒,在620000行下,时间也只有4秒。

或者,用更好的方法,写一个简单的存储过程:

[sql] view plain copy 

create proc count_stuff asdeclare @a intdeclare @b intdeclare @c intdeclare @d char(10)beginselect @a=count(*) from stuff where id_no='0'select @b=count(*) from stuff where id_no='1'endselect @c=@a+@bselect @d=convert(char(10),@c)print @d  

直接算出结果,执行时间同上面一样快!

----总结:----
可见,所谓优化即where子句利用了索引,不可优化即发生了表扫描或额外开销。

1.任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。

2.inor子句常会使用工作表,使索引失效;如果不产生大量重复值,可以考虑把子句拆开;拆开的子句中应该包含索引。

3.要善于使用存储过程,它使SQL变得更加灵活和高效。

从以上这些例子可以看出,SQL优化的实质就是在结果正确的前提下,用优化器可以识别的语句,充份利用索引,减少表扫描的I/O次数,尽量避免表搜索的发生。其实SQL的性能优化是一个复杂的过程,上述这些只是在应用层次的一种体现,深入研究还会涉及数据库层的资源配置、网络层的流量控制以及操作系统层的总体设计。

1、开发人员如果用到其他库的TableView,务必在当前库中建立View来实现跨库操作,最好不要直接使用“databse.dbo.table_name”,因为sp_depends不能显示出该SP所使用的跨库tableview,不方便校验。

2、开发人员在提交SP前,必须已经使用set showplan on分析过查询计划,做过自身的查询优化检查。

3、高程序运行效率,优化应用程序,在SP编写过程中应该注意以下几点:

a) SQL的使用规范:

i. 尽量避免大事务操作,慎用holdlock子句,提高系统并发能力。

ii. 尽量避免反复访问同一张或几张表,尤其是数据量较大的表,可以考虑先根据条件提取数据到临时表中,然后再做连接。

iii. 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该改写;如果使用了游标,就要尽量避免在游标循环中再进行表连接的操作。

iv. 注意where字句写法,必须考虑语句顺序,应该根据索引顺序、范围大小来确定条件子句的前后顺序,尽可能的让字段顺序与索引顺序相一致,范围从大到小。

v. 不要在where子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

vi. 尽量使用exists代替select count(1)来判断是否存在记录,count函数只有在统计表中所有行数时使用,而且count(1)count(*)更有效率。

vii. 尽量使用“>=”,不要使用“>”。

viii. 注意一些or子句和union子句之间的替换

ix. 注意表之间连接的数据类型,避免不同类型数据之间的连接。

x. 注意存储过程中参数和数据类型的关系。

xi. 注意insertupdate操作的数据量,防止与其他应用冲突。如果数据量超过200个数据页面(400k),那么系统将会进行锁升级,页级锁会升级成表级锁。

b) 索引的使用规范:

i. 索引的创建要与应用结合考虑,建议大的OLTP表不要超过6个索引。

ii. 尽可能的使用索引字段作为查询条件,尤其是聚簇索引,必要时可以通过index index_name来强制指定索引

iii. 避免对大表查询时进行table scan,必要时考虑新建索引。

iv. 在使用索引字段作为条件时,如果该索引是联合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用。

v. 要注意索引的维护,周期性重建索引,重新编译存储过程。

c) tempdb的使用规范:

i. 尽量避免使用distinctorder bygroup byhavingjoin***pute,因为这些语句会加重tempdb的负担。

ii. 避免频繁创建和删除临时表,减少系统表资源的消耗。

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

iv. 如果临时表的数据量较大,需要建立索引,那么应该将创建临时表和建立索引的过程放在单独一个子存储过程中,这样才能保证系统能够很好的使用到该临时表的索引。

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

vi. 慎用大的临时表与其他大表的连接查询和修改,减低系统表负担,因为这种操作会在一条语句中多次使用tempdb的系统表。

d) 合理的算法使用:

根据上面已提到的SQL优化技术和ASE Tuning手册中的SQL优化内容,结合实际应用,采用多种算法进行比较,以获得消耗资源最少、效率最高的方法。具体可用ASE调优命令:set statistics io on, set statistics time on , set showplan on

 

 

0 0
原创粉丝点击