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 索引上),对于大型供应商,使用了完整的分区扫描。
我有一个 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 索引上),对于大型供应商,使用了完整的分区扫描。