对分区表使用分区索引
Using partitioned indexes with partitioned tables
我正在尝试了解构建用于分区 tables 的复合本地分区索引的最佳方法。
这是我的例子table:
ADDRESS
id
street
city
state
tenant
Address
table 是在租户列上分区的列表。几乎所有的查询都会在查询中包含租户列,因此这里真的不需要担心跨分区搜索。
我想让像 select * from address where tenant = 'X' and street = 'Y' and city = 'Z'
这样的查询最终尽可能地发挥最佳性能。在我看来,这样做的正确方法似乎是首先限制特定租户(分区),然后使用本地分区索引。
现在,我相信每个引用只能使用一个索引 table,所以我想制作一个最有用的复合本地分区索引。我设想其中包含街道和城市的综合指数。所以我有两个问题:
租户自己要有索引吗?
租户是否应该成为综合指数的一部分?
对为什么它应该在路上或其他地方的一些理解会有所帮助,因为我认为我不完全理解分区如何与分区索引一起工作。
如果索引是唯一的,那么您必须包含 TENANT 以使其成为本地索引。
如果它不是唯一的,则不要包含它,因为在 LIST/RANGE 分区的情况下它不会提高任何性能。一个分区有多个不同值的hash分区可以考虑包含。
UPD:但这取决于您使用的是哪种分区 - "static" 或 "dynamic"。 "Static" 是当所有分区在 create table 语句中定义一次并且在应用程序 运行 时保持不变。 "Dynamic" 是应用程序 adds/change 分区时(如每日进程为所有 table 等添加每日列表分区等)。
因此您应该避免 "dynamic" 分区的全局索引 - 在这种情况下,每次添加新分区时它都会失效。对于 "static" 选项,如果您有时需要扫描所有分区,则可以使用全局索引。
create index address_city_street_idx on address(city, street) compress 1 local;
我认为索引对于这个查询是理想的,给定一个 table 在 TENANT 上分区的列表:
select * from address where tenant = 'X' and street = 'Y' and city = 'Z'
回答问题 1 和 2:由于 TENANT 是分区键,它不应在此索引中,并且可能不应在任何索引中。该列已被分区 p运行ing 使用到 select 相关段。这项工作是在编译或解析时完成的,几乎是免费的。
测试用例中的执行计划表明分区 p运行ing 正在发生。操作 PARTITION LIST SINGLE
以及列 Pstart
和 Pstop
列出数字 3 而不是像 KEY
这样的变量这一事实表明 Oracle 在查询有 运行。 Oracle 会在编译时立即丢弃不相关的租户,无需担心在 运行 时使用索引进一步减少租户。
我的索引建议取决于对数据的一些假设。 CITY 和 STREET 听起来都不像它们会为租户唯一标识一行。 STREET 听起来比 CITY 更 select 生动。如果一个 CITY 有多个 STREET,那么按顺序对它们进行索引并使用索引压缩可以节省很多 space.
如果索引明显更小,它的层级可能会更少,这意味着查找需要的 I/Os 会稍微少一些。如果它更小,它可以容纳更多的缓冲区缓存,这可能会进一步提高性能。
但是 table 这么大,我感觉两者的 BLEVEL(索引级别数)将相同,并且两个索引都太大而无法有效使用缓存。这意味着 (CITY,STREET)
和 (STREET,CITY)
之间可能没有任何性能差异。但是使用 (CITY,STREET)
和压缩你至少可以节省大量的 space.
测试用例
我假设您不能简单地在生产环境中创建两个索引并试用它们。在这种情况下,您需要先创建一些测试。
这个测试用例不强烈支持我的建议。它只是一个更彻底的测试用例的起点。您需要创建一个具有更大数据量和更真实数据分布的数据。
--Create sample table.
create table address
(
id number,
street varchar2(100),
city varchar2(100),
state varchar2(100),
tenant varchar2(100)
) partition by list (tenant)
(
partition p1 values ('tenant1'),
partition p2 values ('tenant2'),
partition p3 values ('tenant3'),
partition p4 values ('tenant4'),
partition p5 values ('tenant5')
) nologging;
--Insert 5M rows.
--Note the assumptions about the selectivity of the street and city
--are critical to this issue. Adjust the MOD as necessary.
begin
for i in 1 .. 5 loop
insert /*+ append */ into address
select
level,
'Fake Street '||mod(level, 10000),
'City '||mod(level, 100),
'State',
'tenant'||i
from dual connect by level <= 1000000;
commit;
end loop;
end;
/
--Table uses 282MB.
select sum(bytes)/1024/1024 mb from dba_segments where segment_name = 'ADDRESS' and owner = user;
--Create different indexes.
create index address_city_street_idx on address(city, street) compress 1 local;
create index address_street_city_idx on address(street, city) local;
--Gather statistics.
begin
dbms_stats.gather_table_stats(user, 'ADDRESS');
end;
/
--Check execution plan.
--Oracle by default picks STREET,CITY over CITY,STREET.
--I'm not sure why. And the cost difference is only 1, so I think things may be different with realistic data.
explain plan for select * from address where tenant = 'tenant3' and street = 'Fake Street 50' and city = 'City 50';
select * from table(dbms_xplan.display);
/*
Plan hash value: 2845844304
--------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 44 | 4 (0)| 00:00:01 | | |
| 1 | PARTITION LIST SINGLE | | 1 | 44 | 4 (0)| 00:00:01 | 3 | 3 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| ADDRESS | 1 | 44 | 4 (0)| 00:00:01 | 3 | 3 |
|* 3 | INDEX RANGE SCAN | ADDRESS_STREET_CITY_IDX | 1 | | 3 (0)| 00:00:01 | 3 | 3 |
--------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("STREET"='Fake Street 50' AND "CITY"='City 50')
*/
--Check execution plan of forced CITY,STREET index.
--I don't suggest using a hint in the real query, this is just to compare plans.
explain plan for select /*+ index(address address_city_street_idx) */ * from address where tenant = 'tenant3' and street = 'Fake Street 50' and city = 'City 50';
select * from table(dbms_xplan.display);
/*
Plan hash value: 1084849450
--------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 44 | 5 (0)| 00:00:01 | | |
| 1 | PARTITION LIST SINGLE | | 1 | 44 | 5 (0)| 00:00:01 | 3 | 3 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| ADDRESS | 1 | 44 | 5 (0)| 00:00:01 | 3 | 3 |
|* 3 | INDEX RANGE SCAN | ADDRESS_CITY_STREET_IDX | 1 | | 3 (0)| 00:00:01 | 3 | 3 |
--------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("CITY"='City 50' AND "STREET"='Fake Street 50')
*/
--Both indexes have BLEVEL=2.
select *
from dba_indexes
where index_name in ('ADDRESS_CITY_STREET_IDX', 'ADDRESS_STREET_CITY_IDX');
--CITY,STREET = 160MB, STREET,CITY=200MB.
--You can see the difference already. It may get larger with different data distribution.
--And it may get larger with more data, as it may compress better with more repetition.
select segment_name, sum(bytes)/1024/1024 mb
from dba_segments
where segment_name in ('ADDRESS_CITY_STREET_IDX', 'ADDRESS_STREET_CITY_IDX')
group by segment_name;
我正在尝试了解构建用于分区 tables 的复合本地分区索引的最佳方法。
这是我的例子table:
ADDRESS
id
street
city
state
tenant
Address
table 是在租户列上分区的列表。几乎所有的查询都会在查询中包含租户列,因此这里真的不需要担心跨分区搜索。
我想让像 select * from address where tenant = 'X' and street = 'Y' and city = 'Z'
这样的查询最终尽可能地发挥最佳性能。在我看来,这样做的正确方法似乎是首先限制特定租户(分区),然后使用本地分区索引。
现在,我相信每个引用只能使用一个索引 table,所以我想制作一个最有用的复合本地分区索引。我设想其中包含街道和城市的综合指数。所以我有两个问题:
租户自己要有索引吗?
租户是否应该成为综合指数的一部分?
对为什么它应该在路上或其他地方的一些理解会有所帮助,因为我认为我不完全理解分区如何与分区索引一起工作。
如果索引是唯一的,那么您必须包含 TENANT 以使其成为本地索引。 如果它不是唯一的,则不要包含它,因为在 LIST/RANGE 分区的情况下它不会提高任何性能。一个分区有多个不同值的hash分区可以考虑包含。
UPD:但这取决于您使用的是哪种分区 - "static" 或 "dynamic"。 "Static" 是当所有分区在 create table 语句中定义一次并且在应用程序 运行 时保持不变。 "Dynamic" 是应用程序 adds/change 分区时(如每日进程为所有 table 等添加每日列表分区等)。
因此您应该避免 "dynamic" 分区的全局索引 - 在这种情况下,每次添加新分区时它都会失效。对于 "static" 选项,如果您有时需要扫描所有分区,则可以使用全局索引。
create index address_city_street_idx on address(city, street) compress 1 local;
我认为索引对于这个查询是理想的,给定一个 table 在 TENANT 上分区的列表:
select * from address where tenant = 'X' and street = 'Y' and city = 'Z'
回答问题 1 和 2:由于 TENANT 是分区键,它不应在此索引中,并且可能不应在任何索引中。该列已被分区 p运行ing 使用到 select 相关段。这项工作是在编译或解析时完成的,几乎是免费的。
测试用例中的执行计划表明分区 p运行ing 正在发生。操作 PARTITION LIST SINGLE
以及列 Pstart
和 Pstop
列出数字 3 而不是像 KEY
这样的变量这一事实表明 Oracle 在查询有 运行。 Oracle 会在编译时立即丢弃不相关的租户,无需担心在 运行 时使用索引进一步减少租户。
我的索引建议取决于对数据的一些假设。 CITY 和 STREET 听起来都不像它们会为租户唯一标识一行。 STREET 听起来比 CITY 更 select 生动。如果一个 CITY 有多个 STREET,那么按顺序对它们进行索引并使用索引压缩可以节省很多 space.
如果索引明显更小,它的层级可能会更少,这意味着查找需要的 I/Os 会稍微少一些。如果它更小,它可以容纳更多的缓冲区缓存,这可能会进一步提高性能。
但是 table 这么大,我感觉两者的 BLEVEL(索引级别数)将相同,并且两个索引都太大而无法有效使用缓存。这意味着 (CITY,STREET)
和 (STREET,CITY)
之间可能没有任何性能差异。但是使用 (CITY,STREET)
和压缩你至少可以节省大量的 space.
测试用例
我假设您不能简单地在生产环境中创建两个索引并试用它们。在这种情况下,您需要先创建一些测试。
这个测试用例不强烈支持我的建议。它只是一个更彻底的测试用例的起点。您需要创建一个具有更大数据量和更真实数据分布的数据。
--Create sample table.
create table address
(
id number,
street varchar2(100),
city varchar2(100),
state varchar2(100),
tenant varchar2(100)
) partition by list (tenant)
(
partition p1 values ('tenant1'),
partition p2 values ('tenant2'),
partition p3 values ('tenant3'),
partition p4 values ('tenant4'),
partition p5 values ('tenant5')
) nologging;
--Insert 5M rows.
--Note the assumptions about the selectivity of the street and city
--are critical to this issue. Adjust the MOD as necessary.
begin
for i in 1 .. 5 loop
insert /*+ append */ into address
select
level,
'Fake Street '||mod(level, 10000),
'City '||mod(level, 100),
'State',
'tenant'||i
from dual connect by level <= 1000000;
commit;
end loop;
end;
/
--Table uses 282MB.
select sum(bytes)/1024/1024 mb from dba_segments where segment_name = 'ADDRESS' and owner = user;
--Create different indexes.
create index address_city_street_idx on address(city, street) compress 1 local;
create index address_street_city_idx on address(street, city) local;
--Gather statistics.
begin
dbms_stats.gather_table_stats(user, 'ADDRESS');
end;
/
--Check execution plan.
--Oracle by default picks STREET,CITY over CITY,STREET.
--I'm not sure why. And the cost difference is only 1, so I think things may be different with realistic data.
explain plan for select * from address where tenant = 'tenant3' and street = 'Fake Street 50' and city = 'City 50';
select * from table(dbms_xplan.display);
/*
Plan hash value: 2845844304
--------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 44 | 4 (0)| 00:00:01 | | |
| 1 | PARTITION LIST SINGLE | | 1 | 44 | 4 (0)| 00:00:01 | 3 | 3 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| ADDRESS | 1 | 44 | 4 (0)| 00:00:01 | 3 | 3 |
|* 3 | INDEX RANGE SCAN | ADDRESS_STREET_CITY_IDX | 1 | | 3 (0)| 00:00:01 | 3 | 3 |
--------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("STREET"='Fake Street 50' AND "CITY"='City 50')
*/
--Check execution plan of forced CITY,STREET index.
--I don't suggest using a hint in the real query, this is just to compare plans.
explain plan for select /*+ index(address address_city_street_idx) */ * from address where tenant = 'tenant3' and street = 'Fake Street 50' and city = 'City 50';
select * from table(dbms_xplan.display);
/*
Plan hash value: 1084849450
--------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 44 | 5 (0)| 00:00:01 | | |
| 1 | PARTITION LIST SINGLE | | 1 | 44 | 5 (0)| 00:00:01 | 3 | 3 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| ADDRESS | 1 | 44 | 5 (0)| 00:00:01 | 3 | 3 |
|* 3 | INDEX RANGE SCAN | ADDRESS_CITY_STREET_IDX | 1 | | 3 (0)| 00:00:01 | 3 | 3 |
--------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("CITY"='City 50' AND "STREET"='Fake Street 50')
*/
--Both indexes have BLEVEL=2.
select *
from dba_indexes
where index_name in ('ADDRESS_CITY_STREET_IDX', 'ADDRESS_STREET_CITY_IDX');
--CITY,STREET = 160MB, STREET,CITY=200MB.
--You can see the difference already. It may get larger with different data distribution.
--And it may get larger with more data, as it may compress better with more repetition.
select segment_name, sum(bytes)/1024/1024 mb
from dba_segments
where segment_name in ('ADDRESS_CITY_STREET_IDX', 'ADDRESS_STREET_CITY_IDX')
group by segment_name;