数据库调优策略

来源:互联网 发布:淘宝关键词点击率查询 编辑:程序博客网 时间:2024/05/02 04:16

1.1  名词解释

OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。OLAP数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果.

1.2  硬件优化

1.2.1  数据库分区

利用数据库分区技术,均匀地把数据分布在系统的磁盘中,平衡I/O访问,避免I/O瓶颈:

 (1)访问分散到不同的磁盘,即使用户数据尽可能跨越多个设备,多个I/O运转,避免I/O竞争,克服访问瓶颈;分别放置随机访问和连续访问数据。

(2)分离系统数据库I/O和应用数据库I/O,把系统审计表和临时库表放在不忙的磁盘

上。

(3)把事务日志放在单独的磁盘上,减少磁盘I/O开销,这还有利于在障碍后恢复,提高了系统的安全性。

(4)把频繁访问的“活性”表放在不同的磁盘上;把频繁用的表、频繁做Join的表分别放在单独的磁盘上,甚至把频繁访问的表的字段放在不同的磁盘上,把访问分散到不同的磁盘上,避免I/O争夺。

1.2.2  RAID

RAID0:称为条带化(Striping)存储,将数据分段存储于各个磁盘中,读写均可以并行处理。因此其读写速率为单个磁盘的N倍(N为组成RAID0的磁盘个数),但是却没有数 据冗余,单个磁盘的损坏会导致数据的不可修复


RAID 1:镜像存储(mirroring),没有数据校验。数据被同等地写入两个或多个磁盘中,可想而知,写入速度会比较慢,但读取速度会比较快。读取速度可以接近所有磁盘吞吐量的总和,写入速度受限于最慢的磁盘。 RAID1也是磁盘利用率最低的一个。如果用两个不同大小的磁盘建立RAID1,可以用空间较小的那一个,较大的磁盘多出来的部分可以作他用,不会浪费。


RAID 5:奇偶校验(XOR),数据以块分段条带化存储。校验信息交叉地存储在所有的数据盘上。


RAID5把数据和相对应的奇偶校验信息存储到组成RAID5的各个磁盘上,并且奇偶校验信息和相对应的数据分别存储于不同的磁盘上,其中任意N-1块磁盘上都存储完整的数据,也就是说有相当于一块磁盘容量的空间用于存储奇偶校验信息。因此当RAID5的一个磁盘发生损坏后,不会影响数据的完整性,从而保证了数据安全。当损坏的磁盘被替换后,RAID还会自动利用剩下奇偶校验信息去重建此磁盘上的数据,来保持RAID5的高可靠性。

RAID 5可以理解为是RAID 0和RAID 1的折衷方案。RAID 5可以为系统提供数据安全保障,但保障程度要比镜像低而磁盘空间利用率要比镜像高。RAID 5具有和RAID 0相近似的数据读取速度,只是因为多了一个奇偶校验信息,写入数据的速度相对单独写入一块硬盘的速度略慢

 

1.3  设计优化

1.3.1  程序设计

从数据库设计者的角度来看,应用程序无非是实现对数据的增加、修改、删除、查询和体现数据的结构和关系。设计者在性能方面的考虑因素,总的出发点是:把数据库当作奢侈的资源看待,在确保功能的同时,尽可能少地动用数据库资源。包括如下原则:  (1)不访问或少访问数据库;

(2)简化对数据库的访问;

(3)使访问最优; 

(4)对前期及后续的开发、部署、调整提出要求,以协助实现性能目标;

(5)不要直接执行完整的SQL 语法,尽量通过存储过程来调用数据库

(6)客户与服务器连接时,建立连接池;

(7)非到不得已,不要使用游标结构。确实使用时,注意各种游标的特性。

(8)归纳一些业务逻辑放在数据库编程实现。数据库编程包括数据库存储过程、触发器和函数,用数据库编程实现业务逻辑的好处是减少网络流量并可更充分利用数据库的预编译和缓存功能。

1.3.2  数据库表设计

1.3.2.1  范式

第一范式:当关系模式R的所有属性都不能在分解为更基本的数据单位时,称R是满足第一范式的,简记为1NF;

第二范式:如果关系模式R满足第一范式,并且R得所有非主属性都完全依赖于R的每一个候选关键属性,称R满足第二范式,简记为2NF。

R是一个满足第一范式条件的关系模式,XR的任意属性集,如果X非传递依赖(直接依赖)于R的任意一个候选关键字,称R满足第三范式,简记为3NF。也就是说, 属性之间不能存在传递关系,即每个属性都跟主键有直接关系而不是间接关系

设计时,尽量满足3NF,但也要考虑实际情况,如增加冗余字段以提高查询效率等。如果全部达到第二范式,大部分达到第三范式,系统会产生较少的列和较多的表,因而减少了数据冗余,也利于性能的提高

1.3.2.2  合理冗余

完全按照规范化设计的系统几乎是不可能的,除非系统特别的小,在规范化设计后,有计划地加入冗余是必要的。冗余可以是冗余数据库、冗余表或者冗余字段,不同粒度的冗余可以起到不同的作用。冗余可以是为了编程方便而增加,也可以是为了性能的提高而增加。从性能角度来说,冗余数据库可以分散数据库压力,冗余表可以分散数据量大的表的并发压力,也可以加快特殊查询的速度,冗余字段可以有效减少数据库表的连接,提高效率。

1.3.2.3  主键设计

主键是必要的,而且在实际应用中,我们往往选择最小的键组合作为主键,所以主键往往适合作为表的聚集索引,聚集索引对查询的影响是比较大的。

在有多个键的表,主键的选择也比较重要,一般选择总的长度小的键,小的键的比较速度快,同时小的键可以使主键的B树结构的层次更少。主键的选择还要注意组合主键的字段次序,对于组合主键来说,不同的字段次序的主键的性能差别可能会很大,一般应该选择重复率低、单独或者组合查询可能性大的字段放在前面。

 

1.3.2.4  外键设计

外键作为数据库对象,很多人认为麻烦而不用,实际上,外键在大部分情况下是很有用的,理由是:外键是最高效的一致性维护方法,数据库的一致性要求,依次可以用外键、CHECK约束、规则约束、触发器、客户端程序,一般认为,离数据越近的方法效率越高。

谨慎使用级联删除和级联更新。因为级联删除和级联更新有些突破了传统的关于外键的定义,功能有点太过强大,使用前必须确定自己已经把握好其功能范围,否则,级联删除和级联更新可能让你的数据莫名其妙的被修改或者丢失。从性能看级联删除和级联更新是比其他方法更高效的方法。

1.3.2.5  字段设计

字段是数据库最基本的单位,其设计对性能的影响是很大的,对此,马海祥提醒大家要注意以下几点:

l  数据类型尽量用数字型数字型的比较比字符型的快很多

l  数据类型尽量小,这里的尽量小是指在满足可以预见的未来需求的前提下的。

尽量不要允许NULL,除非必要,可以用NOT NULL+DEFAULT代替。

少用TEXTIMAGE,二进制字段的读写是比较慢的,而且,读取的方法也不多,大部分情况下最好不用。

自增字段要慎用,不利于数据迁移

 

1.3.2.6  分割表

1)       水平分割

水平分割是按照行将一个表分割为多个表,这可以提高每个表的查询速度,但是由于造成了多表连接,所以应该在同时查询或更新不同分割表中的列的情况比较少的情况下使用。

2)       垂直分割

是对于一个列很多的表,若某些列的访问频率远远高于其它列,在不破坏第三范式的前提下将主键和这些列作为一个表,将主键和其它列作为另外一个表。

 

1.3.3  索引设计

1.3.3.1  基本知识

索引是一种特殊的文件(innoDB(事务性数据库的首选引擎)。数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。为表设置索引要付出代价的:一是增加了数据库的存储空间(索引也是文件,需要存储),二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。


上图展示了一种可能的索引方式。最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。中间是数据表,一共有两列七条记录。为了加快Col2的查找,可以在维护数据表的同时维护一个如右边所示的二叉查找树(即索引),每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针(如[34,0x07]),这样就可以运用二叉查找在O(log2n)的复杂度内获取到相应数据。

索引分为:聚集索引和非聚集索引。

聚集索引:如同字典中按照字母查询,我们把这种正文内容本身就是一种按照一定规则排列目录(正文本身就是目录)称为聚集索引(即不需要单独建立目录文件,因为内容本身就是目录)。我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。由于聚集索引规定了数据的物理存储顺序,因此一个表至多有一个聚集索引,但该索引可以包含多个列(组合索引)。

非聚集索引(先从目录中找到目标的位置信息,再根据位置信息找到正真的数据):非聚集索引与字典中按部首查字的内容相似。比如,当遇到不认识的字,不知道它的发音,这时候,就不能按照聚集索引的方式找到要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”“检字表”而查到的字的排序并不是真正的正文的排序方法,比如查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码

下图总结了聚集索引与非聚集索引的适用环境:


1.3.3.2  索引实现

索引的实现通常使用B树、B+树,B-树。

1.3.3.3  索引选择

l   根据数据量决定哪些表需要增加索引,数据量小的可以只有主键,不建立索引。

l   根据使用频率决定哪些字段需要建立索引,选择经常作为连接条件、筛选条件、聚合查询、排序的字段作为索引的候选字段。

l   把经常一起出现的字段组合在一起,组成组合索引,组合索引的字段顺序与主键一样,也需要把最常用的字段放在前面,把重复率低的字段放在前面。

l   一个表不要加太多索引,因为索引影响插入和更新的速度。

1.3.4  SQL语句优化

尽量必要全表扫描,条件越精确越好。

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

l   选择最有效率的表名顺序(只在基于规则的优化器中有效):ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表(放在最右边)。如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表.

l    WHERE子句中的连接顺序:ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前。那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾(也就是说,将最严格的条件放在where条件的最后)。

l    使用表的别名(Alias):当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上.这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误

l   避免使用having,使用where替代

l   应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null。可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select idfrom t where num=0

l   应尽量避免在 where 子句中使用!=<>操作符,否则将引擎放弃使用索引而进行全表扫描。

l   应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
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

UNION 操作符用于合并两个或多个 SELECT语句的结果集,默认返回不同值,使用UNION ALL 则可返回重复值。

l   in/not in 与exist/not exist的选择

in是把外表和内表作hash连接,而exists是对外表作loop循环,每次loop循环再对内表进行查询,一直以来认为exists比in效率高的说法是不准确的。如果查询的两个表大小相当,那么用in和exists差别不大;如果两个表中一个较小一个较大,则子查询表大的用exists(外层表小,循环较少),子查询表小的用in;无论哪个表大,用not exists都比not in 要快。对于连续的数值,能用 between就不要用 in


l   少用like。非不得已时,使用like “xxxx%”,即like的条件越精确越好或者使用一些函数来代替like,如使用charindex(‘c’,username)>0可达到like ‘%c%’的结果

下面的查询也将导致全表扫描:
select id from t where name like '%abc%'
若要提高效率,可以考虑全文检索。

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

l   可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num

l   应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2

l   应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc' // oracle总有的是substr函数。
select id from t where datediff(day,createdate,'2005-11-30')=0 //查过了确实没有datediff函数。
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' andcreatedate<'2005-12-1' // 
oracle 中时间应该把char转换成date 如:createdate >= to_date('2005-11-30','yyyy-mm-dd')

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

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

l   不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)

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

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

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

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

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

l   任何地方都不要使用 select *from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

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

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

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

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

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

l   尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

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

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

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

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

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

0 0
原创粉丝点击