【问题标题】:Statictis/aggregation queries: full scan or index or partitioning?统计/聚合查询:全扫描或索引或分区?
【发布时间】:2014-05-21 01:26:36
【问题描述】:

假设我们有类似的数据(Oracle 语法但不重要):

create table EVENT (
    UUID raw(16) default sys_guid(),  -- no significant for question
    TYPE number(2,0) not null,
    DATEX date not null,
    AMOUNT number(18,2) -- we use op: SUM, COUNT, AVG, STDDEV_POP, MEDIAN, etc
);

distinct TYPE 仅限于人类可管理的计数(例如 20),DATEX 是过去 10 年,AMOUNT 是用于统计分析的字段(例如获取给定 EVENT 的直方图 @ 987654327@选定DATEX 期间的月份)。

个数或行数约为 2e+6。

由于所有查询都使用TYPE = nDATEX between DATE 'yyyy-mm-dd' and DATE 'yyyy-mm-dd' 限制,我决定为该字段创建索引:

create index INDEX_EVENT_MAIN on EVENT (TYPE ASC, DATEX ASC);

使用全扫描查询性能比使用大约 x2-x5 倍。

另一种策略是按事件TYPE 将数据拆分到不同的单个表中,例如 EVENT1、EVENT2……我使用这些表时根本没有索引。在这种情况下,EVENTn 表中的查询性能比EVENT 大表中的 TYPE = n 好 x2-x10 倍(都是全扫描)。

我还对 EVENT 表进行分区:

alter table EVENT add partition event_default values (DEFAULT);

alter table DATA_XX split partition event_default values(2) into (
  partition event2,
  partition event_default);

EVENT = 2 上的查询性能与单独的EVENT2 表相同。

我不是 DBA 专家,也不是制作 Web 2.0 企业网站的专家。所以我可以做实验和猜测,但不懂黑盒,也无法解释强关系/算法理论的结果。

所以有相关问题:

  • 索引不适用于统计查询(处理范围广泛的行),并且更好地进行全扫描?
  • 索引是否仅用于点(非范围,按 ID 获取)查询(非范围)?
  • 表拆分或分区是否是提高统计/聚合查询的查询性能的唯一方法?

【问题讨论】:

标签: sql database oracle indexing


【解决方案1】:

全表扫描更适合读取“大量”数据,索引更适合读取“少量”数据。找到合适的大小主要取决于索引集群因子和单块 IO 与多块 IO。

索引聚类因子

正如 Ronnis 所说,花费在 I/O 上的时间取决于从磁盘读取的块数。但是一次从同一个块中读取多行通常非常便宜——一旦块在内存中,扫描行的速度很快。

真正的问题是,根据数据在磁盘上的排序方式,读取一小部分行可能需要读取大部分数据。由于表数据的创建方式,某些索引效率低下。

index clustering factor 衡量数据的有序程度。号码是一个 估计“通过索引读取整个表所需的 I/O 数量”。这个号码可以在DBA_INDEXES.CLUSTERING_FACTOR找到。

您通常可以通过使用按特定方式排序的行重建表来优化索引。但这仅适用于一个索引。

单块与多块 I/O

Oracle 可以一次读取一个块或一次读取多个块。

对于从单行读取单个值,显然最快的方法是读取尽可能少的数据。像索引范围扫描这样的访问方法总是使用 单块 I/O,全表扫描和快速全索引扫描等访问方法总是使用多块 I/O。

对于大量数据,以大块的形式读取数据比一次读取一个数据要高效得多。我无法很好地解释磁头如何寻找、从扇区读取数据等。细节并不重要,这里有一个关于数据库性能的通用课程 - 总是分批处理。

您甚至可以计算出系统上的单块与多块时间。如果您收集了系统统计信息并且它们是准确的,则可以使用 这两个查询来确定一次读取一个块与批量读取块的时间:

--Blocks read per multi-block read, if you set the value yourself.
select value from v$parameter where name = 'db_file_multiblock_read_count';

--Time to read single and multiple blocks, in milliseconds.
--And average blocks per multi-block read.
select * from sys.aux_stats$ where pname in ('SREADTIM', 'MREADTIM', 'MBRC');

【讨论】:

  • 索引组织表很棒!感谢您分享您的知识!我已经对彼此的列有分层依赖,因此将尝试将我的表重组为复合索引上的索引组织表(如我的示例中的 TYPE + DATEX)+1
【解决方案2】:

我会回答你的问题,但首先需要一些背景知识才能理解答案:

执行完整扫描所需的时间在很大程度上取决于数据所在硬盘的吞吐量。如果您的磁盘可以提供 200 mb/s 的速度,则无论有多少行,对具有 200 mb 数据的表执行全表扫描大约需要 1 秒。

创建一个 200 mb 的表,没有任何索引,但其中的列 ID 在数据中是唯一的。在这种情况下,以下两个查询将花费相同的时间,因为大部分时间都花在等待硬盘驱动器将数据交给 Oracle 进程。

第一个查询需要很长时间,因为 Oracle 必须遍历所有数据才能找到满足id = 1 的行。第二个查询需要很长时间,因为 Oracle 必须遍历所有数据才能聚合 one_columnanother_column 的所有值。

select id, one_column, another_column
  from two_hundred_mb_table
 where id = 1

select sum(one_column) / sum(another_column) 
  from two_hundred_mb_table

如果您要为列 ID 添加索引,一切都会改变。第一个查询现在只需访问 ID = 1 的索引,获取作为数据文件中行的物理地址的“rowid”,请求磁盘上的“块”,然后选择该行。现在,第一个查询要快得多,因为它不必遍历所有数据

这里的关键点是,即使您已经对列进行了索引,您仍然不能直接从磁盘中选择行。您仍然需要从磁盘中取出整个块(通常为 ~8kb)。平均行长度为 100 字节,这意味着该块包含 82 行。因此,您阅读了 82 行以找到您的一行。

这就是为什么在索引变得比表扫描慢之前通常无法通过索引读取大量行的原因。原因是您最终可能会一遍又一遍地重新阅读同一个块。当然,当通过全表扫描读取数据比通过索引读取数据时,存在一个断点(在每种情况下都不同)。

现在,关于你的问题:

1.索引是否不适用于统计查询(处理范围广泛的行),并且更好地进行全扫描? 答案就在上面的文字中。它与总和/计数或索引无关,它与表中的数据量有关,以及是否有进入感兴趣子集的有效访问路径。

2。索引是否仅用于点(非范围,按 ID 获取)查询(非范围)? 同样在这里,答案就在上面的文字中。您可以对索引使用范围查询,但如果感兴趣的子集太大,您最好使用全表扫描。

3.表拆分或分区是提高统计/聚合查询的查询性能的唯一方法吗?

如果表是 2,000 mb,并且您的磁盘可以返回 200 mb/s,则执行全表扫描需要 10 秒。假设type 上的数据分布均匀,并且您有 10 个不同的值,您可以按type 对表进行列表分区。在这种情况下,每个分区都是 200 mb,因此对 type=n 的任何查询都需要 1 秒而不是 10 秒。但是,所有没有type=n 的查询仍然需要 10 秒。

您还可以在datex 列上进行范围分区,例如每月创建一个分区。再次假设该表为 2000 mb 且数据分布均匀,您最终将在每个分区中获得 1/12 的数据。

您也可以将这些组合起来,并按 LIST(event) 和 RANGE(datex) 进行分区。

如果您仍然无法满足性能要求,您可以考虑创建聚合表(或物化视图)。例如,如果您发现自己对较大的时间跨度进行了大量分析,您可以按月聚合数据,并对聚合数据执行更高级别的查询。一旦您找到了需要“向下钻取”的月份,您就可以再次使用事件表和几乎命中一个分区的谓词。

【讨论】:

  • 感谢您的回答!我以前不知道物化视图。并且已经考虑分区by LIST(event) 和子分区on RANGE(datex)。没有专家建议,新手很难做出这样的决定!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-04
  • 2021-04-22
  • 1970-01-01
相关资源
最近更新 更多