Oracle 分区 table 查询成本与非分区 table 查询成本

Oracle partitioned table query cost vs non-partitioned table query cost

我有一个 table PO_HEADER 大约有 2000 万条记录。考虑到 table 的未来负载,我们决定对 table 进行分区以提高 sql 查询的性能。以下是用于创建新分区 tables.

的查询
CREATE TABLE PO_HEADER_LP 
 PARTITION BY LIST (BUYER_IDENTIFIER)
(PARTITION GC66287246AA VALUES ('GC66287246AA') TABLESPACE MITRIX_TABLES,
PARTITION GC43837235JK VALUES ('GC43837235JK') TABLESPACE MITRIX_TABLES,
PARTITION GC84338293AA VALUES ('GC84338293AA') TABLESPACE MITRIX_TABLES,
PARTITION DEFAULTBUID VALUES (DEFAULT) TABLESPACE MITRIX_TABLES) 
AS SELECT * 
   FROM PO_HEADER;

create index PO_HEADER_LP_SI_IDX on PO_HEADER_LP("SUPPLIER_IDENTIFIER") TABLESPACE MITRIX_INDEXES LOCAL;

旧 Table PO_HEADER 在 "BUYER_IDENTIFIER" 和 "SUPPLIER_IDENTIFIER" 列上有两个索引,如下所示:

create index PO_HEADER_BI_IDX on  PO_HEADER("BUYER_IDENTIFIER") TABLESPACE MITRIX_INDEXES;
create index PO_HEADER_SI_IDX on  PO_HEADER("SUPPLIER_IDENTIFIER") TABLESPACE MITRIX_INDEXES;

为了测试查询的性能,我在两个 table 上都执行了以下查询。但是,令我惊讶的是,我看到第二次查询的成本几乎是第一次查询的两倍。任何人都知道,为什么分区 table 的查询成本比正常 table 高。提前致谢。

select * from po_header where buyer_identifier='GC84338293AA' and supplier_identifier='GC75987723HT'; --cost: 56,941
select * from po_header_lp where buyer_identifier= 'GC84338293AA' and supplier_identifier='GC75987723HT'; --cost: 93,309

PO_HEADER buyer_identifier 和 supplier_identifier 列上的全局索引

PO_HEADER_LP with Global Index on supplier_identifier column

PO_HEADER_LP,本地索引在 supplier_identifier 列

您可以使用此脚本创建本地分区索引。

CREATE INDEX PO_HEADER_LOCAL_IDX ON PO_HEADER_LP
(BUYER_IDENTIFIER, SUPPLIER_IDENTIFIER)
LOCAL (
       PARTITION GC66287246AA,  
       PARTITION GC43837235JK,  
       PARTITION GC84338293AA,  
       PARTITION DEFAULTBUID
      );

还建议使用此脚本收集新创建的分区 table 的统计信息:

EXEC DBMS_STATS.GATHER_TABLE_STATS('SCHEMA Name','PO_HEADER_LP');

现在您可以再次生成以下SQL的执行计划:

select * from po_header_lp where buyer_identifier= 'GC84338293AA' and supplier_identifier='GC75987723HT';

希望对您有所帮助。

根据您的 DDL,我假设您有三个大买家(假设每个买家有 500 万条记录)和一些较小的买家。换句话说,这将是您列出分区架构的正确设置。

您可以验证它是否仅对买方测试访问:

EXPLAIN PLAN  SET STATEMENT_ID = 'jara1' into plan_table  FOR
select * from tab_lp where BUYER_ID = 1;
;  
SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'jara1','ALL'));

------------------------------------------------------------------------------------------------
| Id  | Operation             | Name   | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |  6662K|    82M|  4445   (2)| 00:00:01 |       |       |
|   1 |  PARTITION LIST SINGLE|        |  6662K|    82M|  4445   (2)| 00:00:01 |   KEY |   KEY |
|   2 |   TABLE ACCESS FULL   | TAB_LP |  6662K|    82M|  4445   (2)| 00:00:01 |     2 |     2 |
------------------------------------------------------------------------------------------------

non-partitioned table 的相同查询会产生更高的成本。为什么? 在分区 table 中,选定的买家(在您的情况下为 GC84338293AA,我使用的是代理键)有自己的分区。 所以全盘扫描这个分区是最好的方法。

select * from tab where BUYER_ID = 1;

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |  6596K|    81M| 14025   (1)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TAB  |  6596K|    81M| 14025   (1)| 00:00:01 |
--------------------------------------------------------------------------

   1 - filter("BUYER_ID"=1)

对于non-partitioned table(获取大约四分之一的数据)FULL TABLE SCAN也可以, 但当然 成本更高,因为必须扫描所有数据。

注意 - 如果您在这里看到更低的成本,不切实际的低 Rows 计数 and/or INDEX ACCESS, 这才是造成 低估成本 问题的原因。 所以不用担心旧成本太低,而不是新成本太高!

下一步是访问买方和供应商。要获得答案,您必须提供 附加信息。

供应商过滤器的选择性如何?

即如果谓词 buyer_identifier='GC84338293AA' return 说 5M 记录,如何记录 return 谓词与两列?

buyer_identifier='GC84338293AA' and supplier_identifier='GC75987723HT'

是4M还是100条记录?

如果完整的谓词 returns 只有比供应商本地索引少的记录就可以了。

如果它 return 的行数很大(比如分区的四分之一)- 你应该保持 FULL PARTITION SCAN 而不是使用它。 这类似于我对非分区 table.

的评论

供应商基数估计

如果 SUPPLIER 列包含有偏差的数据(这可能会欺骗 CBO 计算不正确的成本),您可以在此列中明确定义直方图。

我使用了这个语句语句,它计算完整数据的直方图(100% 对于高度偏斜的数据很重要)以及 table 和分区。

exec dbms_stats.gather_table_stats(ownname=>user,tabname=>'TAB_LP',granularity=>'all',estimate_percent => 100,METHOD_OPT => 'for columns SUPPLIER_ID size 254');

这适用于我的测试数据,即对于基数较低的供应商,打开了索引访问(在本地 no-prefixed 索引上),对于大型供应商,使用了完整的分区扫描。