目前绝大多数的MIS系统,都是基于对数据库的交互式操作,开发人员大部分的工作都是直接或者间接与数据库打交道。作为开发人员,正确的使用数据库技术,不但可以提高数据库相关开发的效率,而且可以有效的减少投产运维阶段的工作量。本篇从笔者的实际工作体会入手,简单介绍几个Oracle相关开发原则。
1、“Never Treate Database Like A blackbox”
“绝不要将数据库视为一个黑盒”是Oracle大师Thomas Kyte给所��数据库开发人员的一句忠告。作为一个开发人员,无论使用何种类型的数据库,都要遵守这个基本规则。
现实中,将数据库作为Blackbox的开发人员是很多的。很多开发人员将数据库就是作为一个数据留存的载体,通过标准化的SQL进行操作,不去对具体使用的数据库进行了解。
这个问题的原因是很多的。其中一个重要原因就是在开发人员的观念中,SQL语句都是通用标准的。只要输入相同描述SQL,数据库都能返回相同的结果。开发人员似乎不用去关注数据库本身。
这种想法其实是错误的。目前我们使用最多的关系型数据库系统,都是建立在关系数据库模型的基础之上。同时,行业中也有专门的SQL系列标准。流行的开源或者商用数据库都宣称支持标准SQL和关系模型。但是实际上,各个数据库产品在实现标准的过程中是有所取舍、有所个性化的。
比如在Oracle中,一个重要的特性就是“查询select不会被修改阻塞”,也就是任何时候都可以对数据表进行select,不在乎查询数据是否正在被修改。这个特性大大提升了Oracle数据库的并发能力。但是并不是每种RDBMS都是如此,如SQL Server或者SQLite等DBMS,就存在select被其他DML操作阻塞的情况。
另一个例子是关于锁定范围。如果要对一条记录进行修改,Oracle数据库只会对该数据行进行锁定(行锁机制)。而其他一些数据库进行锁定的范围就会有差异。比如,SQL Server就是基于对页page的锁定,而另一些DBMS甚至会对表级别进行锁定。
对这些数据库机制的了解,对我们进行开发的意义重大,可以帮助我们不犯根本性质的错误。比如,了解SQL Server中select会被DML操作阻塞,就要意识到SQL Server环境下事务Transaction是要及时的提交。
另一部分的机制了解就是SQL。SQL是一种描述性语言。同一个结果集合,使用不同的SQL描述,数据库都会生成不同的执行计划。不同的执行计划方式,意味着不同的效率。所以,书写一手“漂亮”的SQL是不容易的。需要开发人员了解优化器、SQL转换机制和执行计划。
所以,作为一个数据库相关的开发人员不是一件容易的事情。会写几句SQL,用用预定义函数绝不是数据库开发的全部。了解你的项目所使用的数据库,知道自己在做什么,是一个数据库开发人员所具备的基本素质。
2、Be Responsible To Your SQL
对于一个初学者,SQL是一种描述性语言。你需要什么样子的数据,不管什么方法,只要能描述出来,理论上DBMS都可以返回正确的结果。但是,执行效率是有差异的。相同的结果集合,好SQL可能不到一秒钟就可以返回结果,坏SQL可能几个小时不能有结果。
我们经常在开发团队遇到的场景是这样的:开发人员书写了一个SQL,测试入一个很小数据集合,可以返回正确的结果。就直接提交测试投产了。投产之后,随着数据量的积累,原有SQL执行性能下降严重,最后很可能都执行不出结果。此时,开发团队已经解散,修改源代码的可能性很少,于是变成了DBA的噩梦。
这样的场景,本质问题就出现在开发阶段开发人员对SQL的责任缺失。开发人员应该对自己书写的SQL负责,在保证结果正确的前提下,不断优化SQL的书写。这样做的目的就是让SQL在生产环境下也可以正确的执行。
做到SQL负责,听起来是一个很抽象的概念。实际中,开发人员只需要从两个方面入手:
首先,看看自己书写SQL的执行计划。查看执行计划,评估执行计划不是DBA的专利。每个开发人员应该具有看懂SQL执行计划,辨别常见Join的方式操作,识别好坏的能力。能看清SQL的缺点,才有改造的动力。
第二就是时刻注意自己的SQL在做什么?听起来这条规则很有问题,SQL几种操作,我们都能够书写出来,难道还不知道自己的SQL在做什么吗?这里笔者要强调的是数据操作范围。修改一条记录的SQL和修改一千万条记录的SQL书写起来,形式可以是一样的。但是,这样的SQL执行时候的效果是天差地别。一次性修改一千万记录的SQL也许一瞬间就让数据库处在崩溃的边缘。所以,一条SQL语句写出来,开发人员一定要明白这个SQL处理的范畴是多大?一百条还是一百万条?如果可能出现百万条的可能,就一定要预先处理,设置一次操作的范围上限。避免由于一次性的操作带来的风险。
3、Index or Not Index
这里说说Index索引这个老话题。在Oracle环境下,最常见的索引是B+平衡树索引。普通B树索引具有几个特征:
ü始终维持平衡状态。从根节点到达所有叶子节点的距离相同;
ü树节点包括三类���一个根节点、若干分支节点和若干的叶子节点;
ü叶子节点之间,通过双向链表结构加以连接。可以方便的在叶子节点层进行导航;
ü叶子节点上,保存着索引列键值和对应的行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操作的性能低下。所以,索引是一种有成本的优化手段,要统筹规划。