将分区索引与分区表

时间:2017-01-13 17:50:12

标签: sql database oracle indexing database-partitioning

我正在尝试理解构造用于分区​​表的复合本地分区索引的最佳方法。

这是我的示例表:

ADDRESS
id
street
city
state
tenant

Address表是在租户列上分区的列表。几乎所有的查询都会在查询中包含租户列,因此这里不需要考虑跨分区搜索。

我想让select * from address where tenant = 'X' and street = 'Y' and city = 'Z'之类的查询尽可能以最佳方式执行。对我来说,似乎正确的方法是首先限制特定租户(分区),然后使用本地分区索引。

现在,我相信每个引用表只能使用一个索引,所以我想创建一个最有用的复合本地分区索引。我设想了包含街道和城市的综合指数。所以我有两个问题:

  1. 租户本身是否有索引?

  2. 租户是否应该成为综合指数的一部分?

  3. 对于为什么它应该在路上或其他方面的一些理解会有所帮助,因为我不认为我完全理解分区如何与分区索引一起工作。

2 个答案:

答案 0 :(得分:2)

create index address_city_street_idx on address(city, street) compress 1 local;

我相信索引是这个查询的理想选择,给定一个在TENANT上列出分区的表:

select * from address where tenant = 'X' and street = 'Y' and city = 'Z' 

回答问题1和2:由于TENANT是分区键,因此它不应该在此索引中,并且可能不应该在任何索引中。分区修剪已使用该列来选择相关的段。这项工作是在编译或解析时完成的,几乎是免费的。

测试用例中的执行计划表明正在进行分区修剪。操作PARTITION LIST SINGLE以及列PstartPstop列出数字3而不是像KEY这样的变量这一事实表明Oracle已经确定了分区之前的分区查询已运行。 Oracle在编译时立即丢弃不相关的TENANT,无需担心在运行时使用索引进一步减少TENANT。

我的索引建议取决于对数据的一些假设。 CITY和STREET都听起来不像是为租户唯一标识一行。而STREET听起来比CITY更具选择性。如果一个CITY有多个STREET,那么按顺序索引它们并使用索引压缩可以节省大量空间。

如果索引明显更小,则可能具有更少的级别,这意味着它将需要稍微更少的I / O来进行查找。如果它更小,它可以适应缓冲区缓存,这可能会进一步提高性能。

但是如果有一个这么大的表,我觉得BLEVEL(索引级别的数量)对于两者都是相同的,并且两个索引都太大而无法有效地使用缓存。这意味着(CITY,STREET)(STREET,CITY)之间可能没有任何性能差异。但是使用(CITY,STREET)和压缩,您至少可以节省大量空间。

测试用例

我假设您不能简单地在生产中创建两个索引并尝试它们。在这种情况下,您将首先想要创建一些测试。

此测试用例并不强烈支持我的建议。它只是一个更彻底的测试用例的起点。您需要创建一个包含更多数据和更真实的数据分布的文件。

--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;

答案 1 :(得分:1)

如果索引唯一,则必须包含TENANT才能使其成为本地。 如果它不是唯一的,则不要包含它,因为在LIST / RANGE分区的情况下它不会改善任何性能。如果它是一个分区中具有许多不同值的散列分区,则可以考虑包含它。

UPD:但是这取决于你使用的是什么样的分区 - “静态”或“动态”。 “静态”是指在create table语句中定义所有分区一次并在应用程序运行时保持不变。 “动态”是指应用程序添加/更改分区(如每日进程为所有表添加每日列表分区等)。

因此,您应该避免“动态”分区的全局索引 - 在这种情况下,每次添加新分区时它都将变为无效。对于“静态”选项,如果有时需要扫描所有分区,则可以使用全局索引。