我最近负责优化一些现有的Oracle存储过程。每个存储过程查询数据库并生成XML文件输出。特别是大约需要20分钟才能完成执行。看一下它有几个嵌套循环和不必要的查询。例如,而不是做一个
SELECT * from Employee e, Department d WHERE e.DEPT_ID = d.ID
--write data from query to XML
更像是
FOR emp_rec in ( SELECT * from employee )
LOOP
SELECT * from Department WHERE id = emp_rec.DEPT_ID;
--write data from query to XML
END LOOP;
将所有这些案例更改为更像第一个选项,大大加快了程序的速度。我的问题是为什么?为什么在选择查询中加入比手动组合表更快?什么是基本流程?
答案 0 :(得分:5)
让我们来看看原始版本是如何处理的。
FOR emp_rec in ( SELECT * from employee )
LOOP
SELECT * from Department WHERE id = emp_rec.DEPT_ID;
--write data from query to XML
END LOOP;
循环查询可能会在employee
上执行全表扫描。然后,对于返回的每一行,它将执行内部查询。假设id
是department
的主键,每次执行查询都可能使用主键索引进行唯一查找。
听起来不错,对吧?唯一索引查找通常是获取单行的最快方法(ROWID显式查找除外)。但想想在循环的多次迭代中这是做什么的。据推测,每个员工都属于一个部门;每个部门都有员工;大多数或所有部门都有多名员工。
因此,在循环的多次迭代中,您将多次重复内部查询的完全相同的工作。是的,数据块可能被缓存,因此您没有重复物理读取,但访问缓存中的数据确实会产生一些CPU开销,当反复访问相同的块时,这可能会变得非常重要。
此外,最终您可能希望department
中的每一行至少一次,并且可能不止一次。由于表中的每个块都需要被读取,因此您不是通过执行索引查找来保存工作 - 您正在添加工作。
当您将循环重写为单个查询时,优化程序可以将此考虑在内。它有一个选项是执行由employee
驱动的嵌套循环连接,这与PL / SQL中的显式循环基本相同(减去Mark指出的上下文切换)。但是,考虑到两个表之间的关系,以及缺少任何过滤谓词,优化器将能够分辨出全部扫描两个表并执行合并或散列连接更有效。这实际上导致更少的物理IO(假设在每次执行开始时干净的缓存)和更少的逻辑IO。
答案 1 :(得分:4)
“基础流程”需要一个很大的答案。我将离开Tom Kyte来回答这个问题;)