概述

目前绝大多数系统,都是基于对数据库的交互式操作,开发人员大部分的工作都是直接或者间接与数据库打交道。作为开发人员,正确的使用数据库技术,不但可以提高数据库相关开发的效率,而且可以有效的减少投产运维阶段的工作量。今天主要从实际工作体会入手,简单介绍几个Oracle相关开发原则。


01Never Treate Database Like A blackbox

“绝不要将数据库视为一个黑盒”是Oracle大师Thomas Kyte给所有数据库开发人员的一句忠告。作为一个开发人员,无论使用何种类型的数据库,都要遵守这个基本规则。

现实中,将数据库作为Blackbox的开发人员是很多的。很多开发人员将数据库就是作为一个数据留存的载体,通过标准化的SQL进行操作,不去对具体使用的数据库进行了解。

这个问题的原因是很多的。其中一个重要原因就是在很多人的观念中,SQL语句都是通用标准的。只要输入相同描述SQL,数据库都能返回相同的结果。很多运维和开发人员似乎不用去关注数据库本身。

这种想法其实是错误的。目前我们使用最多的关系型数据库系统,都是建立在关系数据库模型的基础之上。同时,行业中也有专门的SQL系列标准。流行的开源或者商用数据库都宣称支持标准SQL和关系模型。但是实际上,各个数据库产品在实现标准的过程中是有所取舍、有所个性化的。

始终维持平衡状态。从根节点到达所有叶子节点的距离相同;

  • 树节点包括三类:一个根节点、若干分支节点和若干的叶子节点;
  • 叶子节点之间,通过双向链表结构加以连接。可以方便的在叶子节点层进行导航;
  • 叶子节点上,保存着索引列键值和对应的行rowid;
  • 索引是一个单独的段结构segment,一个enable的索引是和数据表索引列实时对应;
  • 在SQL的执行计划中,经常可以看到两种路径方式:Full Table Scan和Index Range Scan。这是数据表访问的两个代表性的方式。

    Full Table Scan(全表扫描):此种方式是对数据表数据的全部检索。首先,Oracle会从对象的数据字典中,获取到数据段头块的信息(文件号、块号)。找到头块之后,从头块信息中,获取到该数据表所有相关extents的信息和位置。之后依据extents的分配依次检索数据块。直到检索到HWM(高水位线)下。

    FTS方式有个特点:堆表结构下,会检索到所有分配给的数据块(无论是否有数据内容)。所以,FTS操作的效率是随着数据表的膨胀而变化的;

    Index Range Scan(索引范围扫描):对数据表的检索,并不直接入手数据表。而是从索引入手,通过读少量的索引块,获取定位到符合条件的叶子节点rowid列表。之后,直接借助rowid列表,就可以定位到符合条件的数据行。这种方式的特点是查询性能不随着数据表的胀大而发生变化。

    通常情况下,索引路径是我们追求的优化方向。在CBO时代,索引路径因其少量的IO块读取成本通常小于FTS方式的。但是,并不是意味着所有SQL都会优化为索引路径。索引是有成本的,添加索引是会给select带来很多好处。但是,Oracle要维护数据索引列与索引树的同步结构。这也就意味着索引会带来DML操作的性能低下。所以,索引是一种有成本的优化手段,要统筹规划。


    04The Trap In Your Where Condition and Order/Group clause

    日常数据库开发,主要集中在DML操作类型,以select/update/insert/delete为核心。在这些类型操作,我们最需要关注的并不是select的结果集合列表,也不是insert/update的具体数值,而是定位操作对象的where条件和进行大规模计算操作的group/order。

    Where条件的作用是让Oracle可以定位到我们需要进行检索处理的记录。Where条件的书写起到两个层面的作用,其一是描述了结果集合属性,另一个是间接影响到Oracle定位数据的方式,也就是执行计划。

    SQL是一种描述性语言,我们只需要描述需要的数据属性就可以了。但是也就是这个特性,往往会让我们写出很糟糕的SQL。同样的结果集合,不同的描述方式(SQL where条件),执行效果和执行计划可能会千差万别。

    写好where条件的规则技巧有很多,比如in用exists替换、is null不选择等等,每种技巧都是基于特定的应用场景和内部背景。这里列举一个对条件列不要轻易处理的例子。

    如果我们在where条件中书写一些表达式,通常Oracle在SQL预处理前就会将表达式进行处理。但是,对于携带数据列的条件表达式,这种改写变化是不会越过等号的。下面进行试验。

    SQL> create table t as select * from dba_objects; SQL> create index idx_t_id on t(object_id); SQL> exec dbms_stats.gather_table_stats('SCOTT','T',cascade => true);

    数据库的设计方法和设计步骤(值得一看的数据库设计开发原则总结)(1)

    选择在数据表t的object_id列添加索引。两个SQL含义等价,但是执行计划完全不同。

    SQL> explain plan for select * from t where object_id=999 1; SQL> select * from table(dbms_xplan.display); SQL> explain plan for select * from t where object_id-1=999; SQL> select * from table(dbms_xplan.display);

    数据库的设计方法和设计步骤(值得一看的数据库设计开发原则总结)(2)

    数据库的设计方法和设计步骤(值得一看的数据库设计开发原则总结)(3)

    相同的含义SQL,只是进行简单的算术移向操作,就有如此大的执行计划成本差异。在第一个SQL中,Oracle自动将运算完成后走索引路径。而第二个SQL因为减一操作在object_id同侧,视为将object_id处理的表达式。表达式是不会选择普通索引路径的,只有创建特定的函数索引。

    此类问题还可以引申到对列进行函数处理或者表达式处理,这样的SQL语句非常容易出现在where条件中。

    Group by/order也是同样的问题。在Oracle中,分组操作和排序操作是相当“昂贵”的。当进行group by/order的时候,对应的数据集合需要存放在PGA的专门区域中进行。这种操作消耗PGA甚至临时Temp表空间空间,同时也会消耗一定的CPU资源。所以,如果没有明确的需求,我们尽量少用这两种类型操作。


    05Hard Parse VS Soft Parse

    Oracle开发人员平时听到最多的数据库SQL技巧恐怕就是绑定变量SQL的书写了。使用绑定变量的原因简单的说,就是为了增加SQL共享游标的共享概率,减少硬解析hard parse。

    对Oracle来说,内存的缓存cache技术是贯穿在整个体系框架中的。当一个新的SQL语句输入时,要经历语法、语义和权限等一系列检查,之后要进行parse过程。如果在SGA的Library Cache中没有能找到,就会自己生成该SQL的执行计划和共享游标,这个过程要消耗SGA空间和CPU成本,同时还会带来一定数量的library Cache Latch和Library Cache Pin。进行执行计划生成之后,该SQL以shared cursor的形式缓存在library cache中,等待再次被使用。这个过程被称为hard parse,硬解析。

    当library cache中存在该SQL的执行计划时,另一次SQL调用输入。如果新SQL与原来的SQL字面值和其他一些参数相同,就存在游标共享的可能。这样,新SQL不需要进行SGA空间分配和执行计划生成,会使用原有的执行计划。这个过程我们称之为Soft Parse。

    无论是Oracle自身的SQL和PL/SQL,还是Java/.NET的接口语言,都存在使用绑定变量的接口API。使用绑定变量可以增加SQL出现soft parse的几率,增加数据库并行性。

    最后谈一下绑定变量的适用环境。并不是什么样的场景使用绑定变量都是没有问题的,在OLTP这类事务并发和事务密集型的系统中,使用绑定变量会提高系统整体并发能力。但是在OLAP和DSS类的系统中,一个SQL执行次数很少,但是执行时间很长,这样场景下使用绑定变量的意义就不大。

    此外,使用绑定变量存在出现bind peeking的可能性,这方面的性能抖动问题也不能忽视。


    06Prioritize your Use Cases for Tunning

    系统从业务需求分析、设计、开发到投产运维,性能分析优化是贯穿整个生命周期的。性能分析优化手段越是往前规划,我们可以使用的优化选择手段就越多,性价比就越好。传统意义上的SQL调优,都是谈在投产运维阶段进行的DBA运维调优。在运维阶段进行的手段很有限,而且收效往往很低。

    在开发阶段,我们一般进行优化的方式主要是SQL改写和索引选择。大多数性能优化手段都是需要付出额外的成本。比如索引,建立索引的确可以获取很好的select效率,但是另一方面要付出update/insert/delete成本,而且索引本身也是要有空间占用和管理成本。所以,我们追求的优化,实际上就是最优性价比的优化。

    那么,面对诸多的需求场景,我们如何选择呢?所谓关键用例确定架构,关键用例同样决定优化策略方向。我们面对的需求不可能是相同优先级别的,对用户而言,必然存在轻重缓急。系统的目标是实现用户的愿景,实现用户目标的最大化。但是,用户的目标实现是不可能完全实现。在“质量-工期-成本”三角形的控制下,必然有需求会被裁减。我们追求的目标就是将优先的优化资源分配给尽可能高优先级别的用例需求中。

    举一个例子,两个数据列都有加索引优化的需求,但是资源限制下只能加一个索引。一个用例是在界面上显示系统参数,这个界面对应SQL如果是全表扫描,要多消耗5秒钟。但是该用例很少有人用,每年只会打开一次。另一个用例是每日的Daily Job,每天都会运行成百上千次的SQL。经过详细分析,用例的优先级别立刻可以看出来了,优化方案自然也就出来了。


    总结

    今天主要集中介绍了在系统开发阶段我们需要关注的技术和原则,大部分的技巧是思路和指导原则,希望对大家有些帮助,这些都是DBA、开发和运维人员在系统设计时需要去考虑的。后面会分享更多devops和DBA方面的内容,感兴趣的朋友可以关注一下~

    数据库的设计方法和设计步骤(值得一看的数据库设计开发原则总结)(4)

    ,