2010-05-19 6 views
5

SQL 인덱스를 사용하면 내 쿼리와 일치하는 문자열을 빠르게 찾을 수 있습니다. 이제 큰 테이블에서 이 아닌 문자열을 검색해야합니다.이 일치해야합니다. 물론, 일반 인덱스는 도움이되지 않습니다와 나는 느린 순차 검색 할 필요가 :"같지 않음"검색에 대한 SQL 인덱스

essais=> \d phone_idx 
Index "public.phone_idx" 
Column | Type 
--------+------ 
phone | text 
btree, for table "public.phonespersons" 

essais=> EXPLAIN SELECT person FROM PhonesPersons WHERE phone = '+33 1234567'; 
            QUERY PLAN         
------------------------------------------------------------------------------- 
Index Scan using phone_idx on phonespersons (cost=0.00..8.41 rows=1 width=4) 
    Index Cond: (phone = '+33 1234567'::text) 
(2 rows) 

essais=> EXPLAIN SELECT person FROM PhonesPersons WHERE phone != '+33 1234567'; 
           QUERY PLAN        
---------------------------------------------------------------------- 
Seq Scan on phonespersons (cost=0.00..18621.00 rows=999999 width=4) 
    Filter: (phone <> '+33 1234567'::text) 
(2 rows) 

을 이해 (마크 바이어스 '아주 좋은 설명을 참조)이 볼 때 PostgreSQL을 는 인덱스를 사용하지 않기로 결정할 수 순차 주사 이 더 빠를 것입니다 (예를 들어 거의 모든 튜플이 일치하는 경우). 그러나 여기에서 "같지 않음"검색은 실제로 느립니다.

"이와 동등하지 않습니다"검색을 더 빨리 수행 할 수있는 방법은 무엇입니까?

마크 바이어스 (Mark Byers)의 훌륭한 발언을 다루기위한 또 다른 예가 있습니다. 지수 ( 튜플의 대부분을 반환)가 '='쿼리에 사용하지만 쿼리됩니다 '! ='

essais=> \d tld_idx 
Index "public.tld_idx" 
    Column  | Type 
-----------------+------ 
pg_expression_1 | text 
btree, for table "public.emailspersons" 

essais=> EXPLAIN ANALYZE SELECT person FROM EmailsPersons WHERE tld(email) = 'fr'; 
          QUERY PLAN                
------------------------------------------------------------------------------------------------------------------------------------ 
Index Scan using tld_idx on emailspersons (cost=0.25..4010.79 rows=97033 width=4) (actual time=0.137..261.123 rows=97110 loops=1) 
    Index Cond: (tld(email) = 'fr'::text) 
Total runtime: 444.800 ms 
(3 rows) 

essais=> EXPLAIN ANALYZE SELECT person FROM EmailsPersons WHERE tld(email) != 'fr'; 
         QUERY PLAN              
-------------------------------------------------------------------------------------------------------------------- 
Seq Scan on emailspersons (cost=0.00..27129.00 rows=2967 width=4) (actual time=1.004..1031.224 rows=2890 loops=1) 
    Filter: (tld(email) <> 'fr'::text) 
Total runtime: 1037.278 ms 
(3 rows) 

는 DBMS는 PostgreSQL의 8.3입니다 (하지만 8.4으로 업그레이드 할 수 있습니다) .

답변

4

아마도이 작성 도움이 될 :

SELECT person FROM PhonesPersons WHERE phone < '+33 1234567' 
UNION ALL 
SELECT person FROM PhonesPersons WHERE phone > '+33 1234567' 

또는 단순히

SELECT person FROM PhonesPersons WHERE phone > '+33 1234567' 
             OR phone < '+33 1234567' 
PostgreSQL은 범위 작업의 선택도가 매우 높은 것으로 판단하고 인덱스에 대한 사용을 고려 할 수 있어야한다

그것.

not-equals 조건부를 만족시키기 위해 인덱스를 직접 사용할 수는 없지만, 계획 중에 위와 같지 않은 것을 다시 쓸 수 있다면 좋을 것입니다. 작동하는 경우 개발자에게 제안하십시오.)

이론적 설명 : 특정 값과 같지 않은 모든 값에 대해 인덱스를 검색하면 전체 인덱스를 스캔해야합니다.반대로 특정 키보다 작은 모든 요소를 ​​검색하는 것은 트리에서 가장 일치하지 않는 항목을 찾아 뒤로 검색하는 것을 의미합니다. 마찬가지로 특정 키보다 큰 모든 요소를 ​​반대 방향으로 검색합니다. 이러한 작업은 b-tree 구조를 사용하여 쉽게 수행 할 수 있습니다. 또한 PostgreSQL이 수집하는 통계는 "+33 1234567"이 알려진 빈번한 값임을 지적 할 수 있어야합니다 : 1에서 해당 빈도와 null의 빈도를 제거하면 선택할 행의 비율이 있습니다 : 히스토그램 경계는 그것들이 한쪽 편으로 기울어 져 있는지 아닌지를 가리킨다. 그러나 null 값을 제외하고 빈번한 값이 행의 비율을 충분히 낮게 유지한다면 (Istr 약 20 %) 인덱스 스캔이 적절해야합니다. 실제로 계산 된 비율을 보려면 pg_stats의 열에 대한 통계를 확인하십시오.

업데이트 : 나는이 방법을 로컬 테이블에서 모호하게 비슷한 분포로 시도했으며 위의 두 가지 형식 모두 일반 seq 스캔 이외의 것을 생성했습니다. 후자 ("OR"사용)는 비트 맵 스캔이었는데, 일반적인 가치에 대한 편향이 특히 극단적 인 경우 seq 스캔이 될 수 있습니다. 플래너가이를 볼 수는 있지만 자동으로 실행되지는 않습니다. 내부적으로 "추가 (색인 스캔, 색인 스캔)"로 다시 작성하십시오. "enable_bitmapscan"을 해제하면 seq 스캔으로 되돌아갑니다.

PS : 텍스트 열을 인덱싱하고 데이터베이스 위치 C. 아닌 경우 문제가, 당신은 text_pattern_ops를 사용하거나 varchar_pattern_ops 별도의 인덱스를 추가해야 할 수 있습니다 불평등 연산자를 사용; 이는 column LIKE 'prefix%' 술어에 대한 색인화 문제와 유사합니다.

대체 :

CREATE INDEX PhonesPersonsOthers ON PhonesPersons(phone) WHERE phone <> '+33 1234567' 

이이 <> 그냥 그 부분 인덱스를 스캔 SELECT 문을 - 특수 것 : 당신은 부분 인덱스 만들 수는 테이블에있는 항목의 대부분을 제외 이후를, 그것은 작아야합니다.

+0

"<>"을 (를) "< OR >"(으)로 다시 작성하는 아이디어를 테스트 한 결과 작동했습니다. EXPLAIN은 인덱스가 사용되고 성능이 많이 향상됨을 보여줍니다. 나는 더 많은 시험을하고 당신의 대답을 받아 들일 것입니다. 질문 : PostgreSQL이이 재 작성 자체를 할 수없는 이유는 무엇입니까? – bortzmeyer

+2

@bortzmeyer 아마도 운영자 시스템이 너무 일반적이기 때문에 "="/ "<>"쌍의 연산자를 "<" and ">"과 연관시키는 방법이 필요합니다. PostgreSQL 목록에 기능으로 제안하는 것이 좋습니다. – araqnid

+0

OK, 괜찮습니다. 감사합니다. 작은 경고 : 모든 PostgreSQL 인덱스가 순서를 가지고 있지는 않습니다. http://www.postgresql.org/docs/current/interactive/indexes-types.html – bortzmeyer

5

데이터베이스는이 쿼리에 대해 인덱스를 사용할 수 있지만 데이터베이스가 느리기 때문에 선택하지 않습니다. 업데이트 : 이것은 정확하지 않습니다. 쿼리를 약간 재 작성해야합니다. Araqnid의 답변을 참조하십시오.

where 절은 테이블의 거의 모든 행을 선택합니다 (rows = 999999). 데이터베이스는이 경우 테이블 스캔이 더 빨라 지므로 인덱스를 무시한다는 것을 알 수 있습니다. 인덱스 person이 색인에 없기 때문에 WHERE 절을 검사하기위한 색인에서 한 번, 그리고 주 표에서 person을 다시 가져 오기 위해 각 행에 대해 두 개의 조회를 만들어야하므로 더 빠릅니다.

당신은 가장 값이 foo했다 단지 몇이 bar했다 당신이 WHERE col <> 'foo' 그때 아마 인덱스를 사용하는 것이라고 말했다 있었다 다른 데이터 유형을 가지고 있다면.

"이와 동등하지 않습니다"검색을하는 방법은 더 빠릅니까?

거의 백만 개의 행을 선택하는 쿼리는 느려질 것입니다. 제한 절을 추가하십시오.

+0

좋아, 나는 DBMS가 나보다 더 똑똑하다는 것을 잊어 버리고 때로는 의도적으로 색인을 사용하지 않기로 결정한다. 그것은 심지어 쿼리의 실제 값에 의존합니다. 비록 채워진 특수 데이터베이스가 있더라도 인덱스를 사용하는 NOT 쿼리를 아직 가질 수 없었습니다. 단 80 줄만 선택하더라도 PostgreSQL은 연속 스캔을 사용합니다. – bortzmeyer

+0

결국 araqnid의 솔루션 ("< OR >"에서 "<>"다시 작성)을 사용하고 해결책을 수락했습니다. 감사. – bortzmeyer

+0

@bortzmeyer : 알았어 주셔서 감사합니다. –