SQL编写规范

ORACLE 专栏收录该内容
26 篇文章 0 订阅

 

、数据库开发建议

SQL语句编写规则

1.查询语句的使用原则

(1)索引的正确使用

合理的使用正确的索引是提高系统执行效率的关键因素,对索引的使用需要注意以下一些问题:

①过度索引

一般情况下,使用索引可以缩短查询语句的执行时间,提高系统的执行效率,但是要避免以下两种过度索引的情况出现:

  1. 对一个表建立了过多的索引,从而造成维护索引所需要的时间超过使用索引所降低的时间,从而造成整个系统效率的下降,这一般发生在对一些进行大量更新的表上面。因此一个联机表上的索引,最多不要超过5个。      
  2. 由于索引数据的区分度不够,造成了使用索引而引起的效率的下降,这一般发生在对数据进行大的统计分析的时候。可以通过指定全表扫描等提示(hint)来避免。

②LIKE运算符

在应用程序中为了使用方便,对字符型变量进行比较时经常使用LIKE运算符进行字符模式的匹配。

需要注意的是对于LIKE运算,如果通配符%出现在字符串的尾部或中间,LIKE运算符将可以使用索引进行字符串的匹配,否则如果通配符%出现在字符串的开始,则LIKE必须使用全表扫描的方式去匹配字符串,这将产生较大的系统负荷。

一般情况下,为了提高系统的效率,我们希望用户能够在通配符的左端提供较多的数据信息以降低查询的数量。

③NULL值

NULL值是系统中目前尚无法确定的值,在Oracle数据库系统中NULL是一个比所有的确定值都大的值,然而又不能用大于小于等于运算符来比较,对NULL值的处理只能用是与否来判定,所有的对NULL值的判定都会引起全表扫描,除非同时使用其它的查询条件。

  • 复合索引

复合索引是使用多个数据列的索引,第一个字段的数据区分度非常重要,也是影响一个联合索引效率的关键所在。改写查询语句:

①关联子查询与非关联子查询

对于一个关联子查询,子查询是每行外部查询的记录都要计算一次,然而对于一个非关联子查询,子查询只会执行一次,而且结果集被保存在内存中。

因此,通常在外部查询返回相对较少的记录时,关联子查询比非关联子查询执行得更快;而子查询中只有少量的记录的时候,则非关联子查询将会比关联子查询执行得更快。

②尽量用相同的数据类型的数据进行比较,以避免发生数据转换。

SQL语言对于数据类型不像JAVA和C++那样进行严格的数据类型检查,不同种数据间可以进行某些运算,但是在做数据操作时需要数据库进行隐含的类型转换,在大数据量的查询中,由于要对每一个数据项做同样的操作,会造成时间和CPU处理能力的浪费。

实际应用中通常发生的隐含的数据类型的转换有:

  1. 字符型到数字型的转换,如:SELECT ‘1234’ +3  FROM DUAL等
  2. 数字型到字符型的转换,如:UPDATE DEPT SET EMPNO=5678等
  3. 日期型到字符型的转换,如:UPDATE EMP SET DNAME=SYSDATE等

上述的转换都是隐含发生的,在实际使用中要避免使用不同类型的数据操作。

减少排序的发生:

排序是数据库中执行频度比较大的一种操作,根据排序执行的范围不同又可以分为内排序和外排序。我们希望数据库中的排序操作的数量能够被尽量的减少同时每个排序的时间能够缩短。为此我们可以:

  • 使用UNION ALL来代替UNION。
  • 添加索引。在表连接的时候使用索引可以避免排序的发生,比如添加了合适的索引,可以使连接方式由排序合并连接(Sort Merge Join)转变为索引的嵌套循环连接(Indexed Nestted Loop Join)。
  • 在DISTINCT,GROUP BY,ORDER BY子句涉及到的列上创建索引。
  • 使用较大SORT_AREA_SIZE
  • 在用户的临时表空间上使用大的extent大小。

使用并行查询:

并行查询适合下列情况:

  • 全表扫描的查询语句。
  • 返回大数据量的查询所改造的语句。
  • 其它一些数据操作中的查询子句。

对于较大的数据量的查询,我们可以使用提示(hint)来强制数据库使用并行查询,在Oracle数据库中,并行查询的优先级为语句提示(hint),表的定义,数据库初始化参数。

减少死锁的发生:

在Oracle数据库中大量的数据库的锁都是行级锁,不同的会话间竞争同一条记录的可能性较小,同时Oracle数据库中提供了自动的死锁检测机制来避免数据库的死锁,保证数据库系统的可用性。因此一般情况下应用系统不需要特殊的设计来解决系统的死锁问题,但是在下列情况下系统可能出现死锁:

  • 表A上的列n上有一个索引,表B上的列m使用A上的列n作为外键,然后表A的列n上的索引被删除,此时更新表B上列m将造成对表A的表级锁,会导致死锁的发生。
  • 应用大量的使用SELECT ……FOR UPDATE语句造成系统不必要的加锁。

对于第一种情况要对出现死锁的相关表进行检查,确认是否相关索引被错误的删除。对于第二种情况要修改应用,避免对数据的不必要的加锁。

集合运算符的使用:

Oracle数据库的集合运算包括: UNION, UNION ALL, INTERSECT和MINUS操作。

一般情况下当两个集合中的数据都比较多时,集合运算都是比较耗时的操作,使用时需要谨慎小心。如果可能,可以使用UNION ALL操作代替UNION操作。

(2)使用连接方式的原则

嵌套循环连接(NESTED LOOP JOIN):

知识点描述

嵌套循环连接操作关系到两个表,一个内部表和一个外部表。Oracle比较内部数据集的每一条记录和外部数据集的每一条记录,并返回满足条件的记录。

嵌套循环连接通常会产生巨大的数据量,所以对嵌套循环连接的使用要有严格的限制。

当一个嵌套循环连接的内部表中存在索引的情况,嵌套循环连接变为改进的有索引的嵌套循环连接(INDEXED NESTED LOOP JOIN),通常有索引的嵌套循环连接在产生较小的数据量的情况下可以较快的执行。

在使用有索引的嵌套循环连接是必须确保在查询中使用了正确的驱动表和正确的驱动数据集,通常情况下我们使用包含数据量较小的表作为驱动表。

一般如果我们使用基于成本的优化器,系统会自动选择驱动表,如果是使用基于规则的优化器,则后表作为驱动表。

应用原则

一般的嵌套循环连接的速度较慢,产生的数据量较大,应该严格控制其使用。

在使用有索引的嵌套循环连接时,必须保证其驱动表有合适的索引,最好为主键或唯一键,同时希望在另外一张表在相同的列上有索引。

散列连接(Hash Join):

知识点描述

散列连接将驱动表加载进内存,使用散列技术将驱动表与较大的表进行连接,连接过程中,对大表的访问使用了散列访问。散列连接可以提高等连接的速度。

如果可用的散列空间内存足够大,可以加载构建输入,那么散列连接算法能够很好地运行简单的散列连接,但是并不需要将整个输入放入hash_area_size内存。如果散列连接中较小的驱动表无法放入hash_area_size,那么Oracle将拆分该散列连接,并使用temp表空间中的临时段来管理这个溢出。

Oracle推荐将驱动表的hash_area_size设置为驱动表字节总数的1.6倍。

应用原则

一般的散列连接发生在一个大表和一个小表做连接的时候,此时小表中的数据全部被读入内存,其处理的速度较快。

排序合并连接(Sort Merge Join):

知识点描述

排序合并连接是指从目标表中读取两个记录数据集,并使用连接字段将两个记录集分别排序的操作。合并过程将来自一个数据集的每一条记录同来自另一个数据集与之匹配的记录相连接,并返回记录数据集的交集。

排序合并连接有几种不同的排序方法:外部合并连接,反合并连接和半合并连接。这些不同的排列方法使得合并步骤服务于不同的目的,可以是找到记录数据集的交集,也可以是找到满足SQL语句中WHERE子句条件的那些记录。

应用原则

一般的排序合并连接是在散列连接不能达到应用的要求或Oracle优化器认为排序合并连接效率更高的情况下使用。在下述的条件下排序合并连接被使用:

  1. 数据表之间的连接不是等值连接而是其它连接
  2. 数据库使用的优化模式是基于RBO而不是CBO

(3)进行复杂查询的原则

限制表连接操作所涉及的表的个数:

对于数据库的连接操作操作,我们可以简单的将其想象为一个循环匹配的过程,每一次匹配相当于一次循环,每一个连接相当于一层循环,则N个表的连接操作就相当于一个N-1层的循环嵌套。

一般的情况下在数据库的查询中涉及的数据表越多,则其查询的执行计划就越复杂,其执行的效率就越低,为此我们需要尽可能的限制参与连接的表的数量。

①3-5个表的处理方法

对于较少的数据表的连接操作,需要合理的确定连接的驱动表,从某种意义上说,确定合理的驱动表就是确定多层循环嵌套中的最外层的循环,可以最大限度的提高连接操作的效率,可见选择合适的驱动表的重要性。

RBO模式下,在SQL语句中FROM子句后面的表就是我们要进行连接操作的数据表,Oracle 按照从右到左的顺序处理这些表,让它们轮流作为驱动表去参加连接操作,这样我们可以把包含参与连接的数据量最少的表放在FROM子句的最右端,按照从右到左的顺序依次增加表中参与连接数据的量。

CBO模式下,则不需要考虑表放置的位置。

②5个表以上的处理方法

对于涉及较多的表(>5+)的数据连接查询,其查询的复杂度迅速增加,其连接的存取路径的变化更大,存取路径的个数与连接的表的个数的阶乘有关:当n=5时存取路径=1X2X3X4X5=120个,而当连接的表的个数为6时存取路径变为1X2X3X4X5X6=720个,数据库优化器对于数据的存取路径的判断近乎为不可能,此时完全依赖与用户的语句书写方式。

对于较多的表的连接,要求开发人员查询返回的结果能够有所预测,同时判断出各个参与连接的表中符合条件的记录的数量,从而控制查询的运行时间。

同时为了提高查询的效率,此时可以把部分表的一些连接所形成的中间结果来代替原来的连接表,从而减少连接的表的数目。

  • 对表连接操作涉及的表数目不应多于8个表

如果查询语句拥有过多的表连接,那么它的执行计划过于复杂且可控性降低,容易引起数据库的运行效率低下,即使在开发测试环境中已经经过充分的测试验证,也不能保证在生产系统上由于数据量的变化而引发的相关问题。应该在应用设计阶段就避免这种由于范式过高而导致的情况出现。

限制嵌套查询的层数:

应用中影响数据查询的效率的因素除了参与查询连接的表的个数以外,还有查询的嵌套层数。对于非关联查询,嵌套的子查询相当于使查询语句的复杂度在算术级数的基础上增长,而对于关联查询而言,嵌套的子查询相当于使查询语句的复杂度在几何级数的基础上增长。

因此,降低查询的嵌套层数有助于提高查询语句的效率。

对嵌套查询层数的限制要求:如果查询语句拥有过多的嵌套层数,那么会使该查询语句的复杂度高速增加,应该在数据库设计阶段就避免这种情况出现,不应多于5层。

灵活应用中间表或临时表:

在对涉及较多表的查询和嵌套层数较多的复杂查询的优化过程中,使用中间表或临时表是优化、简化复杂查询的一个重要的方法。

通过使用一些中间表,我们可以把复杂度为M*N的操作转化为复杂度为M+N的操作,当M和N都比较大时M+N <<M*N,则查询的复杂度被大大地降低。

使用一些改写复杂查询的技巧:

①转换连接类型

参见上文的改写查询语句部分

②把OR转换为UNION ALL

③区分不同的情况使用IN或EXISTS

对于主查询中包含较多条件而子查询条件较少的表使用EXISTS,对于主查询中包含较少条件而子查询条件较多的表使用IN。

④使用合理的连接方式

在不同的情况下使用不同的连接方式:散列连接适用于一个较小的表和较大的表的连接,排序合并连接需要对小表进行排序操作,索引的嵌套循环连接对于一般连接是有效的,但是需要在连接的关键字上有索引的存在。

应用开发人员应该根据不同的情况选取合适的连接方式。

①使用并行查询

如果查询的数据在表中所占的比例较大,可以考虑使用并行查询来提高查询的执行速度。

对于UPDATE/INSERT/DELETE操作的查询部分也可以同样做并行查询的处理。

②使用PL/SQL过程和临时表代替复杂查询。

对于涉及巨大的表的连接的统计查询,由于可能会造成大量的排序统计工作,使得查询的速度变慢,此时可以考虑使用PLSQL替代原来的查询。

2.DML语句的调整原则

DML语句包括Insert、Update、Delete和Merge。在使用DML语句的时候,我们也会遇到性能低下的情况,可以参考以下的内容来做出调整。

(1)Oracle存储参数的影响

Oracle的DML语句出现性能问题的一些情况:

①Insert操作缓慢并且占用过多的I/O资源。

这种情况发生在PCTFREE较高且行记录较大,频繁地寻找新的空闲数据块的时候。

在数据对象有(多个)索引的情况下,Insert 操作还需要对索引进行维护,这额外的增加了数据插入的成本,所以对于过度索引的表的维护是比较花费资源的。

②Update操作缓慢。

Update操作需要获得操作对象上的独占锁,如果其它的用户已经占有了该对象的非兼容的锁,那么Update操作就需要等待,通常这是非常短的时间,但是如果该用户在操作时被打断,则该用户持有这个锁的时间就可能变长,造成其它用户的等待,这是一个管理上的问题。

如果Update操作扩展了一个Varchar或Blob列导致发生了行迁移的时候,其更新也会变慢。

③Delete操作缓慢。

通常发生在记录被删除,而且Oracle必须将数据块重新连接到该表的Freelist的时候。

由于删除操作会产生大量的undo和redo信息,所以对系统的性能的影响较大。如果可能可以使用更改状态标志和在另外的表中插入新的记录来代替删除操作。

对于删除全表的操作可以用truncate table等命令来实现,在PL/SQL等不支持truncate命令的环境中可以使用动态SQL来实现truncate的功能。对于碎片比较多的系统,删除操作在某些时候涉及到数据块的回收。

另外,当有多个任务想要对一张数据表进行Insert或Update操作的时候,这张数据表的段头可能会产生冲突情况,这种冲突可以表现为出现等待事件:Buffer Busy Waits,此时对于数据库表的处理办法是提高Pctfree的值,降低一个数据块中数据的行数,对于冲突的索引可以使用倒排索引来避免同一数据块中的数据的索引存放在同一索引块中。

调整原则:

请参考上文相关部分以适当地对这几个参数进行设置,以在有效空间利用和高性能之间获得一个平衡点。

一般情况下对于并发更新比较频繁的对象要降低同一数据块中的数据行数以减少系统对于同一数据块的竞争,以空间换时间以提高性能。对于并发更新竞争不是那么频繁的对象要提高同一数据块中的数据行数,以提高系统空间的利用效率,同时提高缓存的利用率以达到提高系统整体效率的目的。

作为队列使用的表的竞争会比较剧烈,这类表中包含的总的数据行数不会太多,所以可以使用空间来换取效率。

(2)大数据类型的影响

使用大数据类型(RAW,LONG,BLOB,CLOB等)的时候,主要问题在于它们常常会超出普通的数据块大小,导致数据列分散到相邻的数据块。这样,每行记录被访问时,Oracle都会产生两次以上的I/O操作。

调整原则:

对于使用大数据类型的表,一般情况下其数据的行连接是不可避免的,我们能够做的就是尽量的降低这种事件发生的频率。

一般情况下,对于单独存储的LOB对象,我们可以指定其使用较大的db_block_size的表空间以控制其使用的数据块的个数,同时减少对LOB对象的访问次数,其中数据块的大小根据各个不同的LOB的平均大小有所不同。

小于4k的LOB对象可以和数据存储在一起,这样对数据访问速度的影响较大,这种情况下我们可以按照LOB对象的实际大小而选择不同表空间来存储数据。

(3)DML执行时约束的开销

约束会对DML操作的性能产生影响。

  • 完整性约束:时间会耗费在验证每一个数据值是否合法上。
  • 主键约束:主键约束是由唯一索引来强制实施的,而且这个索引在插入和更新操作上的开销使大容量的插入操作和更新操作运行变慢,因为每个值都必须从索引中查询一次。
  • 外键约束:强制实施了交互表之间的数据关系,必  须访问外部的数据表以确认当前值是否合法,才能进行插入。
  • 其它约束:对于其它检查的,数据也需要做相应的 检查。
  • 触发器:触发器对DML的执行效率也有较大的影响,特别当触发器的类型为for each row的时候。

调整原则:

  • 在执行大容量的插入或更新任务时,可以暂时禁用所有与所影响的数据表有关的约束、触发器,装载数据,最后才重新启用约束、触发器。
  • 在启用约束时,需要考虑非法的数据。

(4)DML执行时维护索引所需的开销

在记录被插入和修改时,表上所有的参与索引都必须实时地进行更新。这通常会产生由于大量排序而增加的系统开销,严重降低系统的执行性能。

调整原则:

  • 在大型的DML批操作中,在更改数据表之前,删 除全部索引;在批操作之后,重新建立起索引。
  • 如果索引因为不平衡而产生拆分等额外操作,那么可以通过重建索引操作,也会减少维护索引所需的时间。
  • 如果在数据被加载到数据库之前其数据已在外   部完成排序,则在创建索引时可以使用NOSORT 选项,需要注意的是NOSORT只能使用升序而不能使用降序,并且不能使用在倒排、分区、位图索引中。

在批量数据加载后的创建索引的过程中,可以指定用来排序的表空间以提高排序的效率,在指定排序表空间是需要注意该表空间的extent的大小,一般情况下该值越大越好。

 

3.查询语句使用原则

    1. 基本原则
  1. 在外部查询返回相对较少的记录时,宜使用关联子查询;而子查询中只有少量的记录的时候,则宜使用非关联子查询。
  2. 为避免数据库对做隐含的数据类型转换,在赋值、比较和运算的时候,宜使用相同的数据类型。
  3. 宜使用UNION ALL来代替UNION,从应用层面处理UNION ALL中的重复数据。
  4. 宜添加适当的索引,使表连接的时候使用索引来避免排序的发生。
  5. 宜在DISTINCT,GROUP BY,ORDER BY子句涉及到的列上创建索引。
  6. 宜在用户的临时表空间上使用大的EXTENT大小。
  7. 全表扫描的查询语句宜使用并行查询。
  8. 返回大数据量的查询语句宜使用并行查询。
  9. 其它一些数据操作中的查询子句宜使用并行查询。
  10. 对于较大数据量的查询,可使用提示(HINT)来强制数据库使用并行查询,在Oracle数据库中,并行查询的优先级为语句提示(HINT),表的定义,数据库初始化参数。
  11. 对于使用外键的表,应注意外键字段的索引,避免错误删除索引。
  12. 应避免大量的使用SELECT ……FOR UPDATE语句造成系统不必要的加锁。
  13. 应减少使用集合运算苻,使用时要充分考虑语句效率问题。
    1. 索引使用原则
  1. 在对数据进行大的统计分析的时候,为了避免索引数据的区分度不够,造成了使用索引而引起的效率的下降,可通过指定全表扫描等提示(HINT)来避免。
  2. 对于LIKE运算,如果通配符%出现在字符串的尾部或中间,LIKE运算符将可以使用索引进行字符串的匹配,如果通配符%出现在字符串的开始,则LIKE运算无法使用索引。为了提高系统的效率,宜在通配符的左端提供较多的数据信息以降低查询的数量。
  3. 不宜将NULL值判定作为WHERE子句的查询条件。
  4. 宜使用区分度高的字段作为复合索引的第一个字段。
    1. 连接方式使用原则
  1. 嵌套循环连接(Nested Loop Join)

在使用有索引的嵌套循环连接时,应保证驱动表有合适的索引,最好为主键或唯一键,同时宜在另外一张表相同的列上设置索引。示例见附录A2中的3.1。

  1. 散列连接(Hash Join)

在一个大表和一个小表进行连接的时候宜使用散列连接,散列连接的表较大时,宜使用较大的PGA设置,保证散列连接的效率。示例见附录A2中的3.2。

  1. 排序合并连接(Sort Merge Join)

在散列连接不能达到应用的要求或Oracle优化器认为排序合并连接效率更高的情况下,可使用排序合并连接。

  1. DML语句使用原则

(1)对于DELETE操作频繁的表,宜改用变更状态的方法来实现Delete操作,对于删除全表的操作,宜使用TRUNCATE功能来实现。

(2)在执行大容量的插入或更新任务时,宜暂时禁用所有与所影响的数据表有关的约束、触发器,提高数据效率,处理结束后重启启用约束、触发器。在禁用、启用的处理流程中,应考虑处理异常中断、由于处理超 时进入联机时段等情况下,禁用约束、触发器对联机 带来的影响,在批量相关操作文档中应提供应急处理流程;

(3)在启用约束时,应考虑非法的数据。

(4)在进行大型的DML批量操作时,宜在更改数据表之前删除全部索引。操作之后重新建立起索引。在批量数据加载后的创建索引的过程中,可以指定用来排序的表空间以提高排序的效率,在指定排序表空间是需要注意该表空间的EXTENT的大小,一般情况下该值越大越好。

(5)如果索引因为不平衡而产生拆分等额外操作,宜重建索引操作。

(6)如果在数据被加载到数据库之前其数据已在外部完成排序,则在创建索引宜使用NOSORT 选项,需要注意的是NOSORT只能使用升序而不能使用降序,并且不能使用在倒排、分区、位图索引中。

(7)在单条SQL语句对大规模数据进行DML操作(例如批量处理、变更脚本、数据移行、数据清理等场景)时,若处理记录超过1000条的DML操作,应遵循《开放平台批量规范》中大SQL的使用原则,对于联机交易系统,单条大SQL处理时间不应超过10分钟,对于经营分析系统,单条大SQL执行时间不应超过30分钟。

5.复杂查询的设计原则

(1)限制表连接操作所涉及的表的个数

与表连接的表数量不宜超过5个,超过5个表连接应使用提示(HINT)固定表访问路径、表连接顺序和表连接方式。

(2)限制嵌套查询的层数

过多的嵌套层数,会使查询语句的复杂度大幅增加而影响执行效率,应限制查询语句的嵌套层数不多于5层。

(3)灵活应用中间表或临时表

在涉及多表的查询和嵌套层数较多的复杂查询中,可使用中间表和临时表,简化语句的复杂度。示例见附录A2中的3.3。

(4)宜将查询条件中的OR关键字转换为UNION ALL,从应用层面处理UNION ALL中的重复数据。

(5)对于主查询中包含较多条件而子查询条件较少的表宜使用EXISTS,对于主查询中包含较少条件而子查询条件较多的表宜使用IN。

(6)查询的数据在表中所占的比例较大时,宜使用并行查询来提高查询的执行速度。对于UPDATE/INSERT/DELETE操作的查询部分也可使用并行查询。

当涉及巨大的表的连接的统计查询时,宜使用PL/SQL过程来替代原来的查询。

  1. HINT使用原则

(1)应尽量避免使用复杂查询,设计适当的数据结构,使用应用级并行,固化统计信息等方式来稳定语句的执行计划,仅在必要时才使用HINT;

(2)在能确保知道语句应采用的正确的执行计划,且其它手段无法稳定此执行计划时,可采用HINT方式固定执行计划。

(3)应对应用代码中使用HINT的SQL语句进行登记、管理;

(4)在HINT涉及的对象有变更时,要跟踪HINT是否还有效,在语句的相关业务和数据有变化时,需要评估HINT是否满足功能要求;每年开发人员应至少对HINT相关代码进行一次检查和评估,确保HINT的有效性和正确性,避免由HINT失效导致效率问题,影响年终决算等重要时段的业务处理;

  • SQL编写建议

(一)绑定变量

尽量使用绑定变量,减少数据库硬解析次数。

(二)选择合适连接方式

基于RBO的SQL,调整表的前后顺序。选择合适的驱动表。基于CBO的SQL,更新统计信息,或者使用Hint指定表连接方式。

(三)选择合适访问路径

选择最佳的访问路径。必要的时候更新表和索引的统计信息,在谓词引用的列上建立合适的索引,或者添加Hint指定访问路径。基于RBO的SQL,调整WHERE条件的先后顺序。

(四)避免select *

在非嵌套SELECT语句,INSERT和UPDATE等语句中以及SELECT嵌套语句的最里层,应使用明确的字段名,避免使用*

避免使用:select * from table_name,只取得需要的列。

原因如下:

①增加额外的解析。查询数据字典,将 * 翻译成table_name表的所有列名。

②将不需要的列由服务器传输到客户端,增加网路开销。

③影响SQL计划。如果需要的列全部在索引中可以找到,有可能采用全局索引扫描或者快速索引扫描。

在SELECT嵌套语句的外层,可使用“*”。

(五)避免在列上运算

低效的SQL:

Select empno,ename,job,mgr,hiredate,sal From emp

Where to_char(hiredate,'yyyy-mm-dd')<'1900-12-31' ;

高效的SQL:

Select empno,ename,job,mgr,hiredate,sal From emp

Where hiredate<to_date('1900-12-31' ,'yyyy-mm-dd');

第一种写法,需要针对每一行运行一次to_char函数。

第二种写法,to_date 函数结果被多行共用,提高效率。

(六) 用>=替代>

如果DEPTNO 上有一个索引

高效:

SELECT  * FROM EMP WHERE DEPTNO >=4;

低效:

SELECT  * FROM EMP WHERE DEPTNO >3;

两者的区别在于, 前者DBMS 将直接跳到第一个DEPT 等于4 的记录,而后者将首先定位到DEPTNO=3 的记录并且向前扫描到第一个DEPT 大于3 的记录。

(七) 减少访问数据库的次数

例如, 以下有三种方法可以检索出雇员号等于0342或0291的职员。

方法1 (最低效) :

SELECT EMP_NAME , SALARY , GRADE FROM EMP WHERE EMP_NO = 342;

SELECT EMP_NAME , SALARY , GRADE FROM EMP WHERE EMP_NO = 291;

方法2 (次低效) :

DECLARE

CURSOR C1 (E_NO NUMBER) IS

SELECT EMP_NAME,SALARY,GRADE

FROM EMP WHERE EMP_NO = E_NO;

BEGIN

OPEN C1(342);

FETCH C1 INTO …,..,.。;

OPEN C1(291);

FETCH C1 INTO …,..,.。;

CLOSE C1;

END;

方法3 (高效) :

SELECT  A.EMP_NAME , A.SALARY , A.GRADE, B.EMP_NAME , B.SALARY , B.GRADE

FROM  EMP A,EMP B WHERE  A.EMP_NO = 342 OR B.EMP_NO = 291;

(八) 使用DECODE 函数来减少处理时间

使用 DECODE 函数可以避免重复扫描相同记录或重复连接相同的表。区别于SQL的其它函数,DECODE函数还能识别和操作空值。

其具体的语法格式如下:

 DECODE(input_value,value,result[,value,result…][,default_result]);

SELECT COUNT(*),SUM(SAL)

FROM EMP

WHERE DEPT_NO = 0020

AND ENAME LIKE 'SMITH%';

SELECT COUNT(*),SUM(SAL)

FROM EMP

WHERE DEPT_NO = 0030

AND ENAME LIKE ' SMITH%';

SELECT COUNT(DECODE(DEPT_NO,0020,'X',NULL)) D0020_COUNT,

COUNT(DECODE(DEPT_NO,0030,'X',NULL)) D0030_COUNT,

SUM(DECODE(DEPT_NO,0020,SAL,NULL)) D0020_SAL,

SUM(DECODE(DEPT_NO,0030,SAL,NULL)) D0030_SAL

FROM EMP WHERE ENAME LIKE ' SMITH%';

(九) 处理重复记录

最高效的删除重复记录方法 ( 因为使用了ROWID)

DELETE FROM EMP E

WHERE E.ROWID > (SELECT MIN(X.ROWID)

FROM EMP X

WHERE X.EMP_NO = E.EMP_NO);

找到重复记录的语句

Select col_name from tab group by col_name having count(col_name)>1;

(十) 用TRUNCATE 替代DELETE

当删除表中的记录时,在通常情况下, 回滚段(rollback segments ) 用来存放可以被恢复的信息. 如果你没有COMMIT 事务,ORACLE 会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况)。而当运用TRUNCATE 时, 回滚段不再存放任何可被恢复的信息.当命令运行后,数据不能被恢复.因此很少的资源被调用,执行时间也会很短。

(十一) 用UNION-ALL 替换UNION ( 如果有可能的话)

当 SQL 语句需要UNION 两个查询结果集合时,这两个结果集合会以UNION-ALL 的方式被合并, 然后在输出最终结果前进行排序。

如果用 UNION ALL 替代UNION, 这样排序就不是必要了. 效率就会因此得到提高。

(十二) COMMIT的使用

事务结束后,及时commit,释放资源:          

①回滚段上用于恢复数据的信息。

②被程序语句获得的锁。

③redo log buffer 中的空间。

④ ORACLE 为管理上述3 种资源中的内部花费。

COMMIT 通常是一个非常快的操作,而不论事务大小如何。不论事务有多大,COMMIT的响应时间一般都很“平”(flat,可以理解为无高低变化)。这是因为COMMIT并没有太多的工作去做,不过它所做的确实至关重要。这一点很重要,之所以要了解并掌握这个事实,原因之一是:这样你就能心无芥蒂地让事务有足够的大小。所以不要人为地限制事务的大小,分别提交太多的行,而是一个逻辑工作单元完成后才提交。这样做主要是出于一种错误的信念,即认为可以节省稀有的系统资源,而实际上这只是增加了资源的使用。如果一行的COMMIT 需要X 个时间单位,1,000 次COMMIT 也同样需要X 个时间单位,倘若采用以下方式执行工作,即每行提交一次共执行1,000 次COMMIT,就会需要1000*X 个时间单位才能完成。如果只在必要时才提交(即逻辑工作单元结束时),不仅能提高性能,还能减少对共享资源的竞争(日志文件、各种内部闩等)。

(十三)避免不必要的锁

ORACLE通过UNDO来支持多版本,所以SELECT操作不会被堵塞。不要人为的为SELECT语句的表加锁来保证数据的一致性。

例如:Select * from emp for update

这样会在emp表上加排它锁,其他的DML操作会被堵塞。如果目的是为了查询数据,不应该为表加上排它锁,无论查询语句执行多久(前提UNDO允许),结果集都是游标打开时刻的数据。尽量采用ORACLE默认的锁,不要人为的加锁(特殊情况除外)。

(十四)用WHERE 子句替换HAVING 子句

避免使用 HAVING 子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作. 如果能通过WHERE 子句限制记录的数目,那就能减少这方面的开销。

(十五) 减少对表的查询

在含有子查询的SQL 语句中,要特别注意减少对表的查询。

(十六)使用表的别名(Alias)

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

(十七) EXISTS和IN

(not)in 是把外表和内表作hash 连接,如果内表查询中包含NULL,最终肯定无返回结果。(not )exists是对外表作loop循环,每次loop循环再对内表进行查询。如果内查询中包含NULL,最终有可能返回结果。

一直以来认为exists比in效率高的说法是不准确的。如果查询的两个表大小相当,那么用in和exists差别不大。如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in:

例如:表A(小表),表B(大表)

select * from A where cc in (select cc from B)

效率低,用到了A表上cc列的索引;

select * from A where exists(select cc from B where cc=A.cc)

效率高,用到了B表上cc列的索引。

 相反的:

select * from B where cc in (select cc from A)

效率高,用到了B表上cc列的索引;

select * from B where exists(select cc from A where cc=B.cc)

效率低,用到了A表上cc列的索引。

not in和not exists比较:

如果查询语句使用了not in 那么内外表都进行全表扫描,不会用到索引;而not exists 的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快。也就是说,in和exists需要具体情况具体分析,not in和not exists就不用分析了,尽量用not exists就好了。

(十八)避免笛卡尔运算

多表关联查询不能出现笛卡尔积,如果在报表中为集聚表(或称中间表)生成多个维度组成的复合主键需要使用迪卡尔积的,必须请数据组确认性能。

(十九)使用CTAS备份

在进行DML操作(INSERT,UPDATE,DELETE)之前,必须对数据进行备份,使用如下语句:

方法一:表数据全部备份。

CREATETABLE TAB_NAME_BAK AS SELECT * FROM TAB_NAME;

方法二:部分备份,对大表仅备份将要修改的数据。

CREATE TABLE TAB_NAME_BAK

AS SELECT * FROM TAB_NAME WHERE [选择出被操作数据的条件];

(二十)INSERT时需写全列名

代码中INSERT语句必须写出全部列名,以保证表增加字段后语句执行不受影响。

如:INSERT INTO TAB(COL1,COL2)VALUES(COL1_VAL,COL2_VAL)

再如:

INSERT INTO TAB(COL1,COL2)

SELECT COL1_VAL,COL2_VAL FROM TAB_BB

不能将COL1,COL2和COL1_VAL,COL2_VAL省略。

(二一)大数据量的DML

DML操作涉及到大数据量时,请分解为多次执行。

对于UPDATE和DELETE每次涉及数据量在1万条左右,并且每次执行完就提交。

对于INSERT INTO SELECT如果采用提示(/*+ append parallel */)可以处理百万级别的数据量。

(二二)java的变量绑定

使用“变量绑定”来处理一条SQL带不同常量多次执行的情况,动态绑定可以大大优化SQL的执行效率,还可以优化Oracle的内存使用。

在Java中,结合使用setXXX系列方法,可以为不同数据类型的绑定变量进行赋值,从而大大优化了SQL语句的性能。

(二三)perl的变量绑定

使用“变量绑定”来处理一条SQL带不同常量多次执行的情况,动态绑定可以大大优化SQL的执行效率,还可以优化Oracle的内存使用。

(二四)避免重复访问:使用group

避免重复访问(一):同源单组单查询。

如下语句要避免:

SELECT CLASS,sum(COL) FROM  TAB_TEST  WHERE CLASS=’A’ UNION ALL

SELECT CLASS,sum(COL) FROM  TAB_TEST  WHERE CLASS=’B’ UNION ALL

SELECT CLASS,sum(COL) FROM  TAB_TEST  WHERE CLASS=’C’

改写成:

SELECT CLASS,sum(COL) FROM TAB_TEST GROUP BY CLASS

(二五)避免重复访问:竖向显示变横向现实

避免重复访问(二):竖向显示变横向显示。

问题语句:

SELECTA.C1 AC1,A.C2AC2,A.C3AC3,

      B.C1BC1,B.C2BC2,B.C3BC3,

      C.C1CC1,C.C2CC2,C.C3CC3

FROM

 (SELECT'123' X,'SYNONYM' C1, sum(2)C2,count(1)C3 

    FROMTAB WHERE TABTYPE = 'SYNONYM')A,

 (SELECT'123' X,'TABLE'  C1, sum(2)C2,count(1)C3 

    FROMTAB WHERE TABTYPE = 'TABLE')B,

 (SELECT'123' X,'VIEW'  C1, sum(2)C2,count(1)C3 

    FROMTAB WHERE TABTYPE = 'VIEW')C ;  

正确使用形式如下:

SELECTMAX(DECODE(TABTYPE,'SYNONYM','SYNONYM',NULL)) AC1,

      MAX(DECODE(TABTYPE,'SYNONYM',sum(2),0))AC2,

      MAX(DECODE(TABTYPE,'SYNONYM',count(1),0))AC3,

      MAX(DECODE(TABTYPE,'TABLE','TABLE',NULL)) BC1,

      MAX(DECODE(TABTYPE,'TABLE',sum(2),0))BC2,

      MAX(DECODE(TABTYPE,'TABLE',count(1),0))BC3,

      MAX(DECODE(TABTYPE,'VIEW','VIEW',NULL)) CC1,

      MAX(DECODE(TABTYPE,'VIEW',sum(2),0))CC2,

      MAX(DECODE(TABTYPE,'VIEW',count(1),0))CC3      

FROMTAB

WHERETABTYPE IN('TABLE','SYNONYM','VIEW')

GROUPBY TABTYPE;

(二六)避免重复访问:用表更新表

避免重复访问(三):一个表同时更新另一个表的多个字段

问题SQL:使用TB_SOURCE表更新表TB_TARGET的多个字段

UPDATE TB_TARGET  A  SET  

A.COL1 = (select B.COL1 from TB_SOURCE B where B.id = A.id) ,

A.COL2 = (select B.COL2 from TB_SOURCE B where B.id = A.id) ,

A.COL3 = (select B.COL3 from TB_SOURCE B where B.id = A.id) ,

A.COL4 = (select B.COL4 from TB_SOURCE B where B.id = A.id)

WHERE A.id IN ( select B.id from TB_SOURCE B)

正确使用形式如下:

UPDATE TB_TARGET  

SET (COL1, A.COL2, A.COL3, A.COL4 )=(SELECT B.COL1, B.COL2, B.COL3, B.COL4

FROM TB_SOURCE B WHERE B.id = A.id)

WHERE EXISTS (select 1 from TB_SOURCE B where B.id = A.id)

(二七)数据库连接及时关闭

程序中必须显式关闭数据库连接,不仅正常执行完后需显示关闭,而且在异常处理块(例如java的exception段)也要显示关闭。

(二八)语句中宜使用大写

在整个SQL语句中,除了变量名以外,宜使用大写。

 

  • 2
    点赞
  • 0
    评论
  • 13
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论
请先登录 后发表评论~
8.用执行计划分析SQL性能      EXPLAIN PLAN是一个很好的分析SQL语句的工具,它可以在不执行SQL的情况下分析语句      通过分析,我们就可以知道ORACLE是怎样连接表,使用什么方式扫描表(索引扫描或全表扫描),以及使用到的索引名称      按照从里到外,从上到下的次序解读分析的结果      EXPLAIN PLAN的分析结果是用缩进的格式排列的,最内部的操作将最先被解读,如果两个操作处于同一层中,带有最小操作号的将首先被执行      目前许多第三方的工具如PLSQL Developer和TOAD等都提供了极其方便的EXPLAIN PLAN工具      PG需要将自己添加的查询SQL文记入log,然后在EXPLAIN PLAN中进行分析,尽量减少全表扫描      ORACLE SQL性能优化系列      1.选择最有效率的表名顺序(只在基于规则的优化器中有效)      ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,因此FROM子句中写在最后的表(基础表driving table)将被最先处理      在FROM子句中包含多个表的情况下,必须选择记录条数最少的表作为基础表      当ORACLE处理多个表时,会运用排序及合并的方式连接它们      首先,扫描第一个表(FROM子句中最后的那个表)并对记录进行排序;      然后扫描第二个表(FROM子句中最后第二个表);      最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并      例如:      表 TAB1 16,384 条记录      表 TAB2 5 条记录      选择TAB2作为基础表 (最好的方法)      select count(*) from tab1,tab2 执行时间0.96秒      选择TAB2作为基础表 (不佳的方法)      select count(*) from tab2,tab1 执行时间26.09秒      如果有3个以上的表连接查询,那就需要选择交叉表(intersection table)作为基础表,交叉表是指那个被其他表所引用的表      例如:   EMP表描述了LOCATION表和CATEGORY表的交集   SELECT *   FROM LOCATION L,   CATEGORY C,   EMP E   WHERE E.EMP_NO BETWEEN 1000 AND 2000   AND E.CAT_NO = C.CAT_NO   AND E.LOCN = L.LOCN      将比下列SQL更有效率   SELECT *   FROM EMP E ,   LOCATION L ,   CATEGORY C   WHERE E.CAT_NO = C.CAT_NO   AND E.LOCN = L.LOCN   AND E.EMP_NO BETWEEN 1000 AND 2000      2.WHERE子句中的连接顺序      ORACLE采用自下而上的顺序解析WHERE子句      根据这个原理,表之间的连接必须写在其他WHERE条件之前,那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾      例如:   (低效,执行时间156.3秒)   SELECT *   FROM EMP E   WHERE SAL > 50000   AND JOB = 'MANAGER'   AND 25 < (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO);      (高效,执行时间10.6秒)   SELECT *   FROM EMP E   WHERE 25 50000   AND JOB = 'MANAGER';      3.SELECT子句中避免使用'*'      当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用'*'是一个方便的方法,不幸的是,这是一个非常低效的方法      实际上,ORACLE在解析的过程中,会将'*'依次转换成所有的列名      这个工作是通过查询数据字典完成的,这意味着将耗费更多的时间      4.减少访问数据库的次数
©️2021 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页

打赏作者

天行健自强不息的码农

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值