느슨한 인덱스 스캔 또는 PostgreSQL의 스킵 스캔 🐘🚀
23727 단어 skipscanlooseindexyugabytedbpostgres
다음은 올바른 색인이 있을 때 가장 최근 레코드(많은 항목)에 대한 가장 빠른 답변을 위해 추가 열로 작업하는 내 버전입니다.
이전 게시물(및 Ryan Booz 기사)과 동일한 테이블을 만들고 있습니다.
drop table truck_readings;
create table truck_readings (
truck_id bigint
,ts timestamptz
,milage int
,primary key(truck_id, ts)
);
여기서는 PostgreSQL과 호환되는 오픈 소스 분산 SQL 데이터베이스인 YugabyteDB를 사용하고 있습니다. 구문은 동일하지만 행은 기본적으로 첫 번째 열의 해시에 의해 배포됩니다.
몇 개의 행을 삽입합니다. 2,000대의 트럭 각각에 대해 10,000개의 판독값입니다.
with trucks as (select generate_series(1,2000) truck_id)
insert into truck_readings(truck_id, ts, milage)
select truck_id, now() -
interval '1 second' * generate_series(1,10000)
, 42 from trucks;
analyze truck_readings;
이 시점에서 각 트럭에 대한 마지막 판독값을 얻기 위해 이전 게시물과 동일한 솔루션이 있습니다. 트럭 목록이 도움이 되며 트리거로 마지막 값을 유지하는 것이 하나의 효율적인 솔루션입니다.
이 게시물에서는 Index Skip Scan과 유사한 기술을 사용하지만 WITH RECURSIVE 절에 의해 SQL에서 구동됩니다. 분산되지 않은 PostgreSQL이나 YugabyteDB에서는 범위별로 샤딩할 때 다른 인덱스가 필요하지 않습니다. 그러나 해시 샤딩이거나 단순히 생성된 기본 키가 있는 경우에는 보조 인덱스가 필요합니다. 여기있어:
create index truck_last_reading on truck_readings
( truck_id asc, ts asc) include (milage);
이 인덱스는 먼저
truck_id
로 정렬된 다음 타임스탬프인 ts
로 정렬됩니다. 여기에는 필요한 모든 열이 포함됩니다. 여기에서 각 트럭의 마지막 판독값에 대한 마일리지를 원합니다.내 목표는 인덱스 끝에서 시작하여 마지막 트럭에 대한 마지막 판독값을 갖는 것입니다.
yugabyte=#
select
last_truck_last_reading.truck_id,
last_truck_last_reading.milage
from truck_readings last_truck_last_reading
order by
last_truck_last_reading.truck_id desc,
last_truck_last_reading.ts desc
limit 1;
truck_id | milage
----------+--------
2000 | 42
(1 row)
그런 다음 다음 항목으로 건너뜁니다.
yugabyte=#
select truck_id,milage from truck_readings
where truck_id < 2000
order by truck_id desc, ts desc limit 1;
truck_id | milage
----------+--------
1999 | 42
조인과 결합하여 이전 항목에서
truck_id
를 가져올 수 있습니다.with truck_last_reading as (
select
last_truck_last_reading.truck_id,
last_truck_last_reading.milage
from truck_readings last_truck_last_reading
order by
last_truck_last_reading.truck_id desc,
last_truck_last_reading.ts desc
limit 1
)
select
next_truck_last_reading.truck_id,
next_truck_last_reading.milage
from truck_last_reading, lateral
(
select truck_id,milage from truck_readings
where truck_id < truck_last_reading.truck_id
order by truck_id desc, ts desc limit 1
)
as next_truck_last_reading;
재귀 ... 측면으로
마지막으로 UNION ALL을 사용하여 두 행을 모두 표시할 수 있습니다. 그리고 이것을 재귀적으로 수행하여 다음
truck_id
을 찾고 결과에 연결합니다.with recursive truck_last_reading as (
(
select
last_truck_last_reading.truck_id,
last_truck_last_reading.milage
from truck_readings last_truck_last_reading
order by
last_truck_last_reading.truck_id desc,
last_truck_last_reading.ts desc
limit 1
)
union all
select
next_truck_last_reading.truck_id,
next_truck_last_reading.milage
from truck_last_reading, lateral
(
select truck_id,milage from truck_readings
where truck_id < truck_last_reading.truck_id
order by truck_id desc, ts desc limit 1
)
as next_truck_last_reading
) select * from truck_last_reading
order by truck_id,milage;
이것은 필요한 결과를 제공하는 최종 쿼리입니다(각각
ts
에 대해 truck_id
순서로 마지막 읽기 및 milage
반환). 실행 계획이 최적입니다.,
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort (actual time=1078.569..1078.661 rows=2000 loops=1)
Output: truck_last_reading.truck_id, truck_last_reading.milage
Sort Key: truck_last_reading.truck_id, truck_last_reading.milage
Sort Method: quicksort Memory: 142kB
CTE truck_last_reading
-> Recursive Union (actual time=0.872..1075.442 rows=2000 loops=1)
-> Subquery Scan on "*SELECT* 1" (actual time=0.871..0.872 rows=1 loops=1)
Output: "*SELECT* 1".truck_id, "*SELECT* 1".milage
-> Limit (actual time=0.871..0.871 rows=1 loops=1)
Output: last_truck_last_reading.truck_id, last_truck_last_reading.milage, last_truck_last_reading.ts
-> Index Only Scan Backward using truck_last_reading on public.truck_readings last_truck_last_reading (actual time=0.870..0.870 rows=1 loops=1)
Output: last_truck_last_reading.truck_id, last_truck_last_reading.milage, last_truck_last_reading.ts
Heap Fetches: 0
-> Nested Loop (actual time=0.525..0.526 rows=1 loops=2000)
Output: truck_readings.truck_id, truck_readings.milage
-> WorkTable Scan on truck_last_reading truck_last_reading_1 (actual time=0.000..0.000 rows=1 loops=2000)
Output: truck_last_reading_1.truck_id, truck_last_reading_1.milage
-> Limit (actual time=0.524..0.525 rows=1 loops=2000)
Output: truck_readings.truck_id, truck_readings.milage, truck_readings.ts
-> Index Only Scan Backward using truck_last_reading on public.truck_readings (actual time=0.513..0.513 rows=1 loops=2000)
Output: truck_readings.truck_id, truck_readings.milage, truck_readings.ts
Index Cond: (truck_readings.truck_id < truck_last_reading_1.truck_id)
Heap Fetches: 0
-> CTE Scan on truck_last_reading (actual time=0.874..1077.252 rows=2000 loops=1)
Output: truck_last_reading.truck_id, truck_last_reading.milage
Planning Time: 0.165 ms
Execution Time: 1078.821 ms
Peak Memory Usage: 337 kB
(28 rows)
각 반복에 대해 하나의 행만 읽히므로 작업 영역 메모리가 필요하지 않습니다. 이것은 최상의 사용을 위해 색인을 사용합니다. 각
Index Only Scan
에 대해 하나의 truck_id
, 먼저 truck_id
의 목록을 가져올 필요가 없습니다. 인덱스에서 읽을 때마다 올바른 위치를 직접 찾고 한 행만 읽습니다. YugabyteDB는 스토리지 태블릿 서버에 LIMIT 1을 푸시합니다.이론적으로 이 액세스는
truck_id
에 대한 해시 샤딩과 값이 아닌 해시에 대한 정렬을 통해 가능해야 합니다. 목표는 다음 truck_id
에 도달하는 것입니다. 그런 다음 order by yb_hash_code(truck_id) desc, truck_id desc, ts desc
는 구현이 없는 동일한 비트를 사용할 수 있습니다. 필요한 경우 git 문제를 여십시오. 그러나 primary key (truck_id desc, ts desc)
로 테이블을 선언하고 사전 분할하거나 테이블이 커지면 자동 분할하도록 할 수도 있습니다. 2.15.0에는 issue이 있기 때문에 버전 2.15.1에서 이 예제를 실행했습니다.
Reference
이 문제에 관하여(느슨한 인덱스 스캔 또는 PostgreSQL의 스킵 스캔 🐘🚀), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/yugabyte/loose-index-scan-aka-skip-scan-in-postgresql-1jfo텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)