오라클은 긴 키에 고유 인덱스를 사용하지 않습니다


16

테스트 데이터베이스에 250K 개의 행이있는 테이블이 있습니다. (생산에는 수억 대가 있으며, 동일한 문제를 관찰 할 수 있습니다.)이 테이블에는 고유 인덱스가있는 nvarchar2 (50) 문자열 식별자가 null이 아니라 (PK가 아님) 있습니다.

식별자는 테스트 데이터베이스에서 8 개의 다른 값을 가진 첫 번째 부분 (및 프로덕션에서 약 천 개), @ 기호 및 마지막으로 1-6 자릿수의 숫자로 구성됩니다. 예를 들어 'ABCD_BGX1741F_2006_13_20110808.xml @'로 시작하는 5 만 개의 행이있을 수 있으며 그 뒤에 5 만 개의 다른 숫자가옵니다.

식별자를 기반으로 단일 행을 쿼리하면 카디널리티가 1로 추정되고 비용이 매우 낮아서 잘 작동합니다. IN 표현식 또는 OR 표현식에 여러 식별자가있는 둘 이상의 행을 쿼리하면 인덱스에 대한 추정이 완전히 잘못되어 전체 테이블 스캔이 사용됩니다. 힌트를 사용하여 인덱스를 강제 실행하면 매우 빠릅니다. 전체 테이블 스캔은 실제로 훨씬 느린 속도로 실행됩니다 (생산 속도가 훨씬 느립니다). 최적화 문제입니다.

테스트로, 동일한 DDL과 동일한 내용으로 동일한 스키마 + 테이블 스페이스에서 테이블을 복제했습니다. 좋은 측정을 위해 첫 번째 테이블에서 고유 인덱스를 다시 작성하고 복제 테이블에서 정확히 동일한 인덱스를 작성했습니다. 나는했다 DBMS_STATS.GATHER_SCHEMA_STATS('schemaname',estimate_percent=>100,cascade=>true);. 색인 이름이 연속적임을 알 수도 있습니다. 이제 두 테이블의 유일한 차이점은 첫 번째 테이블은 디스크에 흩어져있는 블록 (다른 여러 큰 테이블과 함께 테이블 공간에 있음)과 함께 오랜 시간 동안 임의의 순서로로드되고 두 번째 테이블은 배치 된 것으로로드 된 것입니다 삽입 선택. 그 외에는 어떤 차이도 상상할 수 없습니다. (원래 테이블은 마지막 큰 삭제 이후 축소되었으며 그 이후에는 단일 삭제가 없었습니다.)

병자와 복제 테이블에 대한 쿼리 계획은 다음과 같습니다 (검은 브러시 아래의 문자열은 그림 전체에서 동일하며 회색 브러시 아래에 있습니다).

쿼리 계획

(이 예에는 검정색 브러시로 표시된 식별자로 시작하는 1867 개의 행이 있습니다. 2 행 쿼리는 1867 * 2의 카디널리티를 생성하고 3 행 쿼리는 1867 * 3의 카디널리티를 생성합니다. 우연의 일치, 오라클은 식별자의 끝을 신경 쓰지 않는 것 같습니다.)

이 문제의 원인은 무엇입니까? 프로덕션 환경에서 테이블을 재생성하는 것은 비용이 많이 듭니다.

USER_TABLES : http://i.stack.imgur.com/nDWze.jpg USER_INDEXES : http://i.stack.imgur.com/DG9um.jpg 스키마와 테이블 스페이스 이름 만 변경했습니다. 테이블 및 인덱스 이름이 쿼리 계획 스크린 샷과 동일 함을 알 수 있습니다.

답변:


7

(이것은 히스토그램이 다른 이유 에 대한 다른 질문에 대답합니다 .)

히스토그램은 기본적으로 열 왜곡 열이 관련 술어에서 사용되었는지 여부 에 따라 작성됩니다 . DDL과 데이터를 복사하는 것만으로는 충분하지 않으며 워크로드 정보도 중요합니다.

성능 조정 안내서 에 따르면 :

테이블을 삭제하면 자동 히스토그램 수집 기능에서 사용 된 워크로드 정보와 RESTORE _ * _ STATS 프로 시저에서 사용하는 저장된 통계 히스토리가 유실됩니다. 이 데이터가 없으면 이러한 기능이 제대로 작동하지 않습니다.

예를 들어, 다음은 기울어 진 데이터는 있지만 히스토그램은없는 테이블입니다.

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
NONE

같은 것을 실행하지만 통계를 수집하기 전에 쿼리를 수행하면 히스토그램이 생성됩니다.

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
select count(*) from test1 where a = sysdate; --Only new line
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
FREQUENCY

2
훌륭하게 간단한 예. CBO가 1을 가정하는 대신 고유 스캔에서 카디널리티 추정에 히스토그램을 사용하는 이유를 알고 있습니까?
Jack Douglas

감사! 내 블로그에서 joco.name/2014/01/05/…
fejesjoco

@ 잭 게으름이라고 생각합니다. Oracle 엔지니어는 고유 인덱스의 통계가 행과 동일한 수의 고유 값을 가지므로 1 카디널리티 가정은 고정 된 것이 아니라 다른 경우와 같이 통계에서 간단히 사용됩니다. 또한 일반적인 경우 히스토그램은 간단한 통계보다 우선합니다. 긴 키 때문에 내 경우가 매우 특별 해 보이지만, 그렇지 않으면 꽤 잘 작동한다고 생각합니다.
fejesjoco

@fejesjoco JL의 설명 은 히스토그램이 단일 조회 ()가없는 경우 일반적인 통계보다 우선하기 때문에 더 가능성이 있다고 생각 in하지 않습니까? CBO는 카디널리티 1을 가정하지만 가장 간단한 경우에만 가정합니다. 나는 당신이 큰 것을 사용하여 전체를 해결할 수 있다고 생각 UNION ALL하지만 그렇게하지 않는 다른 이유가있을 수 있으며 JL은 링크 된 블로그 게시물에서 다른 가능한 해결 방법을 언급합니다.
잭 더글러스

1
고려해야 할 또 다른 사소한 신비-이 히스토그램은 처음에 어떻게 만들어 졌습니까? 오라클은 열이 중복 된 경우에만 열이 비뚤어진 것으로 간주합니다. 누군가가 의도적으로이 히스토그램을 작성 했습니까? 아니면 권장하지 않는 통계를 수집 했 method_opt=>'for all indexed columns'습니까?
Jon Heller

8

해결책을 찾았습니다! 너무 아름다워서 실제로 Oracle에 대해 많은 것을 배웠습니다.

한마디로 히스토그램.

오라클의 CBO가 어떻게 작동하는지에 대해 많은 것을 읽기 시작했고 히스토그램을 발견했습니다. 나는 완전히 이해하지 못해서 USER_HISTOGRAMS 테이블을 보았고 voilá를 보았습니다. 아픈 테이블에는 몇 개의 행이 있었고 실제로는 복제 된 테이블에는 아무것도 없었습니다. 아픈 테이블의 경우 8 개의 서로 다른 식별자 시작 부분 각각에 대해 하나의 행이있었습니다. 그리고 이것이 핵심입니다 : @ 기호 앞에 32 문자로 잘 렸습니다. 내가 말했듯이, 키의 첫 번째 부분은 매우 반복적이며 @ 기호 다음에 달라집니다.

히스토그램은 고유 인덱스가 주어진 값에 대해 항상 0 또는 1의 카디널리티를 가지고 있다는 단순한 사실보다 더 강력 할 수 있습니다. 2+ 행을 쿼리 할 때 Oracle은 히스토그램을 살펴보면서 해당 식별자 시작 부분에 대해 수만 개의 값이있을 수 있다고 생각하고 CBO를 버렸습니다.

이전 테이블에서 해당 열의 히스토그램을 삭제했는데 문제가 해결되었습니다!

더 읽기 : https://blogs.oracle.com/optimizer/entry/how_do_i_drop_an_existing_histogram_on_a_column_and_stop_the_auto_stats_gathering_job_from_creating


2
나는 우리의 대화방에서 언급했다 :) chat.stackexchange.com/transcript/message/12987649#12987649
Philᵀᴹ

나는 그것을 보지 못했다 :). 그래서 유일한 이상한 점은 첫 번째 테이블에 히스토그램이 있고 클론이 아닌 이유입니다. gather_schema_stats가 모든 것을 업데이트했다고 생각했습니다.
fejesjoco

6

나는 이것에 대해 Jonathan Lewis에게 이메일을 보냈고 매우 유용한 답변을 얻었습니다.

계산의 기이함은 문자 기반 히스토그램에 대한 제한의 결과입니다.

http://jonathanlewis.wordpress.com/2010/10/13/frequency-histogram-5/ http://jonathanlewis.wordpress.com/2010/10/19/frequency-histograms-6/

예제를 살펴보면 쿼리는 단일 행이 아닌 IN 목록에 대한 것이므로 초기 추측은 옵티마이 저가 특수 사례 코드를 사용하는 대신 여러 행 선택성을 계산하는 일반적인 전략을 사용했을 것입니다. 기본 키의 IN 목록. 나는 그들이이 사건을 인식하기가 너무 어렵지 않을 것이라고 생각하지만, 개발자는 아마도 그만한 가치가 있다고 생각하지 않았을 것입니다.

링크 된 블로그 게시물을 읽는 것이 좋습니다. 다음은 실행중인 히스토그램의 제한 사항에 대해 자세히 설명합니다.

결론 : 빈도 히스토그램 (예 : 매우 설명적인 상태 열)에 적합한 후보 인 열에 상당히 길고 유사한 문자열이있는 경우 매우 드문 값이 매우 인기있는 것으로 보이는 경우 문제가 있습니다 처음 32 자까지 값을 입력하십시오. 유효한 값 목록을 변경하는 것이 유일한 해결책이라는 것을 알 수 있습니다 (가상 열 또는 함수 기반 인덱스와 관련된 다양한 전략이 문제를 우회 할 수 있음).


슬프게도 히스토그램은 약간 알려진 기능인 것 같습니다. SQL 개발자에게는 너무 깊고 대부분의 시간 동안 일하는 것이기 때문에 많은 리소스가 있다는 것을 아는 것이 좋습니다. 올바른 장소 :). 오라클이 32 바이트를 줄이고 그에 따라 비참한 결정을 내리는 것은 꽤 나쁩니다. 운 좋게도 히스토그램을 삭제하는 것은 완벽한 해결책입니다. 키 값은 고유하며 항상 한 번에 20 개의 값을 찾고 인덱스에서만 잘 작동하며 결정적입니다. 그러나 나는 다음에 긴 키를 사용하지 않을 것입니다.
fejesjoco

히스토그램은 DBA들 사이에서 꽤 잘 알려져 있습니다.;) 나는 당신이 더 깊은 것을 배우고 싶어한다는 것을 좋아하고 실제로 JL의 책을 읽어야한다고 생각합니다 . 매우 훌륭합니다. CBO는 일반적으로 훌륭한 업무를 수행합니다. 항상 조사해야하는 최첨단 사례가 있지만 중단이 없어도 추정치는 항상 추정치라는 점을 명심해야합니다.
Jack Douglas

1
정기적 인 통계 작업 (예 : Oracle이 새로 설치에서 기본적 으로 실행되는 작업)을 실행하는 경우 히스토그램이 다시 나타날 수 있습니다 (예 : LOCK_TABLE_STATS 등 )
Jack Douglas

내 대답에 블로그 게시물을 언급했는데 열의 히스토그램을 방지하는 방법에 대한 지침이 있습니다.
fejesjoco

1
@ 잭 더글러스, J. Lewis와 함께 해주셔서 감사합니다.
Dimitre Radoulov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.