Oracle 中临时数据的性能注意事项

Posted

技术标签:

【中文标题】Oracle 中临时数据的性能注意事项【英文标题】:Performance considerations for temporary data in Oracle 【发布时间】:2014-03-21 12:28:27 【问题描述】:

我正在评估各种选项,以针对 Oracle 中的单个临时数据集运行一组高性能查询。在 T-SQL 中,我可能会使用内存中的临时表,但 Oracle 没有与此功能完全相同的功能。

我目前看到以下选项:

1。全局临时表

CREATE GLOBAL TEMPORARY TABLE test_temp_t (
  n NUMBER(10), 
  s VARCHAR2(10)
) ON COMMIT DELETE ROWS; -- Other configurations are possible, too

DECLARE
  t test_t;
  n NUMBER(10);
BEGIN

  -- Replace this with the actual temporary data set generation
  INSERT INTO test_temp_t
  SELECT MOD(level, 10), '' || MOD(level, 12)
  FROM dual
  CONNECT BY level < 1000000;

  -- Replace this example query with more interesting statistics
  SELECT COUNT(DISTINCT t.n)
  INTO n 
  FROM test_temp_t t;

  DBMS_OUTPUT.PUT_LINE(n);
END;

计划:

----------------------------------------------------
| Id  | Operation            | A-Rows |   A-Time   |
----------------------------------------------------
|   0 | SELECT STATEMENT     |      1 |00:00:00.27 |
|   1 |  SORT AGGREGATE      |      1 |00:00:00.27 |
|   2 |   VIEW               |     10 |00:00:00.27 |
|   3 |    HASH GROUP BY     |     10 |00:00:00.27 |
|   4 |     TABLE ACCESS FULL|    999K|00:00:00.11 |
----------------------------------------------------

2。 PL/SQL 表类型变量的取消嵌套

CREATE TYPE test_o AS OBJECT (n NUMBER(10), s VARCHAR2(10));
CREATE TYPE test_t AS TABLE OF test_o;

DECLARE
  t test_t;
  n NUMBER(10);
BEGIN

  -- Replace this with the actual temporary data set generation
  SELECT test_o(MOD(level, 10), '' || MOD(level, 12))
  BULK COLLECT INTO t
  FROM dual
  CONNECT BY level < 1000000;

  -- Replace this example query with more interesting statistics
  SELECT COUNT(DISTINCT n)
  INTO n 
  FROM TABLE(t) t;

  DBMS_OUTPUT.PUT_LINE(n);
END;

计划:

------------------------------------------------------------------
| Id  | Operation                          | A-Rows |   A-Time   |
------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |      1 |00:00:00.68 |
|   1 |  SORT GROUP BY                     |      1 |00:00:00.68 |
|   2 |   COLLECTION ITERATOR PICKLER FETCH|    999K|00:00:00.22 |
------------------------------------------------------------------

3。物化视图

我将它们排除在这个用例之外,因为有问题的临时数据集相当复杂,而且对更新物化视图的影响太大。

真实数据注意事项

以上是我正在尝试做的示例。真实数据集涉及:

临时数据是从大约 15 个连接的表中非规范化的。 它的生产速度约为 2-20x / 秒。 每个临时数据集的实际行数约为 10-200(没有上例中那么大)。 系统的每个用户都有自己的临时数据集(总共 100 万用户,10k 并发用户)。 建立数据集后,应针对它运行大约 10-50 个分析查询。 这些分析必须在线运行,即不能推迟到批处理作业。

问题

根据我的直觉,临时表查询“应该”比较慢,因为它(可能)涉及 I/O 和磁盘访问,而 PL/SQL 集合查询仅仅是内存中的解决方案.但在我的简单基准测试中,情况并非如此,因为临时表查询比 PL/SQL 集合查询高出 3 倍。为什么会这样?是否发生了一些 PL/SQL SQL 上下文切换?

对于定义明确的临时数据集,我是否还有其他选项可以快速(但广泛)“内存中”数据分析?是否有任何重要的公开基准比较各种选项?

【问题讨论】:

对我来说,更大的问题是你想用这个临时数据做什么?填充临时表或集合的速度与 imo 无关。例如,根据您要完成的任务,在 mat 视图上进行 COMPLETE 刷新可能是最佳选择。 @tbone:我已经更新了这个问题。我不喜欢使用物化视图的原因是: 1. 数据源是从许多经常更新的表中连接起来的。 2. 每个用户只能看到“他们的” 数据。我觉得这对于物化视图可能有点过于复杂,而不是全局临时表(它本身是基于会话的) 执行计划中的时间只是 Oracle 优化器的估计。真正的执行时间是多少? Lukas,提醒一句:如果您使用 GTT 和连接池,请小心,您似乎认为 GTT 是一种优势,因为“每个用户应该只看到他们的数据”,而 GTT 是“本身基于会话”。根据您的应用程序和池的设置方式,您可能有不同的用户共享同一个会话。只是需要考虑的事情 @tbone:好点!这实际上可能是以前生产性“基准”的问题。我不认为我们当时使用过ON COMMIT DELETE ROWS,不过...... 【参考方案1】:

由于缓存和异步 I/O,临时表实际上与内存表相同,并且临时表解决方案不需要任何在 SQL 和 PL/SQL 之间转换的开销。

确认结果

将两个版本与 RunStats 进行比较,临时表版本看起来要差得多。 Run1 中临时表版本的所有垃圾,Run2 中的 PL/SQL 版本只有一点额外内存。起初,PL/SQL 似乎应该是明显的赢家。

Type  Name                              Run1 (temp) Run2 (PLSQL)         Diff
----- -------------------------------- ------------ ------------ ------------
...
STAT  physical read bytes                    81,920            0      -81,920
STAT  physical read total bytes              81,920            0      -81,920
LATCH cache buffers chains                  104,663          462     -104,201
STAT  session uga memory                    445,488      681,016      235,528
STAT  KTFB alloc space (block)            2,097,152            0   -2,097,152
STAT  undo change vector size             2,350,188            0   -2,350,188
STAT  redo size                           2,804,516            0   -2,804,516
STAT  temp space allocated (bytes)       12,582,912            0  -12,582,912
STAT  table scan rows gotten             15,499,845            0  -15,499,845
STAT  session pga memory                    196,608   19,857,408   19,660,800
STAT  logical read bytes from cache     299,958,272            0 -299,958,272

但归根结底,只有挂钟时间很重要。使用临时表时,加载和查询步骤都运行得更快。

可以通过将BULK COLLECT 替换为cast(collect(test_o(MOD(a, 10), '' || MOD(a, 12))) as test_t) INTO t 来改进PL/SQL 版本。但它仍然比临时表版本慢很多。

优化读取

从小型临时表中读取仅使用内存中的缓冲区缓存。只运行查询部分多次,观察consistent gets from cache(内存)如何增加,而physical reads cache(磁盘)保持不变。

select name, value
from v$sysstat
where name in ('db block gets from cache', 'consistent gets from cache', 
'physical reads cache');

优化写入

理想情况下不会有物理 I/O,尤其是因为临时表是 ON COMMIT DELETE ROWS。而且听起来Oracle的下一个版本可能会引入这样的机制。但在这种情况下,这并不重要,磁盘 I/O 似乎并没有减慢速度。

多次运行加载步骤,然后运行select * from v$active_session_history order by sample_time desc;。大多数 I/O 是BACKGROUND,这意味着没有任何东西在等待它。我假设临时表内部逻辑只是常规 DML 机制的副本。一般来说,如果新表数据已提交,可能需要写入磁盘。 Oracle 可能会开始处理它,例如将数据从日志缓冲区移动到磁盘,但在有实际的COMMIT 之前不会急于求成。

PL/SQL 时间都去哪儿了?

我不知道。 SQL 和 PL/SQL 引擎之间是否存在多个上下文切换或单个转换?据我所知,没有可用的指标显示在 SQL 和 PL/SQL 之间切换所花费的时间

我们可能永远不知道为什么 PL/SQL 代码会更慢。我不太担心。一般的答案是,绝大多数数据库工作无论如何都必须在 SQL 中完成。如果 Oracle 花费更多时间优化其数据库核心 SQL,而不是附加语言 PL/SQL,那将是很有意义的。

补充说明

对于性能测试,将connect by 逻辑删除到单独的步骤中会很有帮助。该 SQL 是加载数据的绝妙技巧,但它可能非常缓慢且占用大量资源。使用该技巧加载样本表一次,然后从该表中插入更为现实。

我尝试使用新的 Oracle 12c 特性、临时撤消和新的 18c 特性、私有临时表。两者都没有提高常规临时表的性能。

我不会打赌,但我可以看到结果会随着数据变大而完全改变的方式。日志缓冲区和缓冲区缓存只能变得如此之大。最终,后台 I/O 可能会累加并压倒一些进程,将 BACKGROUND 等待变成 FOREGROUND 等待。另一方面,PL/SQL 解决方案只有这么多的 PGA 内存,然后事情就崩溃了。

最后,这部分证实了我对“内存数据库”的怀疑。缓存并不是什么新鲜事,数据库已经做了几十年了。

【讨论】:

非常感谢您的广泛回答!下次我可以访问该客户的系统时,我会与您联系。虽然我普遍同意挂钟时间是唯一重要的事情,但我们的 DBA 告诉我,以前使用全局临时表的尝试在重负载下表现非常糟糕。这归结为我的基准对实际用例不是很重要。我很快会和他一起审查你的声明,看看他可能在哪里碰到临时表的墙。 有趣的是,在我的基准测试中,从 BULK COLLECT INTO 切换到 CAST(COLLECT(...) ...) INTO 的速度提高了大约 20% ...事实上,大量时间浪费在 SQL/PL/SQL 接口上。可能是因为从 SQL 收集到 PL/SQL 以及从 PL/SQL 绑定到 SQL 时,所有数据在不同内存区域之间移动。 See the comments in this answer。我认为临时表可能真的是最好的前进方式,因为一切都可以只使用 SQL 进行批量处理【参考方案2】:

GTT 比 PLSQL 集合更快的原因可能是从双重(或您的真实表)中选择的上下文切换以及表很小,几乎可以肯定在缓冲区缓存中(无 I/O)的事实

因为您的表很小,我会使用带有 CACHE 选项的 GTT,以增加数据在 SGA 中的机会。

(但是这种类型的选项应该小心使用,因为它看起来不像。 请阅读Ask Tom about alter table cache和ask Tom about pools)

【讨论】:

我不认为 single 上下文切换会导致性能方面的任何明显差异,只有在循环内多次发生或相似的。我还认为这更多的是使用缓存/缓冲区/池的问题。 感谢您的链接。恐怕在生产中,这可能会随着 10k 并发用户再次改变。我的基准可能与生产用途无关……您对此有何看法? @Wernfried 你是对的,单个上下文切换应该不重要。 Lukas,我担心的是那些“2-20x / seconds”。您确定这可以扩展到 10k 并发用户吗? 我们在系统中有相当数量的“常规”查询,但全局临时表和对象类型都是新的,这就是我问的原因。请注意,此查询每次登录仅执行一次,因此 10k 并发用户在这里并不是什么大问题...【参考方案3】:

如果你像这样比较解决方案,结果是什么:

DECLARE
  t test_t;
  n NUMBER(10);
StartTime TIMESTAMP(9);  
BEGIN

StartTime := LOCALTIMESTAMP;  
  -- Replace this with the actual temporary data set generation
  SELECT test_o(MOD(level, 10), '' || MOD(level, 12))
  BULK COLLECT INTO t
  FROM dual
  CONNECT BY level < 1000000;
DBMS_OUTPUT.PUT_LINE ( EXTRACT(SECOND FROM (LOCALTIMESTAMP - StartTime) ) ||' sec.');

StartTime := LOCALTIMESTAMP;  
  -- Replace this example query with more interesting statistics
  SELECT COUNT(DISTINCT n)
  INTO n 
  FROM TABLE(t) t;
DBMS_OUTPUT.PUT_LINE ( EXTRACT(SECOND FROM (LOCALTIMESTAMP - StartTime) ) ||' sec.');

END;

这些数字可能更适合比较。

【讨论】:

好点。我会在下次为该客户访问该系统时与您联系。 时间很长。我得到 6.5 秒,然后是 1.9 秒。这可能是由于“大量”数据在内存部分之间传输以收集或绑定t 变量。与计划中的实际执行时间相比,这是一个重大发现,尽管现实世界的数据远小于 1M 记录...

以上是关于Oracle 中临时数据的性能注意事项的主要内容,如果未能解决你的问题,请参考以下文章

Oracle数据库表设计时的注意事项

ORACLE 常用sql用法和及注意事项

Oracle ORA-08104报错处理方法及注意事项

MySQL派生表

MySQL派生表

ORACLE存储过程的创建和执行的简单示例和一些注意点