如何优化从具有数百万行的多个表中进行选择

时间:2010-11-17 15:08:41

标签: oracle optimization tree indexing

拥有以下表格(Oracle 10g):

catalog (
  id NUMBER PRIMARY KEY,
  name VARCHAR2(255),
  owner NUMBER,
  root NUMBER REFERENCES catalog(id)
  ...
)
university (
  id NUMBER PRIMARY KEY,
  ...
)
securitygroup (
  id NUMBER PRIMARY KEY
  ...
)
catalog_securitygroup (
  catalog REFERENCES catalog(id),
  securitygroup REFERENCES securitygroup(id)
)
catalog_university (
  catalog REFERENCES catalog(id),
  university REFERENCES university(id)
)

目录:500 000行,catalog_university:500 000,catalog_securitygroup:1 500 000。

我需要从目录中选择任意50行,并为当前大学和当前安全组按名称排序指定的根。有一个问题:

SELECT ccc.* FROM (
  SELECT cc.*, ROWNUM AS n FROM (
      SELECT c.id, c.name, c.owner
        FROM catalog c, catalog_securitygroup cs, catalog_university cu
        WHERE c.root = 100
          AND cs.catalog = c.id
          AND cs.securitygroup = 200
          AND cu.catalog = c.id
          AND cu.university = 300
        ORDER BY name
    ) cc 
) ccc WHERE ccc.n > 0 AND ccc.n <= 50;

其中100 - 某些目录,200 - 某些安全组,300 - 某些大学。此查询在3分钟内从~170 000返回50行。

但是下一个查询会在2秒内返回此行:

SELECT ccc.* FROM (
  SELECT cc.*, ROWNUM AS n FROM (
      SELECT c.id, c.name, c.owner
        FROM catalog c
        WHERE c.root = 100
        ORDER BY name
    ) cc 
) ccc WHERE ccc.n > 0 AND ccc.n <= 50;

我构建下一个索引:(catalog.id,catalog.name,catalog.owner),(catalog_securitygroup.catalog,catalog_securitygroup.index),(catalog_university.catalog,catalog_university.university)。

计划第一次查询(使用PLSQL Developer):

http://habreffect.ru/66c/f25faa5f8/plan2.jpg

计划第二次查询:

http://habreffect.ru/f91/86e780cc7/plan1.jpg

优化查询的方法有哪些?

5 个答案:

答案 0 :(得分:3)

可能有用且应该考虑的索引处理

WHERE c.root = 100
      AND cs.catalog = c.id
      AND cs.securitygroup = 200
      AND cu.catalog = c.id
      AND cu.university = 300

因此索引

的以下字段可能很有用
c: id, root   
cs: catalog, securitygroup   
cu: catalog, university

所以,尝试创建

(catalog_securitygroup.catalog, catalog_securitygroup.securitygroup)

(catalog_university.catalog, catalog_university.university)

修改 我错过了ORDER BY - 这些字段也应该被考虑,所以

(catalog.name, catalog.id)

可能是有益的(或者可以用于排序条件的一些其他复合索引 - 可能(catalog.root,catalog.name,catalog.id))

<强> EDIT2 虽然接受了另一个问题,但我会提供更多的思考。 我已经创建了一些测试数据并运行了一些基准测试。

测试用例在记录宽度方面是最小的(在catalog_securitygroup和catalog_university中,主键是(目录,安全组)和(目录,大学))。以下是每个表的记录数:

test=# SELECT (SELECT COUNT(*) FROM catalog), (SELECT COUNT(*) FROM catalog_securitygroup), (SELECT COUNT(*) FROM catalog_university);
 ?column? | ?column? | ?column? 
----------+----------+----------
   500000 |  1497501 |   500000
(1 row)

数据库是postgres 8.4,默认ubuntu安装,硬件i5,4GRAM

首先我将查询重写为

SELECT c.id, c.name, c.owner
FROM catalog c, catalog_securitygroup cs, catalog_university cu
WHERE c.root < 50 
  AND cs.catalog = c.id 
  AND cu.catalog = c.id
  AND cs.securitygroup < 200
  AND cu.university < 200
ORDER BY c.name
LIMIT 50 OFFSET 100

注意:条件变为少于保持可比较的中间行数(上述查询将返回198,801行而不使用LIMIT子句)

如果运行如上,没有任何额外的索引(保存PK和外键)它在冷数据库上运行 556 ms (这实际上表明我以某种方式过度简化了示例数据 - 我如果我在这里有2-4s而不求助于少于操作员,那会更高兴。)

这让我想到了我的观点 - 任何直接查询只加入和过滤(一定数量的表)并且只返回一定数量的记录,应该在任何体面的数据库上以1s运行,而不需要使用游标或对数据进行非规范化(其中一天我将不得不写一篇文章)。

此外,如果一个查询只返回50行并且执行简单的相等连接和限制性相等条件,它应该运行得更快。

现在让我们看看我是否添加了一些索引,这样的查询中最大的潜力通常是排序顺序,所以让我试试看:

CREATE INDEX test1 ON catalog (name, id);

这使得在冷数据库上查询的执行时间为 22ms

那是重点 - 如果您只想获取一页数据,那么您应该只在规范化上获得一页数据和查询执行时间strong>具有正确索引的数据应该在不错的硬件上花费不到100毫秒。

我希望我没有过分简化这种情况,以至于没有进行比较(正如我之前所说的那样简化,因为我不知道目录和多对多表之间关系的基数)。 / p>

所以,结论是

  • 如果我是你,我不会停止调整索引(和SQL),直到我得到查询的性能低于200毫秒作为拇指规则。
  • 只有当我找到一个客观的解释为什么它不能低于这个值时,我会求助于非规范化和/或游标等......

答案 1 :(得分:2)

首先,我假设您的University和SecurityGroup表格相当小。你发布了大表的大小,但它确实是其他大小的问题

您的问题来自于您无法首先加入最小的表格。您的加入订单应该从小到大。但由于您的映射表不包含安全组到大学表,因此您无法首先加入最小的表。所以你从一个或另一个开始,到一张大桌子,到另一张大桌子然后用那个大的中间结果你必须去一张小桌子。

如果您始终将current_univ和current_secgrp以及root作为输入,则希望尽快使用它们进行过滤。唯一的方法是更改​​您的架构。实际上,如果必须,您可以保留现有表格,但是您将使用此建议添加到空间。

您已经很好地规范了数据。这对于更新的速度来说非常好......对于查询而言并不是那么好。我们反规范化以加速查询(这是数据仓库的全部原因(确定和历史))。使用以下列构建单个映射表。

Univ_id,SecGrp_ID,Root,catalog_id。将它作为pk。

的前3列的索引组织表

现在,当您使用所有三个PK值查询该索引时,您将使用完整的允许目录ID列表完成该索引扫描,现在它只是对cat表的单个连接以获取cat项目详细信息而且您'重新开始跑步。

答案 2 :(得分:0)

Oracle基于成本的优化器利用它所拥有的所有信息来确定数据的最佳访问路径是什么,以及获取数据的成本最低的方法是什么。以下是与您的问题相关的一些随机点。

您列出的前三个表都有主键。其他表(catalog_university和catalog_securitygroup)也有主键吗?主键定义一列或一组非空且唯一的列,并且在关系数据库中非常重要。

Oracle通常通过在给定列上生成唯一索引来强制实施主键。如果可用,Oracle优化器更有可能使用唯一索引,因为它更有可能更具选择性。

如果可能,应将包含唯一值的索引定义为唯一(CREATE UNIQUE INDEX...),这将为优化程序提供更多信息。

您提供的其他索引不会比现有索引更具选择性。例如,(catalog.id,catalog.name,catalog.owner)上的索引是唯一的,但不如(catalog.id)上的现有主键索引有用。如果在catalog.name列上编写了一个查询来进行选择,则可以进行索引跳过扫描,但这开始变得很昂贵(在这种情况下大多数都不可能)。

由于您尝试在catalog.root列中进行选择,因此可能值得在该列上添加索引。这意味着它可以快速找到目录表中的相关行。第二个查询的时间可能有点误导。从目录中找到50个匹配的行可能需要2秒钟,但这些行很容易成为目录表中的前50行......找到符合您所有条件的50行可能需要更长时间,而不仅仅是因为您需要加入其他表来获取它们。在尝试演奏曲调时,我总是使用create table as select而不限制rownum。通过一个复杂的查询,我通常会关心将所有行恢复到需要多长时间...而使用rownum进行简单选择可能会产生误导

关于Oracle性能调优的一切都是为了为优化器提供足够的信息和正确的工具(索引,约束等)来正常工作。因此,使用DBMS_STATS.GATHER_TABLE_STATS()等内容获取优化程序统计信息非常重要。索引应具有在Oracle 10g或更高版本中自动收集的统计信息。

不知怎的,这对于Oracle优化器来说已经变成了很长的答案。希望其中一些能回答你的问题。以下是上述内容的摘要:

  • 为优化程序提供尽可能多的信息,例如,如果index是唯一的,则将其声明为此。
  • 在您的访问路径上添加索引
  • 查找正确的查询时间,不受限于rowwnum。在一个罐子里找到前50名M&amp; Ms总是比找到前50名红色M&amp; Ms
  • 更快
  • 收集优化程序统计信息
  • 在所有存在的表上添加唯一/主键。

答案 3 :(得分:0)

使用rownum是错误的并导致处理所有行。它将处理所有行,为它们分配所有行号,然后找到介于0和50之间的行。如果要在解释计划中查找COUNT STOPKEY而不是仅计数

下面的查询应该是一个改进,因为它只会获得前50行...但是仍然存在连接问题:

SELECT ccc.* FROM (
  SELECT cc.*, ROWNUM AS n FROM (
      SELECT c.id, c.name, c.owner
        FROM catalog c
        WHERE c.root = 100
        ORDER BY name
    ) cc 
    where rownum <= 50
) ccc WHERE ccc.n > 0 AND ccc.n <= 50;

此外,假设这是针对网页或类似的东西,也许有一种更好的方法来处理这个问题,而不仅仅是再次运行查询来获取下一页的数据。

答案 4 :(得分:-1)

尝试声明游标。我不知道oracle,但在Sql Server中看起来像这样:

declare @result 
table ( 
    id numeric,
    name varchar(255)
); 

declare __dyn_select_cursor cursor LOCAL SCROLL DYNAMIC for 

--Select
select distinct 
    c.id, c.name
From [catalog] c
    inner join university u
    on     u.catalog = c.id
       and u.university = 300
    inner join catalog_securitygroup s
    on     s.catalog = c.id
       and s.securitygroup = 200
Where
    c.root = 100
Order by name   

--Cursor
declare @id numeric;
declare @name varchar(255);

open __dyn_select_cursor; 

fetch relative 1 from __dyn_select_cursor into @id,@name declare @maxrowscount int 

set @maxrowscount = 50

while (@@fetch_status = 0 and @maxrowscount <> 0) 
begin 
     insert into @result values (@id, @name);
     set @maxrowscount = @maxrowscount - 1;
     fetch next from __dyn_select_cursor into  @id, @name; 
end 
close __dyn_select_cursor; 
deallocate __dyn_select_cursor; 


--Select temp, final result
select 
 id, 
 name
from @result;