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_ops나 varchar_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.