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

Luk*_*der 5 sql oracle oracle11g

我正在评估各种选项,以针对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;
Run Code Online (Sandbox Code Playgroud)

计划:

----------------------------------------------------
| 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 |
----------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

计划:

------------------------------------------------------------------
| 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 |
------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

3.物化观点

我正在为这个用例排除它们,因为所讨论的临时数据集相当复杂,对更新物化视图的影响太大了.

真实的数据考虑

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

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

问题

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

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

Jon*_*ler 3

由于缓存和异步 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
Run Code Online (Sandbox Code Playgroud)

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

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

优化读取

从小型临时表中读取数据仅使用位于内存中的缓冲区高速缓存。仅多次运行查询部分,并观察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');
Run Code Online (Sandbox Code Playgroud)

优化写入

理想情况下不会有物理 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 内存,然后就会崩溃。

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