4

次の Oracle テーブルがあります。

create table my_table(
   start int,
   end int
);

insert into my_table values(1, 3);
insert into my_table values(5, 7);
insert into my_table values(11, 200);
insert into my_table values(311, 5000);
insert into my_table values(60004, 60024);
insert into my_table values(123213, 12312312);

このテーブルには 1M 行があり、番号の範囲 ('start'、'end') が格納されています。すべての番号は一意であり、重複する範囲はなく、このテーブルの 1 つの範囲にのみ番号を入れることができます。範囲の「開始」を識別するための変数 my_number。

 execute immediate 
    'select start from my_table where :1 between start and end' using my_number

2 つのフィールドに結合インデックスを作成しました。問題は、my_number が小さい場合、クエリのパフォーマンスは良好ですが、my_number が増加している場合、クエリ時間は継続的に増加しています。my_number がはるかに大きい場合、完了するまでにかなりの時間がかかります。誰かがこのクエリを改善する方法を持っていますか? この方法には、my_table の再設計が含まれます。ありがとう。

4

5 に答える 5

0

これはできていますか?

create table my_table(
start int,
end int
constraint PK_comp primary key (start, end)
) ;
于 2013-04-04T18:03:35.220 に答える
0

各列のインデックスを作成し、次のクエリを使用します。

select start_num
from my_table
where
    start_num =
    (
        --Last start <= number
        select start_num
        from
        (
            select start_num
            from my_table
            where :1 >= start_num
            order by start_num desc
        )
        where rownum = 1
    ) and
    end_num =
    (
        --First end >= number
        select end_num
        from
        (
            select end_num
            from my_table
            where :1 <= end_num
            order by end_num
        )
        where rownum = 1
    );

うん。これを書くにはもっと良い方法があるかもしれません。または、これを関数でラップすることもできます。

問題

テスト データ (予約語以外の列名を使用):

drop table my_table;
create table my_table(
   start_num int,
   end_num int
);
insert into my_table select level*2,level*2+1 from dual connect by level <= 1000000;
commit;
create index my_table_index on my_table(start_num, end_num);
begin
    dbms_stats.gather_table_stats(user, 'MY_TABLE', no_invalidate => false);
end;
/

低い数値はほぼ瞬時 - 0.015 秒

select start_num from my_table where 2 between start_num and end_num;

数値が大きいほど遅くなります - 0.125 秒

select start_num from my_table where 1000000 between start_num and end_num;

範囲スキャンと全テーブル スキャンの間には 1 つのポイントがあります。

explain plan for select start_num from my_table where 402741 between start_num and end_num;
select * from table(dbms_xplan.display);

Plan hash value: 3804444429

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |   160K|  1570K|   622   (2)| 00:00:08 |
|*  1 |  TABLE ACCESS FULL| MY_TABLE |   160K|  1570K|   622   (2)| 00:00:08 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("START_NUM"<=402742 AND "END_NUM">=402742)


explain plan for select start_num from my_table where 402742 between start_num and end_num;
select * from table(dbms_xplan.display);


Plan hash value: 3804444429

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |   160K|  1570K|   622   (2)| 00:00:08 |
|*  1 |  TABLE ACCESS FULL| MY_TABLE |   160K|  1570K|   622   (2)| 00:00:08 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("START_NUM"<=402742 AND "END_NUM">=402742)

しかし問題は、Oracle がインデックスを使用しないことではありません。素朴な方法でインデックスを使用しても役に立ちません。実際、これはさらに遅く、0.172 秒です。

select /*+ index(my_table my_table_index) */ start_num
from my_table
where 1000000 between start_num and end_num;

解決

新しいインデックスを作成します。

drop index my_table_index;
create index my_table_index1 on my_table(start_num);
create index my_table_index2 on my_table(end_num);
begin
    dbms_stats.gather_table_stats(user, 'MY_TABLE', no_invalidate => false);
end;
/

任意の数に対して、結果は再び瞬時に得られます。

select start_num
from my_table
where
    start_num =
    (
        --Last start <= number
        select start_num
        from
        (
            select start_num
            from my_table
            where 1000000 >= start_num
            order by start_num desc
        )
        where rownum = 1
    ) and
    end_num =
    (
        --First end >= number
        select end_num
        from
        (
            select end_num
            from my_table
            where 1000000 <= end_num
            order by end_num
        )
        where rownum = 1
    );

計画は素晴らしいようです - これはおそらくあなたが得ることができる最高のパフォーマンスです.

Plan hash value: 522166032

--------------------------------------------------------------------------------------------------
| Id  | Operation                      | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                 |     1 |    10 |    10   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID   | MY_TABLE        |     1 |    10 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN             | MY_TABLE_INDEX2 |     1 |       |     3   (0)| 00:00:01 |
|*  3 |    COUNT STOPKEY               |                 |       |       |            |          |
|   4 |     VIEW                       |                 |     3 |    39 |     3   (0)| 00:00:01 |
|*  5 |      INDEX RANGE SCAN          | MY_TABLE_INDEX2 |     3 |    18 |     3   (0)| 00:00:01 |
|*  6 |   COUNT STOPKEY                |                 |       |       |            |          |
|   7 |    VIEW                        |                 |     2 |    26 |     3   (0)| 00:00:01 |
|*  8 |     INDEX RANGE SCAN DESCENDING| MY_TABLE_INDEX1 |   500K|  2929K|     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("START_NUM"= (SELECT "START_NUM" FROM  (SELECT "START_NUM" "START_NUM" FROM 
              "MY_TABLE" "MY_TABLE" WHERE "START_NUM"<=1000000 ORDER BY "START_NUM" DESC) 
              "from$_subquery$_002" WHERE ROWNUM=1))
   2 - access("END_NUM"= (SELECT "END_NUM" FROM  (SELECT "END_NUM" "END_NUM" FROM 
              "MY_TABLE" "MY_TABLE" WHERE "END_NUM">=1000000 ORDER BY "END_NUM") "from$_subquery$_004" 
              WHERE ROWNUM=1))
   3 - filter(ROWNUM=1)
   5 - access("END_NUM">=1000000)
   6 - filter(ROWNUM=1)
   8 - access("START_NUM"<=1000000)
于 2013-04-06T04:58:48.950 に答える
0

これは、オラクルをだまして競合他社のように振る舞わせようとするケースであり、オラクルにアクセスできないと私は推測しています。多分自己結合はこれを行うことができますか?各列に個別にインデックスを付けて、

SELECT t1.start
FROM my_table t1 JOIN my_table t2
ON t1.start=t2.start AND t2."end"=t1."end"
AND t1.start <= :1
AND t2.end >= :1

これはばかげているように見えますが、直接的な解決策は Joe Frambach のものです。私が持っているPostgresをだまして、インデックス検索のみを実行させます。

endところで、Postgres は列名として非常に不満です。実際のテーブルで予約語が使用されていないことを願っています。

于 2013-04-05T05:15:02.887 に答える