PostgreSQL LIKE 쿼리 성능 변화


112

LIKE내 데이터베이스의 특정 테이블에 대한 쿼리 와 관련하여 응답 시간이 상당히 많이 변하는 것을 보았습니다 . 때로는 200-400ms (매우 수용 가능) 내에 결과를 얻을 수 있지만 결과를 반환하는 데 30 초 정도 걸릴 수도 있습니다.

LIKE쿼리가 매우 리소스 집약적 이라는 것을 이해 하지만 응답 시간에 그렇게 큰 차이가있는 이유를 이해할 수 없습니다. owner1필드 에 btree 인덱스를 만들었지 만 LIKE쿼리에 도움이되지 않는다고 생각 합니다. 누구나 아이디어가 있습니까?

샘플 SQL :

SELECT gid, owner1 FORM parcels
WHERE owner1 ILIKE '%someones name%' LIMIT 10

나는 또한 시도했다 :

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%') LIMIT 10

과:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('someones name%') LIMIT 10

비슷한 결과가 있습니다.
테이블 행 수 : 약 95,000.

답변:


281

FTS는 지원하지 않습니다 LIKE

이전에 허용 대답은 잘못되었습니다. 전체 텍스트 인덱스가있는 전체 텍스트 검색 은 연산자를 위한 것이 아닙니다.LIKE 자체 연산자가 있으며 임의의 문자열에 대해서는 작동하지 않습니다. 사전 및 형태소 분석을 기반으로 하는 단어로 작동합니다 . 그것은 않습니다 지원 단어에 대한 접두사 일치를 에 있지만 LIKE운영자 :

Trigram 인덱스 LIKE

GIN 및 GiST 트라이 그램 인덱스pg_trgm대한 연산자 클래스를 제공 하는 추가 모듈 을 설치하여 왼쪽 앵커뿐만 아니라 모든 패턴 을 지원 합니다.LIKEILIKE

색인 예 :

CREATE INDEX tbl_col_gin_trgm_idx  ON tbl USING gin  (col gin_trgm_ops);

또는:

CREATE INDEX tbl_col_gist_trgm_idx ON tbl USING gist (col gist_trgm_ops);

쿼리 예 :

SELECT * FROM tbl WHERE col LIKE '%foo%';   -- leading wildcard
SELECT * FROM tbl WHERE col ILIKE '%foo%';  -- works case insensitively as well

트라이 그램? 짧은 문자열은 어떻습니까?

인덱싱 된 값 이 3 자 미만인 단어는 여전히 작동합니다. 매뉴얼 :

각 단어는 문자열에 포함 된 트라이 그램 집합을 결정할 때 접두사 2 개와 접미사 1 개 공백이있는 것으로 간주됩니다.

그리고 3 자 미만의 검색 패턴? 매뉴얼 :

LIKE정규식 검색과 정규식 검색 모두 에 대해 추출 가능한 트라이 그램이없는 패턴은 전체 인덱스 스캔으로 저하된다는 점에 유의하십시오.

즉, 인덱스 / 비트 맵 인덱스 스캔은 여전히 ​​작동하지만 (준비된 명령문에 대한 쿼리 계획은 중단되지 않음) 더 나은 성능을 얻을 수 없습니다. 일반적으로 1 자 또는 2 자 문자열은 선택성이 거의없고 (기본 테이블 일치의 몇 퍼센트 이상) 인덱스 지원으로 인해 전체 테이블 스캔이 더 빠르기 때문에 성능이 향상되지 않기 때문에 큰 손실이 없습니다.


text_pattern_ops 접두사 일치

단지를 들어 왼쪽 고정 된 패턴 당신은 적절한에 최적의 수 (더 와일드 카드를 선도 없음) 연산자 클래스 하십시오 BTREE 지수를 text_pattern_opsvarchar_pattern_ops. 표준 Postgres의 두 내장 기능 모두 추가 모듈이 필요하지 않습니다. 성능은 비슷하지만 인덱스가 훨씬 작습니다.

색인 예 :

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

쿼리 예 :

SELECT * FROM tbl WHERE col LIKE 'foo%';  -- no leading wildcard

또는 'C' 로케일로 데이터베이스를 실행해야하는 경우 (실제로 로케일 )로 어쨌든 모든 것이 바이트 순서에 따라 정렬되고 기본 연산자 클래스가있는 일반 btree 인덱스가 작업을 수행합니다.

dba.SE의 관련 답변에 대한 자세한 내용, 설명, 예제 및 링크 :


500K 줄의 테이블에 선행 와일드 카드가 없으면 gin_trgm_ops가있는 gin 인덱스가 btree보다 10 배 더 빠릅니다
nicolas

@nicolas : 비교는 많은 변수에 따라 달라집니다. 키 길이, 데이터 분포, 패턴 길이, 가능한 인덱스 만 스캔 ... 그리고 가장 중요한 것은 Postgres 버전입니다. GIN 지수는 9.4 페이지와 9.5 페이지에서 상당히 개선되었습니다. pg_trgm의 새 버전 (9.6 페이지와 함께 출시 예정)은 더 많은 개선을 가져올 것입니다.
Erwin Brandstetter

1
문서 가 맞으면 pg_trgm적어도 3 자 길이의 쿼리 문자열이 필요합니다. 예를 들어 fo%인덱스에 도달하지 않고 대신 스캔을 수행합니다. 주목할 점.
Tuukka Mustonen 2017

1
@TuukkaMustonen : 좋은 지적입니다. 글쎄, (비트 맵) 인덱스 스캔은 여전히 작동 하지만 더 나은 성능을 얻을 수는 없습니다. 위에 설명을 추가했습니다.
Erwin Brandstetter

7

아마도 빠른 것은 인덱스를 사용할 수있는 것과 같이 대소 문자를 구분하는 고정 된 패턴 일 것입니다. 즉, 일치 문자열의 시작 부분에 와일드 카드가 없으므로 실행 프로그램이 인덱스 범위 스캔을 사용할 수 있습니다. ( 문서의 관련 주석은 여기에 있습니다 ) Lower 및 ilike는 해당 목적을 위해 특별히 색인을 생성하지 않는 한 색인을 사용할 수있는 능력을 잃게됩니다 ( 기능 색인 참조 ).

필드 중간에있는 문자열을 검색하려면 전체 텍스트 또는 트라이 그램 인덱스를 살펴 봐야 합니다 . 첫 번째는 Postgres 코어에 있고 다른 하나는 contrib 모듈에서 사용할 수 있습니다.


필드의 소문자 값에 대한 인덱스를 만드는 것에 대해 생각하지 않았습니다. 이렇게하면 쿼리하기 전에 백엔드에서 쿼리 텍스트를 소문자로 변환 할 수 있습니다.
Jason

4

PostgreSQL에서 다른 유형의 인덱스 인 Wildspeed를 설치할 수 있습니다. Wildspeed는 % word % 와일드 카드와 함께 작동합니다. 문제 없습니다. 단점은 인덱스의 크기입니다. 이것은 매우 클 수 있습니다.


3

postgresql에서 LIKE 쿼리 성능을 향상시키기 위해 아래 언급 된 쿼리를 실행하십시오. 더 큰 테이블에 대해 다음과 같은 색인을 작성하십시오.

CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)

패턴이 와일드 카드로 시작하지 않는 경우에만 작동합니다.이 경우 처음 두 샘플 쿼리는 모두 와일드 카드로 시작됩니다.
cbz

1

그만한 가치 를 위해 Django ORM은 UPPER(text)모든 사람들 에게 사용하는 경향이 있습니다.LIKE 쿼리에 대소 문자를 구분하지 않는 .

색인을 추가하면 UPPER(column::text)다른 것과 달리 시스템 속도가 크게 빨라졌습니다.

선행 %까지는 인덱스를 사용하지 않습니다. 훌륭한 설명은이 블로그를 참조하십시오.

https://use-the-index-luke.com/sql/where-clause/searching-for-ranges/like-performance-tuning


1

최근에 200000 개의 레코드가 포함 된 테이블에서 비슷한 문제가 발생하여 LIKE 쿼리를 반복해야합니다. 제 경우에는 검색중인 문자열이 수정되었습니다. 다른 분야는 다양했습니다. 그 때문에 다시 쓸 수있었습니다.

SELECT owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%');

같이

CREATE INDEX ix_parcels ON parcels(position(lower('someones name') in lower(owner1)));

SELECT owner1 FROM parcels
WHERE position(lower('someones name') in lower(owner1)) > 0;

쿼리가 빠르게 돌아오고 인덱스가 EXPLAIN ANALYZE다음 과 함께 사용되는지 확인했을 때 기뻤습니다 .

 Bitmap Heap Scan on parcels  (cost=7.66..25.59 rows=453 width=32) (actual time=0.006..0.006 rows=0 loops=1)
   Recheck Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
   ->  Bitmap Index Scan on ix_parcels  (cost=0.00..7.55 rows=453 width=0) (actual time=0.004..0.004 rows=0 loops=1)
         Index Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
 Planning time: 0.075 ms
 Execution time: 0.025 ms

0

유사한 쿼리는 다음과 같은 이유로 만든 색인을 사용할 수 없습니다.

1) LIKE 기준은 와일드 카드로 시작합니다.

2) LIKE 기준으로 함수를 사용했습니다.


0

LIKE, ILIKE, upper, lower 등과 같은 함수가있는 열에 절을 사용할 때마다 postgres는 일반 인덱스를 고려하지 않습니다. 각 행을 통과하는 테이블의 전체 스캔을 수행하므로 속도가 느려집니다.

올바른 방법은 쿼리에 따라 새 색인을 만드는 것입니다. 예를 들어 대소 문자를 구분하지 않고 열을 일치시키고 내 열이 varchar 인 경우. 그러면 이렇게 할 수 있습니다.

create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);

마찬가지로 열이 텍스트 인 경우 다음과 같이합니다.

create index ix_tblname_col_upper on tblname (UPPER(col) text_pattern_ops);

마찬가지로 상위 기능을 원하는 다른 기능으로 변경할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.