인덱스와 제약#
속성 인덱스는 레이블의 jsonb 컬럼인 properties에서 키 하나를 읽는 표현식에 걸린 PostgreSQL btree입니다.
이 한 문장이 언제 인덱스가 도움이 되는지에 대해 거의 모든 것을 설명합니다. 플래너가 여러분의 조건을
그 표현식과 맞출 수 있을 때 정확히 도움이 됩니다. 맞출 수 없으면 전혀 도움이 되지 않습니다.
이 페이지는 어떤 조건이 그 자격을 갖추는지 실험으로 확인합니다. 답이 대칭적이지 않고 몇 가지는
뜻밖이므로, 아래의 표들이 이 페이지의 요점입니다. 인덱스가 디스크에서 어떻게 생겼는지는
그래프가 어떻게 저장되는가가
보여 줍니다. 전체 구문은 CREATE PROPERTY INDEX와
CREATE CONSTRAINT에 있습니다.
측정할 그래프#
예제 데이터셋에는 영화가 18편뿐이어서, 플래너가 테이블을 그냥 읽는 것보다 인덱스를 선호하기에는 너무 적습니다. 작업할 더 큰 레이블을 만들고, 페이지 끝에서 지웁니다:
CREATE TABLE seed_film AS
SELECT 'Film ' || lpad(g::text, 6, '0') AS title,
1900 + (g % 130) AS year,
(ARRAY['Action','Sci-Fi','Drama','Comedy'])[1 + (g % 4)] AS genre
FROM generate_series(1, 50000) g;
CREATE GRAPH idxlab;
SET graph_path = idxlab;
CREATE VLABEL film;
LOAD FROM seed_film AS r CREATE (:film {title: r.title, year: r.year, genre: r.genre});
CREATE PROPERTY INDEX ON film (title);
CREATE PROPERTY INDEX ON film (year);
ANALYZE idxlab.film;
ANALYZE는 인덱스만큼 중요합니다. 플래너가 통계를 갖고 있지 않은 인덱스는 플래너가 쓰기를
거절하는 인덱스입니다.
아래의 모든 실행 계획은 50,000행에서 플래너가 강요 없이 스스로 고른 것입니다. 논지를 만들려고 무언가를 끈 것은 없습니다.
인덱스가 처리할 수 있는 조건#
같음 비교가 다른 모든 것을 견주는 기준입니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.title = 'Film 000042' RETURN f.year;
QUERY PLAN
-------------------------------------------------------------
Index Scan using film_title_idx on film f
Index Cond: (properties.'title' = '"Film 000042"'::jsonb)
찾아봐야 할 단어는 Index Cond입니다. 그 조건이 인덱스 탐색이 됐다는 뜻입니다. 대신 Filter로
나타난 조건은 스캔이 이미 가져온 행에 대해 평가된 것입니다. 그것이 바로 인덱스가 피하려던
일입니다.
패턴 안의 속성 맵은 같은 같음 비교로 컴파일되고 같은 실행 계획을 받습니다:
EXPLAIN (COSTS OFF) MATCH (f:film {title: 'Film 000042'}) RETURN f.year;
QUERY PLAN
-------------------------------------------------------------
Index Scan using film_title_idx on film f
Index Cond: (properties.'title' = '"Film 000042"'::jsonb)
범위 조건은 됩니다. btree가 순서를 갖기 때문입니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.year >= 2025 AND f.year < 2030 RETURN f.title;
QUERY PLAN
----------------------------------------------------------------------------------------------------
Bitmap Heap Scan on film f
Recheck Cond: ((properties.'year' >= '2025'::jsonb) AND (properties.'year' < '2030'::jsonb))
-> Bitmap Index Scan on film_year_idx
Index Cond: ((properties.'year' >= '2025'::jsonb) AND (properties.'year' < '2030'::jsonb))
비교는 jsonb 자체의 순서로 이루어지고, 그 순서는 숫자를 수치로 정렬하므로 >= 2025는 뜻하는 대로
동작합니다. 그러나 jsonb는 값보다 타입을 먼저 정렬합니다. 문자열 "2025"를 담은 속성은 두 숫자
사이에 있지 않습니다.
리터럴로 된 IN 목록은 배열 탐색이 됩니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.title IN ['Film 000042', 'Film 000043'] RETURN f.year;
QUERY PLAN
---------------------------------------------------------------------------------------------
Index Scan using film_title_idx on film f
Index Cond: (properties.'title' = ANY ('{"\"Film 000042\"","\"Film 000043\""}'::jsonb[]))
인덱스가 처리할 수 없는 조건#
<>는 인덱스를 쓸 수 없습니다. “이 값 하나가 아닌 것”이 테이블의 대부분이라는 평범한 이유
때문입니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.year <> 2020 RETURN f.title;
QUERY PLAN
------------------------------------------------
Seq Scan on film f
Filter: (properties.'year' <> '2020'::jsonb)
사람들이 걸리는 것은 문자열 조건입니다. STARTS WITH는 접두 범위여야 할 것처럼 보이기
때문입니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.title STARTS WITH 'Film 00004' RETURN f.year;
QUERY PLAN
-------------------------------------------------------------------------
Seq Scan on film f
Filter: string_starts_with(properties.'title', '"Film 00004"'::jsonb)
그렇지 않습니다. STARTS WITH는 함수 호출인 string_starts_with로 컴파일되고,
properties.'title'에 걸린 btree에는 그 함수에 대한 연산자 클래스 항목이 없습니다. 그래서 인덱스
경로가 거부된 것이 아니라 아예 없습니다. enable_seqscan을 꺼도 인덱스 경로가 생기지 않습니다.
계획은 순차 스캔으로 남고 Disabled: true로 표시됩니다.
CONTAINS, ENDS WITH, =~도 똑같이 동작하며, 각각 자기 함수로 컴파일됩니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.title CONTAINS '00042' RETURN f.year;
QUERY PLAN
-----------------------------------------------------------------
Seq Scan on film f
Filter: string_contains(properties.'title', '"00042"'::jsonb)
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.title =~ '^Film 00004' RETURN f.year;
QUERY PLAN
--------------------------------------------------------------------
Seq Scan on film f
Filter: string_regex(properties.'title', '"^Film 00004"'::jsonb)
IS NULL과 IS NOT NULL도 승격되지 않은 속성에서는 스캔으로 되돌아갑니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.director IS NULL RETURN f.title;
QUERY PLAN
-------------------------------------------
Seq Scan on film f
Filter: (properties.'director' IS NULL)
그 이유는 알아 둘 만합니다. 승격 뒤에 IS NULL이 다르게 동작하는 까닭도 함께 알려 주기
때문입니다. properties에 director 키가 아예 없는 행에서는 properties.'director'가 null입니다. 그
키에 걸린 표현식 인덱스는 그런 행마다 null을 저장하므로, 인덱스를 거의 끝까지 읽어야 합니다.
승격된 컬럼에서는 같은 검사가 실제 컬럼에 대한 실제 IS NULL이고, btree가 그것에 곧바로
답합니다.
표#
title에 film_title_idx, year에 film_year_idx를 걸고 ANALYZE를 돌린 상태에서 50,000행으로
측정했습니다:
조건 |
인덱스 사용 |
|---|---|
|
예 — |
패턴 안의 |
예 — |
|
예 — |
|
예 — |
|
아니요 — |
|
아니요 — |
|
아니요 — |
|
아니요 — |
|
아니요 — |
|
아니요 — |
|
아니요 — |
|
예 — |
Cypher 조건에서 문자열 조건을 구해 주는 인덱스 종류는 없습니다. 그것들은 함수 호출이고, 어떤 연산자 클래스도 그것을 맡지 않습니다. 접두 검색이나 부분 문자열 검색이 필요하다면 전문 검색이 그 도구입니다. SQL에서 그 컬럼에 닿는 방법도 있습니다. 아래를 참고하십시오.
정렬#
인덱스는 같은 속성에 대한 ORDER BY를 처리해 줍니다. 그래서 “속성 기준 상위 N개” 쿼리에는 정렬이
전혀 필요하지 않습니다:
EXPLAIN (COSTS OFF) MATCH (f:film) RETURN f.title AS t ORDER BY t LIMIT 5;
QUERY PLAN
-------------------------------------------------
Limit
-> Index Scan using film_title_idx on film f
이것이 되는 이유는 정렬 키가 jsonb로 그려진 모습이 아니라 그 값 자체로 정렬되기 때문입니다. 인덱스가 담고 있지 않은 표현식으로 정렬하면 여전히 정렬 작업이 일어납니다.
복합 인덱스와 부분 인덱스#
속성 인덱스는 여러 키를 받으며, 가장 왼쪽 접두 규칙은 PostgreSQL의 것과 같습니다:
CREATE PROPERTY INDEX ON film (genre, year);
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.genre = 'Comedy' AND f.year >= 2025 RETURN f.title;
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on film f
Recheck Cond: ((properties.'genre' = '"Comedy"'::jsonb) AND (properties.'year' >= '2025'::jsonb))
-> Bitmap Index Scan on film_genre_year_idx
Index Cond: ((properties.'genre' = '"Comedy"'::jsonb) AND (properties.'year' >= '2025'::jsonb))
year만으로 하는 쿼리는 이것을 쓸 수 없고 year에 걸린 단일 키 인덱스로 돌아갑니다. 그러므로
SQL에서 하듯 같음 비교를 앞에 두는 순서로 키를 배치하십시오.
인덱스에 붙인 WHERE 절이 그것을 부분 인덱스로 만듭니다. 큰 레이블의 작고 자주 쓰이는 일부에
인덱스를 거는 방법이 이것입니다:
CREATE PROPERTY INDEX film_comedy_year_idx ON film (year) WHERE genre = 'Comedy';
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.genre = 'Comedy' AND f.year >= 2025 RETURN f.title;
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Bitmap Heap Scan on film f
Recheck Cond: ((properties.'year' >= '2025'::jsonb) AND (properties.'genre' = '"Comedy"'::jsonb))
-> Bitmap Index Scan on film_comedy_year_idx
Index Cond: (properties.'year' >= '2025'::jsonb)
genre 검사가 인덱스 조건에서 빠졌습니다. 이 인덱스의 모든 항목이 이미 코미디이기 때문이며, 그
검사는 재확인에만 남습니다. 이 인덱스는 year에 걸린 전체 인덱스의 4분의 1 크기입니다. 524,288
바이트 대 2,056,192 바이트입니다. 플래너는 다른 두 대안보다 이것을 골랐습니다.
CREATE PROPERTY INDEX는 USING btree | hash | gin | brin | gist | spgist, CONCURRENTLY,
IF NOT EXISTS, INCLUDE, 그리고 테이블스페이스도 받습니다. 모두 PostgreSQL에서와 같은 뜻입니다.
참고
CREATE PROPERTY INDEX (...) 안의 모든 맨 이름은 속성 키이며, properties라는 단어도
그렇습니다. CREATE PROPERTY INDEX ON film USING gin (properties)는 properties라는 이름의 속성에
인덱스를 거는 것이고, 그런 속성을 가진 영화는 없습니다. properties 자체에 인덱스를 걸려면 평범한 SQL을
쓰십시오 — CREATE INDEX film_bag_idx ON idxlab.film USING gin (properties) — 그리고 그 인덱스는
properties @> '{"genre": "Comedy"}' 같은 SQL 조건만 쓸 수 있다는 점에 유의하십시오. Cypher의 속성
읽기는 properties.'genre'로 컴파일되고, properties에 걸린 GIN 인덱스는 그것과 맞지 않습니다.
승격이 바꾸는 것#
속성을 승격하면 실제 타입이 있는 컬럼이 생기고, 그 뒤로는 모든 조건이 properties가 아니라 그 컬럼을
대상으로 컴파일됩니다. 왜 그리고 어떻게 하는지는 속성을 컬럼으로 승격하기가
다룹니다. 여기서 중요한 것은 그것이 위 표의 답을 양방향으로 바꾼다는 점입니다.
ALTER VLABEL film ADD COLUMN year int GENERATED;
ALTER VLABEL film ADD COLUMN title text GENERATED;
이미 갖고 있는 인덱스에 승격이 무슨 일을 하는지부터 봅니다. properties.'year'에 걸린 인덱스는
어떤 것과도 맞지 않게 됩니다. 더 이상 어떤 쿼리도 그 표현식으로 컴파일되지 않기 때문입니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.year >= 2025 AND f.year < 2030 RETURN f.title;
QUERY PLAN
----------------------------------------------
Seq Scan on film f
Filter: ((year >= 2025) AND (year < 2030))
인덱스는 여전히 거기 있고, 쓰기마다 여전히 유지되며, 이제 아무것도 그것을 쓰지 않습니다. 그러므로 승격은 항상 두 단계입니다. 승격하고, 그다음 컬럼에 인덱스를 다시 만듭니다.
속성 인덱스로 다시 만드는 것은 맨 키에 대해서는 잘 됩니다. 그것이 흔한 경우이고 아래의 경우입니다.
그 키에 대한 표현식은 다른 형태가 필요합니다. CREATE PROPERTY INDEX는 맨 키는 승격된 컬럼으로
컴파일하지만, 그 키에 대한 표현식은 jsonb properties로 컴파일하는데 그것은 쿼리가 읽는 대상이 아닙니다.
그런 인덱스는 컬럼에 대한 SQL로 쓰십시오. \dGi가 그것도 여전히 나열해 줍니다:
CREATE INDEX film_lower_idx ON idxlab.film (lower(title));
표현식 속성 인덱스는 properties에 걸린다를
참고하십시오. 표현식 속성 인덱스는 properties에 걸린다를
참고하십시오.
DROP PROPERTY INDEX film_year_idx;
DROP PROPERTY INDEX film_title_idx;
DROP PROPERTY INDEX film_genre_year_idx;
DROP PROPERTY INDEX film_comedy_year_idx;
CREATE PROPERTY INDEX ON film (year);
CREATE PROPERTY INDEX ON film (title);
레이블은 이제 이런 모양입니다. 새 인덱스는 표현식 인덱스가 아니라 보통 컬럼 인덱스입니다:
\d idxlab.film
Table "idxlab.film"
Column | Type | Collation | Nullable | Default
------------+---------+-----------+----------+-----------------------------------------------------------------------------------------------
id | graphid | | not null | graphid(graph_labid('idxlab.film'::cstring), nextval('idxlab.film_id_seq'::regclass))
properties | jsonb | | not null | jsonb_build_object()
year | integer | | | generated always as (ag_property_text(properties, 'year'::text, 'n'::"char")::integer) stored
title | text | | | generated always as (ag_property_text(properties, 'title'::text, 's'::"char")) stored
Indexes:
"film_pkey" PRIMARY KEY, btree (id)
"film_bag_idx" gin (properties)
"film_title_idx" btree (title)
"film_year_idx" btree (year)
Inherits: idxlab.ag_vertex
같음 비교와 범위 비교가 이제 jsonb가 아니라 네이티브 값을 비교합니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.title = 'Film 000042' RETURN f.year;
QUERY PLAN
---------------------------------------------
Index Scan using film_title_idx on film f
Index Cond: (title = 'Film 000042'::text)
IS NULL이 인덱스로 처리 가능해집니다. 전에는 그렇지 않았습니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.title IS NULL RETURN f.year;
QUERY PLAN
-------------------------------------------
Index Scan using film_title_idx on film f
Index Cond: (title IS NULL)
ORDER BY는 인덱스 스캔에서 인덱스 전용 스캔으로 나아집니다. 쿼리가 원하는 값이 인덱스 안에 있기
때문입니다:
EXPLAIN (COSTS OFF) MATCH (f:film) RETURN f.title AS t ORDER BY t LIMIT 5;
QUERY PLAN
------------------------------------------------------
Limit
-> Index Only Scan using film_title_idx on film f
그리고 두 조건은 나빠집니다. IN은 더 이상 인덱스를 쓰지 않습니다. jsonb 리스트와 비교하려고
컬럼이 다시 jsonb로 감싸이기 때문입니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.title IN ['Film 000042', 'Film 000043'] RETURN f.year;
QUERY PLAN
---------------------------------------------------------------------------------------------
Seq Scan on film f
Filter: (cypher_to_jsonb(title) = ANY ('{"\"Film 000042\"","\"Film 000043\""}'::jsonb[]))
STARTS WITH도 같은 방식으로 감싸이므로, 승격이 이것을 구해 주지도 않습니다:
EXPLAIN (COSTS OFF) MATCH (f:film) WHERE f.title STARTS WITH 'Film 00004' RETURN f.year;
QUERY PLAN
-----------------------------------------------------------------------------
Seq Scan on film f
Filter: string_starts_with(cypher_to_jsonb(title), '"Film 00004"'::jsonb)
그러나 그 컬럼은 실제 text 컬럼이므로 SQL이 실제 text 연산자로 그것에 닿을 수 있고, 그것은
진짜 접두 범위입니다:
EXPLAIN (COSTS OFF) SELECT count(*) FROM idxlab.film WHERE title LIKE 'Film 00004%';
QUERY PLAN
--------------------------------------------------------------------------------------
Aggregate
-> Index Only Scan using film_title_idx on film
Index Cond: ((title >= 'Film 00004'::text) AND (title < 'Film 00005'::text))
Filter: (title ~~ 'Film 00004%'::text)
자주 쓰는 키에 대한 접두 검색의 실용적인 답이 이것입니다. 속성을 승격하고 접두 조건을 SQL로 쓰되, Cypher 부분이 필요하면 그것을 감싸십시오. SQL과 Cypher 섞기를 참고하십시오.
승격 뒤의 표#
조건 |
승격 전(jsonb) |
승격 후(타입 컬럼) |
|---|---|---|
|
예 |
예 |
|
예 |
예 |
|
예 |
아니요 — jsonb로 감싸임 |
|
아니요 |
아니요 |
|
아니요 |
아니요 |
|
아니요 |
아니요 |
|
아니요 |
아니요 |
|
아니요 |
예 |
|
예 — 인덱스 스캔 |
예 — 인덱스 전용 스캔 |
SQL |
해당 없음 |
예 — 접두 범위 |
레이블 계층에 걸쳐서#
인덱스는 하나의 테이블에 속합니다. 부모 레이블에 대한 패턴은 부모와 그 아래의 모든 레이블을 스캔하고, 그 각각에는 자기 인덱스가 필요합니다. 확인할 계열을 하나 만들어 봅니다:
CREATE TABLE seed_work AS SELECT g FROM generate_series(1, 10000) g;
CREATE VLABEL work;
CREATE VLABEL short INHERITS (work);
CREATE VLABEL feature INHERITS (work);
LOAD FROM seed_work AS r CREATE (:short {code: 'S' || lpad((r.g)::text, 6, '0')});
LOAD FROM seed_work AS r CREATE (:feature {code: 'F' || lpad((r.g)::text, 6, '0')});
CREATE PROPERTY INDEX ON work (code);
ANALYZE idxlab.work; ANALYZE idxlab.short; ANALYZE idxlab.feature;
work에만 인덱스가 있고, 모든 자식은 전부 읽힙니다:
EXPLAIN (COSTS OFF) MATCH (w:work) WHERE w.code = 'F000042' RETURN w;
QUERY PLAN
----------------------------------------------------------------
Result
-> Append
-> Seq Scan on work w_1
Filter: (properties.'code' = '"F000042"'::jsonb)
-> Seq Scan on short w_2
Filter: (properties.'code' = '"F000042"'::jsonb)
-> Seq Scan on feature w_3
Filter: (properties.'code' = '"F000042"'::jsonb)
자식마다 인덱스를 걸면 Append의 모든 가지가 인덱스를 얻습니다:
CREATE PROPERTY INDEX ON short (code);
CREATE PROPERTY INDEX ON feature (code);
QUERY PLAN
--------------------------------------------------------------------
Result
-> Append
-> Seq Scan on work w_1
Filter: (properties.'code' = '"F000042"'::jsonb)
-> Index Scan using short_code_idx on short w_2
Index Cond: (properties.'code' = '"F000042"'::jsonb)
-> Index Scan using feature_code_idx on feature w_3
Index Cond: (properties.'code' = '"F000042"'::jsonb)
work 자신은 순차 스캔을 유지합니다. 자기 행이 없고, 빈 테이블을 읽는 것이 인덱스를 여는 것보다
값싸기 때문입니다. 그것이 옳은 판단입니다. 그리고 이것이 눈여겨봐야 할 모양이기도 합니다. 계열을
검색하는 속성마다, 계층은 레이블당 인덱스 하나의 비용을 영구히 요구합니다.
제약#
형태가 두 가지 있고, 그 아래는 PostgreSQL의 두 형태입니다.
ASSERT <key> IS UNIQUE는 그 키의 표현식에 배제 제약을 만듭니다:
CREATE VLABEL account;
CREATE CONSTRAINT account_email_uq ON account ASSERT email IS UNIQUE;
CREATE (:account {email: 'a@example.com', age: 30});
CREATE (:account {email: 'a@example.com', age: 31});
ERROR: conflicting key value violates exclusion constraint "account_email_uq"
DETAIL: Key ((properties.'email'))=("a@example.com") conflicts with existing key ((properties.'email'))=("a@example.com").
ASSERT <predicate>는 검사 제약을 만듭니다:
CREATE CONSTRAINT account_age_ck ON account ASSERT age > 0;
CREATE (:account {email: 'b@example.com', age: -1});
ERROR: new row for relation "account" violates check constraint "account_age_ck"
DETAIL: Failing row contains (7.3, {"age": -1, "email": "b@example.com"}).
둘 다 보통 제약으로 보이고, 고유성 제약은 그 표현식에 걸린 실제 btree가 뒤를 받치므로 그 키를 찾는 인덱스 역할도 겸합니다:
SELECT conname, pg_get_constraintdef(oid) FROM pg_constraint
WHERE conrelid = 'idxlab.account'::regclass AND contype IN ('c', 'x') ORDER BY conname;
conname | pg_get_constraintdef
------------------+---------------------------------------------------
account_age_ck | CHECK ((properties.'age' > cypher_to_jsonb(0)))
account_email_uq | EXCLUDE USING btree ((properties.'email') WITH =)
두 제약 모두 그 속성이 없는 정점에 대해서는 아무것도 말하지 않습니다. 없는 키는 null로 읽히고, null은 다른 null과 같지 않으며, 알 수 없는 값에 대한 검사 제약은 통과합니다:
CREATE (:account {email: 'c@example.com'});
CREATE (:account {age: 44});
CREATE (:account {age: 45});
MATCH (a:account) RETURN a.email AS email, a.age AS age ORDER BY email NULLS LAST, age;
email | age
-----------------+-----
"a@example.com" | 30
"c@example.com" |
| 44
| 45
이메일이 아예 없는 계정 둘이 아무 문제 없이 공존합니다. 어떤 키가 고유해야 하는 데다 반드시 있어야
한다면, null이 아님을 확인하는 제약을 하나 더 추가하십시오. properties가 그것을 대신 해 주지
않습니다.
CREATE UNIQUE PROPERTY INDEX가 고유성을 표기하는 또 다른 방법이며, 배제 제약이 아니라 고유
인덱스를 만듭니다:
CREATE VLABEL member;
CREATE UNIQUE PROPERTY INDEX ON member (card);
CREATE (:member {card: '0001'});
CREATE (:member {card: '0001'});
ERROR: duplicate key value violates unique constraint "member_card_idx"
DETAIL: Key ((properties.'card'))=("0001") already exists.
빠른 고유 조회에는 이쪽을 택하십시오. pg_constraint와 덤프에서 직접 정한 이름으로 보고 싶은
규칙이라면 이름 붙은 제약을 택하십시오. 둘 다 자식 레이블에 상속되지 않습니다. 부모에 걸린 제약은
부모 자신의 행만 제약합니다.
실제 작업 순서#
“왜 이게 느린가” 하는 질문 대부분에 답하는 순서입니다:
관련된 레이블에
ANALYZE를 돌리십시오. 간선 레이블도 포함합니다. 분석하지 않은 간선 레이블은 빠진 인덱스보다 비쌉니다. 쿼리 튜닝을 참고하십시오.패턴이 고정된 속성에 인덱스를 거십시오. 시작 정점을 골라 내는, 선택도가 높은 같음 비교 하나가 그것입니다. 그 뒤의 모든 것은 이미 존재하는 간선 인덱스를 통한 조인입니다.
그 조건이
Filter가 아니라Index Cond로 도착하는지EXPLAIN으로 확인하십시오.그러고 나서야 속성 승격을 검토하십시오. 승격은 탐색이 아니라 읽기 자체가 비용일 때의 답입니다.
정리#
DROP GRAPH idxlab CASCADE;
DROP TABLE seed_film;
DROP TABLE seed_work;
다음에 갈 곳#
속성을 컬럼으로 승격하기 — 위 이야기의 나머지 절반입니다.
쿼리 튜닝 — 실행 계획의 모양, 통계, 그리고 그것들을 바꾸는 설정입니다.
전문 검색 — 부분 문자열과 접두 일치에 대한, 인덱스로 처리되는 답입니다.