1

我有两个查询来从具有相同架构的不同表中获取数据。我想通过两个表中的共享列来合并结果和排序。这需要尽可能高效地完成(两个表都有大量数据,两个查询的结果也是如此)。仅运行一个排序查询需要大约 1 小时。第一个表中大约有 5 亿行,目前在另一个表中很少(但会增加)。查询返回的项目数约为 2500 万。

具有相同架构但数据不同的两个表:

表a:

Name   |  ImpFile   |  ImpTime                  | FieldX  | FieldY
Sam      Imp01        2012-05-16 09:54:02.477     blah      abcde
Ann      Imp01        2012-05-16 09:54:02.478     blah      ldkse
Bart     Import12     2012-05-16 09:55:37.387     blah      dkcke
Sasha    Import12     2012-05-16 09:55:37.385     blah      leele

表b:

Name   |  ImpFile   |  ImpTime                  | FieldX  | FieldY
Mark     Imp01        2012-05-16 09:54:02.477     blah      lslsk
John     Import12     2012-05-16 09:55:37.384     blah      lmwqd

输出应按以下顺序:

Ann      Imp01        2012-05-16 09:54:02.478
Bart     Import12     2012-05-16 09:55:37.387
John     Import12     2012-05-16 09:55:37.384
Mark     Imp01        2012-05-16 09:54:02.477
Sam      Imp01        2012-05-16 09:54:02.477
Sasha    Import12     2012-05-16 09:55:37.385

我在想这样的事情可能会起作用(我无法测试,目前无法访问表),但我认为联合然后从中选择 * 的效率很低,只是为了订购:

SELECT * from
(
  SELECT
    a.Name as field1,
    a.ImpFile as field2,
    a.ImpTime
  FROM
    tablea a
  WHERE
    a.fieldX = "blah" AND
    length(a.fieldY) = 5
  UNION ALL
  SELECT
    b.Name as field1,
    b.ImpFile as field2,
    b.ImpTime
  FROM
    tableb b
  WHERE
    b.fieldX = "blah" AND
    length(b.fieldY) = 5
) foo
ORDER BY field1, field2;
4

2 回答 2

1

运行此查询的最快方法是删除内联视图并使用并行性。

这是运行此答案底部的测试用例的平均秒数。

                   No Parallel       Parallel
Inline View        221               206
No Inline View     167               154

细节

并行执行可以显着提高许多长时间运行的查询的性能。但它需要企业版、充足的资源、合理的配置等。在 11gR2 上,它可能像SELECT /*+ parallel */ * from .... 如果您没有企业版,那么像 @pkuderov 的“穷人的并行性”之类的东西可能会有所帮助。

令人惊讶的是,@AndyDan 建议将order by内联视图移到内部效果如此之好。通常,Oracle 足够聪明,可以为您进行这些简单的查询转换。UNION ALLOracle 显然对语句的性能投入了很多心思。它们在物化视图中受支持,有启用和计划USE_CONCAT之间切换的提示,12c 可以同时执行分支等。奇怪的是他们错过了如此简单的性能修复。ORUNION

这个问题可能缺少一个重要的细节。如果查询需要一个小时才能运行,则结果不会显示在屏幕上。如果正在存储数据,则大部分执行时间可能用于写入磁盘、记录更改、更新索引等。您应该包含有关如何存储结果的更多信息。

这个问题要求的是效率,而不是性能。从字面上回答,最有效的解决方案是“No Inline View, No Parallel”,因为并行可以消耗大量额外资源而获得一点好处。特别是在我的测试中,因为我在带有单个硬盘驱动器的桌面上运行它。使用 2 个并行线程几乎永远不会使性能翻倍,但在大多数服务器上它应该比我的测试用例做得更好。

测试设置

--Create sample tables with 67 million rows, gather stats, create table to hold results.
create table tablea nologging as
select 'Sam'   name,  'Imp01'    impfile, timestamp '2012-05-16 09:54:02.477' imptime, 'blah' fieldX, 'abcde' fieldY from dual union all
select 'Ann'   name,  'Imp01'    impfile, timestamp '2012-05-16 09:54:02.478' imptime, 'blah' fieldX, 'ldkse' fieldY from dual union all
select 'Bart'  name,  'Import12' impfile, timestamp '2012-05-16 09:55:37.387' imptime, 'blah' fieldX, 'dkcke' fieldY from dual union all
select 'Sasha' name,  'Import12' impfile, timestamp '2012-05-16 09:55:37.385' imptime, 'blah' fieldX, 'leele' fieldY from dual;

begin
    for i in 1 .. 24 loop
        insert /*+ append */ into tablea select * from tablea;
        commit;
    end loop;
end;
/

create table tableb nologging as
select 'Mark' name, 'Imp01'    impfile, timestamp '2012-05-16 09:54:02.477' imptime, 'blah' fieldX, 'lslsk' fieldY from dual union all
select 'John' name, 'Import12' impfile, timestamp '2012-05-16 09:55:37.384' imptime, 'blah' fieldX, 'lmwqd' fieldY from dual;

begin
    for i in 1 .. 25 loop
        insert /*+ append */ into tableb select * from tableb;
        commit;
    end loop;
end;
/

begin
    dbms_stats.gather_table_stats(user, 'TABLEA');
    dbms_stats.gather_table_stats(user, 'TABLEB');
end;
/

create table results nologging as select name, impfile, imptime from tablea;

测试查询

--#1: Inline view, no parallel.
insert /*+ append */ into results
SELECT * from
(
  SELECT a.Name as field1, a.ImpFile as field2, a.ImpTime
  FROM tablea a WHERE a.fieldX = 'blah' AND length(a.fieldY) = 5
  UNION ALL
  SELECT b.Name as field1, b.ImpFile as field2, b.ImpTime
  FROM tableb b WHERE b.fieldX = 'blah' AND length(b.fieldY) = 5
) foo
ORDER BY field1, field2;


--#2: No inline view, no parallel.
insert /*+ append */ into results
SELECT a.Name as field1, a.ImpFile as field2, a.ImpTime
FROM tablea a WHERE a.fieldX = 'blah' AND length(a.fieldY) = 5
UNION ALL
SELECT b.Name as field1, b.ImpFile as field2, b.ImpTime
FROM tableb b WHERE b.fieldX = 'blah' AND length(b.fieldY) = 5
ORDER BY 1, 2;

--#3: Inline view, parallel.
insert /*+ append */ into results
SELECT /*+ parallel(2) */ * from
(
  SELECT a.Name as field1, a.ImpFile as field2, a.ImpTime
  FROM tablea a WHERE a.fieldX = 'blah' AND length(a.fieldY) = 5
  UNION ALL
  SELECT b.Name as field1, b.ImpFile as field2, b.ImpTime
  FROM tableb b WHERE b.fieldX = 'blah' AND length(b.fieldY) = 5
) foo
ORDER BY field1, field2;

--#4: No inline view, parallel.
insert /*+ append */ into results
SELECT /*+ parallel(2) */ a.Name as field1, a.ImpFile as field2, a.ImpTime
FROM tablea a WHERE a.fieldX = 'blah' AND length(a.fieldY) = 5
UNION ALL
SELECT b.Name as field1, b.ImpFile as field2, b.ImpTime
FROM tableb b WHERE b.fieldX = 'blah' AND length(b.fieldY) = 5
ORDER BY 1, 2;

测试笔记

测试在 12c 上运行。为简单起见,我truncate table results;在每个查询之后删除了,并且我没有显示查询是如何运行 5 次的,而高值和低值被抛出。在实际场景中,您不仅要尝试衡量select性能,parallel还应将提示移到insert语句中。

于 2013-10-12T19:52:40.240 回答
0

如果它工作超过一个小时,那么就会有很多行。而且似乎有too many行。

您可以将查询执行划分为一些步骤并按部分检索数据。例如,将其除以field1. 获取以“A”开头的用户的结果,然后是“B”等。
您也可以动态制作:编写存储过程,返回(小于或等于)以子字符串和字符串<= N开头的行@s-直到返回数据的子字符串(如果返回所有数据,则返回 null)。 它将具有参数并以这种方式返回数据:field1@ns
varchar(max) @s, int @N, varchar(max) @ns output

  1. field1 like @s + '%'计算两个表 的行数
    1. 如果行数<= @N则返回数据并将@ns 输出为@s,并增加最后一个字符(直到z- 在这种情况下删除最后一个字符并增加下一个最后一个字符,依此类推)。例如,@s = 'A' ---> @ns = 'B'@s = 'CKZ' ---> @ns = 'CL'
    2. 如果>= @N那么@s --> @s + 'A',例如@s = 'KD' ---> @s = 'KDA'
  2. @s = ''只需在每次执行之后开始循环执行此存储过程,@s <--- returned @ns直到它不相等null

如果计数步骤花费太多时间,则手动设置过滤器的粒度(即从“A”循环到“Z”或从“AA”循环到“ZZ”或从“AAA”循环到“ZZZ”等)。
这只是一个想法,它可以帮助您在检索第一行之前无需等待一个小时。

于 2013-10-04T19:52:49.383 回答