1 创建游标
游标(或称上下文区域)是内存中的一块区域,Oracle利用它来保存SQL语句和相关的信息。这包括SQL与家具的已经解析的与未经解析的内容、执行计划以及一个指向当前记录的指针。
当SQL语句执行完成时,与此游标相关的内存可以出于其他目的而被释放,也可被保存以供再次执行时使用。
在大部分程序语言中,一个游标对应于一个SQL对象或者一个语句句柄。在JAVA的JDBC中,一个游标等价于一个Statement或者Prepared Statement对象。
2 检查缓存的SQL语句
为了避免不必要的解析,Oracle维护了一个缓存来保存最近执行的SQL语句及对应的执行计划。这个缓存保存在共享内存的一个区域中:共享池(shared pool)的SQL Area中。每提交一个SQL语句的执行请求,Oracle都会在这个缓存中检索匹配的语句。如果找到匹配的语句,Oracle就会使用保存在这个缓存中的执行计划,从而避免解析过程中涉及的大部分开销。
3 解析SQL语句
解析是为了执行SQL语句所做的准备工作,解析过程如下:
- 检查SQL语句中的语法是否有效,也就是这条SQL语句是否遵循SQL语言的规则,以及所有的关键字与操作符是否都有效并被正确使用。
- 检查SQL语句在语义上是否有效。这意味着这条SQL语句引用的所有数据库对象(即表和字段)都是有效的。
- 检查用户是否对所有相关对象执行给定操作的安全权限。
- 确定SQL语句的执行计划。执行计划描述Oracle访问的修改数据时涉及的一系列步骤。这是由Oracle优化器(optimizer)来完成的。 解析操作有时候是一个昂贵的操作,虽然它的开销常常会被高IO请求带来的更大开销所掩盖。不过消除不必要的解析总是值得的。
4 关联绑定变量
SQL语句中可以包含变量,他们在每次执行时可以变化。通常这些变量都是SQL语句的参数,该SQL语句定义将要被处理的记录或者将要被插入或更新的新值。我们以字面量(literal)或绑定变量(bind variable)的方式来指定这些变量。
例如,使用字面量时,我们可以使用如下的SQL语句来提取雇员206的详细信息select first_name, last_name from hr.employees where employee_id = 206`下一次我们想要选择另外一个雇员时,可以将字面量“206”改成新值并重新执行。这样做当然时可行的,不过请注意,SQL语句必须完全一致才能在共享池中找到匹配项。因为每次执行时EMPLOYEE_ID可能都不一样,所以几乎总是无法在共享池中找到匹配的语句,从而导致这个语句每次都需要重新解析。
另一个可选的方法就是利用绑定变量来指定这些变量部分。绑定变量是对这些变量的固定引用,他们可以被定义在编程语言或者查询工具的任何地方。在大部分编程语言或者工具中,绑定变量都需要添加一个前缀(冒号“:”),从而使其可以被识别出来。例如,在下面这个基于SQLPLUS的例子中,orgid的值就保存在一个绑定变量中(可以利用SQLPLUS的VARIABLE命令来定义一个绑定变量)。SQL> variable bind_orgid number; SQL> begin 2 :bind_orgid := 227; 3 end; 4 / PL/SQL procedure successfully completed bind_orgid --------- 227 SQL> select * from sysorg where orgid = :bind_orgid;关于使用绑定变量,起码存在如下两个又说服力的理由。第一,如果绑定变量的值发生变化,在重新执行这条SQL语句的时候,不需要创建一个新的游标(上下文)或者重新解析这条SQL语句。第二,如果其他会话也执行同一条SQL语句,它可以在共享池中找到一条匹配的SQL,因为不同执行进程中绑定变量的名称并不会发生改变。
相反如果使用字面量而不是绑定变量,将遇到如下这些问题。
- 每次改变字面量的值,都必须请求对此SQL语句进行重新解析。
- 当请求解析时,在共享池中找到一条匹配的SQL语句的可能想将非常小。
- SQL缓存将被“仅用一次”的SQL语句填满,可能需要更大的容积,而实际上并不需要这么多。
- 当一个Oracle会话想要在共享池中新增一条SQL语句时,它必须获取一个Oracle的内部锁(闩锁或互斥)。在极端情况下,这会导致闩锁或互斥的争用,进而导致性能瓶颈(最好的结果)甚至严重的性能问题(最坏的结果)。
5 执行SQL语句
当SQL语句解析完毕且所有变量都已绑定时,Oracle就做好执行这条语句的准备了。如果执行的是DML语句(insert、update、merge、delete),那么执行这条语句将导致SQL立即变动,尽管在commit语句执行前这个变更不会被持久化。
如果执行的是select语句,那么执行调用只是为提取(fetch)操作准备好游标。当执行的是某些特定的查询时(例如,当记录必须进行排序或者锁定时),打开游标就会使得Oracle提取需要返回的所有记录。在其他情况下,打开游标只是将记录指针(record pointer)定位到第一条记录。
6 提取记录
不管open调用是否必须访问复核条件的所有记录,将数据返回给客户端环境都是由fetch调用完成的。fetch调用从数据库提取一条或多条记录,并将结果保存到程序可操作的主机变量中。
从程序角度看,每次fetch调用只会返回一条或多条记录。在内部,Oracle可能会从磁盘或缓冲区高速缓存(共享内存中用来缓存数据库的区域)中提取数据。在Oracle 11g中,Oracle可能从客户端结果缓存(client-side result cache)或服务器端缓存(server-side result cache)中返回整个结果集。
7 使用批量提取
每次提取请求都可以返回一条以上的记录。当一次提取操作以这种方式返回一批记录时,就被称为批量提取(array fetch)。批量提取比一次仅提取一条记录要高效得多。
通常,客户端工具都会自动执行批量提取。其他工具可能需要明确指定进行批量提取。
8 处理结果集
SQL查询的输出也被称为结果集(result set)。结果集包含行与列,可以认为它是一个包含查询结果的临时表。中间操作也会产生结果集。例如,在一个由表A、B、C组冲的联结(join)操作中,表A先与表B进行联结,产生一个中间结果集。这个结果集接着与表C进行连接并生成返回给程序的最终结果集。
9 关闭游标
关闭游标会释放与此游标相关的所有内存。如果需要再次执行这条SQL语句,就必须创建一个新的游标。不过,Oracle有时会透明地在内存中保存一份这个游标的副本,以供再次执行这条SQL时使用。我们可以通过配置参数SESSION_CACHE_CURSORS来控制这一点。
10 优化Oracle API调用
在编写与Oracle服务器进行交互的代码(与SQL语句本身无关)时,主要有如下三种方式来取得最佳性能。
- 确保SQL语句(或游标)在应用中都可重用。这可以降低对Oracle的解析调用,从而降低CPU的开销以及数据库的争用。
- 使用绑定变量,以确保SQL语句可以在会话内部以及会话之间实现共享。这样可以提高在共享池中找到相匹配的SQL语句的机会,从而降低解析操作的开销。
- 利用批量提取来提高查询和插入操作的性能。使用批量提取功能可以降低对Oracle的调用次数,也可以降低网络流量,特别是客户端服务器环境的网络流量。