2013-08-24 2 views
3

배경더 빠른 FTS4 쿼리 결과를 다른 테이블의 필드로 정렬하려면 어떻게합니까?

나는를 활용, SQLite는에 저장된 전자 메일 메시지의 몸 전체 텍스트 검색을 구현하고있어 환상적인 내장 FTS4 엔진. 정확히 예상하지 못한 부분이 있지만 쿼리 성능이 좋지 않습니다. 한 번 보자.

적용 나는 전체 코드에 대한 링크, 문제의 코드의 몇 가지 간단한 예제를주지

대표 스키마.

CREATE TABLE MessageTable (
    id INTEGER PRIMARY KEY, 
    internaldate_time_t INTEGER 
); 
CREATE INDEX MessageTableInternalDateTimeTIndex 
    ON MessageTable(internaldate_time_t); 

검색 가능한 텍스트가 FTS4 테이블에 추가됩니다 : 우리는 (풀 버젼 here을 여러 개의 파일 here, here 퍼져 등) 전자 메일 메시지에 대한 데이터를 저장하는 MessageTable있어

이름 MessageSearchTable (전체 버전 here) : 메시지에 대한 외부 키로 검색 테이블 행위에

CREATE VIRTUAL TABLE MessageSearchTable USING fts4(
    id INTEGER PRIMARY KEY, 
    body 
); 

id 표.

독자가이 테이블에 데이터를 삽입하는 연습으로 남겨 두겠습니다. (물론 개인 이메일은 제공 할 수 없습니다). 각 테이블에 26k 미만의 기록이 있습니다. 우리는 검색 결과를 검색 할 때

, 우리는 그들을 필요로

문제 쿼리는 그래서 우리는 가장 최근의 몇 가지 결과를 뽑아 수 internaldate_time_t으로 내림차순으로 정렬합니다. 내 컴퓨터에

SELECT id 
FROM MessageSearchTable 
JOIN MessageTable USING (id) 
WHERE MessageSearchTable MATCH 'a' 
ORDER BY internaldate_time_t DESC 
LIMIT 10 OFFSET 0 

, 내 이메일로를 통해 측정, 약 150 밀리 초 단위로 실행 : 예를 들면 다음과 같습니다 검색 쿼리 (전체 버전 here)의

time sqlite3 test.db <<<"..." > /dev/null 

150 밀리 것은 전혀 짐승입니다 쿼리가 있지만 단순한 FTS 조회 및 인덱싱 된 주문에 대해서는 부진합니다. ORDER BY을 생략하면 10 밀리 초 단위로 완료됩니다. 또한 실제 쿼리에는 하나 이상의 하위 선택이 있으므로 일반적으로 조금 더 많은 작업이 진행됩니다. 쿼리의 전체 버전은 약 600 밀리 초 안에 실행되며 이는 짐승의 영역에 속하므로 ORDER BY은 생략합니다. 사건은 500 밀리 세컨드를 면도했다. 내가 sqlite3 내부 통계를 켜고 쿼리를 실행하면

, 내가 선을주의 사항 :

docs about those stats의 내 해석이 맞다면 쿼리가 완전히 MessageTableInternalDateTimeTIndex를 사용하여 건너 뛰는 것처럼
Sort Operations:      1 

, 그것은 보인다. 이 곳 테이블을 걷고 있지만,의 지금은 그것을 무시 할 수처럼

Fullscan Steps:      25824 

는 소리 : 쿼리의 전체 버전은 라인을 가지고있다.

내가

을 발견했습니다 무엇 그래서 조금 것을 최적화 작업을 할 수 있습니다. 나는 INDEXED BY 확장자를 가진 우리의 인덱스를 사용하는 서브 선택 힘 SQLite는에 쿼리를 다시 정렬 할 수 있습니다 :

SELECT id 
FROM MessageTable 
INDEXED BY MessageTableInternalDateTimeTIndex 
WHERE id IN (
    SELECT id 
    FROM MessageSearchTable 
    WHERE MessageSearchTable MATCH 'a' 
) 
ORDER BY internaldate_time_t DESC 
LIMIT 10 OFFSET 0 

보라 보라, 실행 시간의 정식 버전 (약 100 밀리 초 300 밀리 초를 떨어졌다 쿼리, 실행 시간이 50 % 감소) 정렬 작업이보고되지 않습니다. 위와 같이 쿼리를 재구성하고 인덱스를 으로 강제 실행하지 않으면 여전히 정렬 작업이 수행됩니다 (아직 몇 밀리 초 밖에 안되었습니다). 따라서 SQLite는 강제로 처리하지 않으면 실제로 인덱스를 무시합니다. 그것.

나는 또한이 차이를 만들 것입니다 있는지 확인하기 위해 몇 가지 다른 것들을 시도했습니다,하지만 그들은하지 않았다 :

  • 가 명시 적으로 그리고없이 설명 here로 인덱스 DESCINDEXED BY
  • 나는이 순간에 기억할 수로 및 INDEXED BY
  • 아마 몇 가지 다른 일없이 명시 적으로하고 internaldate_time_t 않고, 인덱스에 id 열을 추가는 DESC을 주문

질문 여기

100 밀리 초는 여전히 간단한 FTS 조회 및 인덱스 순서로해야 것 같아 무엇에 대한 지독하게 느린 것 같다.

  • 여기 어떻게됩니까? 왜 손을 강제로 사용하지 않으면 명백한 색인을 무시합니까?
  • 가상 테이블과 일반 테이블의 데이터를 결합 할 때 몇 가지 제한이 있습니까?
  • 왜 아직도 상대적으로 느리고 다른 테이블의 필드에 정렬 된 FTS 일치를 얻으려면 다른 방법이 있습니까?

감사합니다.

답변

3

인덱스는 인덱싱 된 열의 값을 기반으로 테이블 행을 조회하는 데 유용합니다. 일단 테이블 행이 발견되면 다른 기준으로 인덱스에서 테이블 행을 검색하는 것이 효율적이지 않으므로 인덱스가 더 이상 유용하지 않습니다.

이 의미는 쿼리에 액세스 된 각 테이블에 둘 이상의 인덱스를 사용할 수 없다는 것을 의미합니다.

설명서도 참조하십시오 : Query Planning, Query Optimizer.


첫 번째 쿼리는 다음 EXPLAIN QUERY PLAN 출력이 있습니다

0 0 0 SCAN TABLE MessageSearchTable VIRTUAL TABLE INDEX 4: (~0 rows) 
0 1 1 SEARCH TABLE MessageTable USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) 
0 0 0 USE TEMP B-TREE FOR ORDER BY 

무엇 발생하는

  1. FTS의 지수가 일치하는 모든 MessageSearchTable 행을 찾는 데 사용되는입니다;
  2. 1. 각 행에 대해 일치하는 행을 찾으려면 MessageTable 기본 키 색인을 사용합니다.
  3. 2에서 찾은 모든 행이 임시 테이블로 정렬됩니다.
  4. 처음 10 개의 행이 반환됩니다.

두 번째 쿼리는 EXPLAIN 다음 쿼리 계획 출력이 있습니다

0 0 0 SCAN TABLE MessageTable USING COVERING INDEX MessageTableInternalDateTimeTIndex (~100000 rows) 
0 0 0 EXECUTE LIST SUBQUERY 1 
1 0 0 SCAN TABLE MessageSearchTable VIRTUAL TABLE INDEX 4: (~0 rows) 

무엇 발생하는

  1. FTS의 지수가 일치하는 모든 MessageSearchTable 행을 찾는 데 사용되는입니다;
  2. SQLite는 인덱스 순서로 MessageTableInternalDateTimeTIndex의 모든 항목을 처리하고 id 값이 1 단계에서 찾은 값 중 하나 일 때 행을 반환합니다. SQLite는 열 번째 열에서 중지됩니다.

이 쿼리에서는이 테이블의 행을 조회하는 데 다른 인덱스가 사용되지 않았기 때문에 (묵시적) 정렬을 위해 인덱스를 사용할 수 있습니다. 이 방법으로 색인을 사용하면 SQLite가 모두 항목을 거쳐 다른 조건과 일치하는 몇 개의 행을 조회해야합니다.

당신이 당신의 두 번째 쿼리에서 INDEXED BY 절을 생략하면 EXPLAIN 다음 쿼리 계획 출력을 얻을 : 그 조인과 서브 쿼리는 약간 다르게 처리됩니다 제외하고,

0 0 0 SEARCH TABLE MessageTable USING INTEGER PRIMARY KEY (rowid=?) (~25 rows) 
0 0 0 EXECUTE LIST SUBQUERY 1 
1 0 0 SCAN TABLE MessageSearchTable VIRTUAL TABLE INDEX 4: (~0 rows) 
0 0 0 USE TEMP B-TREE FOR ORDER BY 

기본적으로 첫 번째 쿼리와 동일합니다 .


테이블 구조를 사용하면 실제로 속도를 향상시킬 수 없습니다. 다음 세 가지 작업을하고있다 : MessageSearchTable 행을 찾는

  1. 을;
  2. MessageTable에서 해당 행을 조회합니다.
  3. 행을 MessageTable 값별로 정렬합니다.

색인에 관한 한, 2 단계와 3 단계는 서로 충돌합니다. 데이터베이스는 2 단계 (이 경우 정렬을 명시 적으로 수행해야 함) 또는 3 단계 (모든 경우 MessageTable 항목을 거쳐야 함)에 인덱스를 사용해야하는지 여부를 선택해야합니다.

메시지를 FTS 테이블의 일부로 만들고 지난 며칠 동안 만 검색하여 충분한 결과를 얻지 못할 경우 시간을 늘리거나 줄임으로써 FTS 검색에서 적은 수의 레코드를 반환 할 수 있습니다. .

+0

"쿼리에서 액세스 된 각 테이블에 둘 이상의 인덱스를 사용할 수 없습니다."- 그건 사실상 아닙니다. 사실입니다. [이 섹션] (http://www.sqlite.org/eqp.html#section_1_1)의 끝 부분을 참조하십시오. 그러나 내부적으로 두 개의 별도 쿼리를 실행하고 결과를 'UNION'하는 것으로 양보합니다. – chazomaticus

+0

"MessageSearchTable의 행 조회 중 세 가지 작업을 수행하고 있습니다 ..."- 목표는 기본 키만 검색하는 것이 아니라 _rows_를 검색하는 것입니다. 모든 인덱스는 커버 인덱스로 간주되어야합니다 (쿼리 [planning] (http://www.sqlite.org/queryplanner.html) 및 [최적화] (http : //www.sqlite. org/optoverview.html) docs), 따라서 _all_ 테이블 스캔은 피할 수 있어야합니다. – chazomaticus

+0

"메시지 시간을 FTS 테이블의 일부로 만듭니다."- FTS 테이블에 대한 나의 이해는 임의의 데이터를 추가 할 수 없으며 FTS 알고리즘에 의해 인덱싱 된 텍스트 일뿐입니다. 틀렸어? FTS 테이블에 열을 추가하고 주문할 수 있습니까? – chazomaticus

관련 문제