쿼리 실행 계획

소개

이 페이지는 쿼리 실행 계획에 대한 개념과 더불어 Cloud Spanner가 분산 환경에서 쿼리를 수행하는 데 쿼리 실행 계획을 어떻게 사용하는지 설명합니다. Cloud Console을 사용하여 특정 쿼리의 실행 계획을 검색하는 방법은 Cloud Spanner의 쿼리 실행 방법 이해를 참조하세요.

Cloud Spanner는 선언적 SQL 구문을 사용하여 데이터베이스를 쿼리합니다. SQL 구문은 결과를 얻는 방법을 지정하지 않고 사용자가 원하는 대상을 정의합니다. 쿼리 실행 계획은 결과가 얻어지는 방식에 대한 단계를 나타냅니다. 주어진 SQL 구문에서 결과를 얻는 방법에는 여러 가지가 있을 수 있습니다. Cloud Spanner 쿼리 최적화 도구는 서로 다른 실행 계획을 평가하고 가장 효율적으로 간주되는 항목을 선택합니다. 그러면 Cloud Spanner가 이 실행 계획을 사용하여 결과를 검색합니다.

개념적으로, 실행 계획은 관계 연산자 트리입니다. 각 연산자는 입력에서 행을 읽고 출력 행을 생성합니다. 실행 루트에서 연산자 결과는 SQL 쿼리 결과로 반환됩니다.

예를 들어, 쿼리는 다음과 같습니다.

SELECT s.SongName FROM Songs AS s;

다음과 같이 시각화할 수 있는 쿼리 실행 계획을 생성합니다.

쿼리 실행 계획 예시

이 페이지의 쿼리 및 실행 계획은 다음 데이터베이스 스키마를 기반으로 합니다.

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
  BirthDate  DATE,
) PRIMARY KEY(SingerId);

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

CREATE TABLE Albums (
  SingerId        INT64 NOT NULL,
  AlbumId         INT64 NOT NULL,
  AlbumTitle      STRING(MAX),
  MarketingBudget INT64,
) PRIMARY KEY(SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);

CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);

CREATE TABLE Songs (
  SingerId  INT64 NOT NULL,
  AlbumId   INT64 NOT NULL,
  TrackId   INT64 NOT NULL,
  SongName  STRING(MAX),
  Duration  INT64,
  SongGenre STRING(25),
) PRIMARY KEY(SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC), INTERLEAVE IN Albums;

CREATE INDEX SongsBySongName ON Songs(SongName);

CREATE TABLE Concerts (
  VenueId      INT64 NOT NULL,
  SingerId     INT64 NOT NULL,
  ConcertDate  DATE NOT NULL,
  BeginTime    TIMESTAMP,
  EndTime      TIMESTAMP,
  TicketPrices ARRAY<INT64>,
) PRIMARY KEY(VenueId, SingerId, ConcertDate);

다음과 같은 데이터 조작 언어(DML) 문을 사용하여 이러한 테이블에 데이터를 추가할 수 있습니다.

INSERT INTO Singers (SingerId, FirstName, LastName, BirthDate)
VALUES (1, "Marc", "Richards", "1970-09-03"),
       (2, "Catalina", "Smith", "1990-08-17"),
       (3, "Alice", "Trentor", "1991-10-02"),
       (4, "Lea", "Martin", "1991-11-09"),
       (5, "David", "Lomond", "1977-01-29");

INSERT INTO Albums (SingerId, AlbumId, AlbumTitle)
VALUES (1, 1, "Total Junk"),
       (1, 2, "Go, Go, Go"),
       (2, 1, "Green"),
       (2, 2, "Forever Hold Your Peace"),
       (2, 3, "Terrified"),
       (3, 1, "Nothing To Do With Me"),
       (4, 1, "Play");

INSERT INTO Songs (SingerId, AlbumId, TrackId, SongName, Duration, SongGenre)
VALUES (2, 1, 1, "Let's Get Back Together", 182, "COUNTRY"),
       (2, 1, 2, "Starting Again", 156, "ROCK"),
       (2, 1, 3, "I Knew You Were Magic", 294, "BLUES"),
       (2, 1, 4, "42", 185, "CLASSICAL"),
       (2, 1, 5, "Blue", 238, "BLUES"),
       (2, 1, 6, "Nothing Is The Same", 303, "BLUES"),
       (2, 1, 7, "The Second Time", 255, "ROCK"),
       (2, 3, 1, "Fight Story", 194, "ROCK"),
       (3, 1, 1, "Not About The Guitar", 278, "BLUES");

Cloud Spanner는 데이터를 분할로 나누므로 효율적인 실행 계획을 쉽게 얻을 수 없습니다. 분할은 독립적으로 서로 간에 이동할 수 있으며 여러 물리적 위치에 있을 수 있는 여러 서버로 할당될 수 있습니다. 분산 데이터에 대한 실행 계획을 평가하기 위해 Cloud Spanner는 다음을 기반으로 실행을 사용합니다.

  • 데이터가 포함된 서버에서 하위 계획 로컬 실행
  • 적극적인 배포 잘라내기를 통한 여러 원격 실행 조정 및 집계

Cloud Spanner는 이 모델을 사용하기 위해 기본 연산자인 distributed union과 변형인 distributed cross applydistributed outer apply를 사용합니다.

쿼리 수명

Cloud Spanner의 SQL 쿼리는 먼저 실행 계획으로 컴파일된 후 실행을 위해 초기 루트 서버로 보내집니다. 쿼리 중인 데이터에 도달하기 위한 홉 수가 최소화되도록 루트 서버가 선택됩니다. 그러면 루트 서버가 다음을 수행합니다.

  • 하위 계획의 원격 실행 시작(필요한 경우)
  • 원격 실행 결과 기다림
  • 결과 집계와 같은 나머지 로컬 실행 단계 처리
  • 쿼리 결과 반환

하위 계획을 받는 원격 서버는 최상위 루트 서버와 동일한 모델을 따라 하위 계획의 '루트' 서버로 작동합니다. 결과는 원격 실행 트리입니다. 개념적으로 쿼리 실행은 위에서 아래로 진행되고 쿼리 결과는 아래에서 위로 반환됩니다. 다음 다이어그램은 이 패턴을 보여줍니다.

개념적 쿼리 계획

다음 예는 이 패턴을 보다 자세히 설명합니다.

집계 쿼리

집계 쿼리 구현 GROUP BY 쿼리

예를 들어 다음 쿼리를 사용합니다.

SELECT s.SingerId, COUNT(*) AS SongCount
FROM Songs AS s
WHERE s.SingerId < 100
GROUP BY s.SingerId;

결과는 다음과 같습니다.

+----------+-----------+
| SingerId | SongCount |
+----------+-----------+
|        3 |         1 |
|        2 |         8 |
+----------+-----------+

개념적으로 이는 실행 계획입니다.

집계 쿼리 실행 계획

Cloud Spanner는 쿼리 실행을 조정하고 하위 계획의 원격 배포를 수행하는 루트 서버로 실행 계획을 보냅니다.

이 실행 계획은 분할이 SingerId < 100을 충족하는 원격 서버로 하위 계획을 배포하는 분산 통합으로 시작됩니다. 계획에서 뒤쪽에 나타나는 로컬 분산 통합은 원격 서버에서의 실행을 나타냅니다. 각 로컬 분산 통합은 SingerId < 100 필터에 따라 Songs 테이블의 분할과 독립적으로 서브 쿼리를 평가합니다. 로컬 분산 통합은 집계 연산자로 결과를 반환합니다. 집계 연산자는 SingerIdCOUNT 집계를 수행하고 결과를 결과 직렬화 연산자에 반환합니다. 결과 직렬화 연산자는 SingerId별로 곡 수를 포함한 행으로 결과를 직렬화합니다. 그러면 분산 통합이 모든 결과를 결합하고 쿼리 결과를 반환합니다.

집계 연산자에서 집계에 대해 자세히 알아볼 수 있습니다.

공동 배치된 조인 쿼리

인터리빙 테이블은 물리적으로 관련 테이블의 행이 함께 배치되어 저장됩니다. 공동 배치된 조인은 인터리브 처리된 테이블 간의 조인입니다. 공동 배치된 조인은 색인이 필요한 조인 또는 백 조인에 비해 성능상의 이점을 제공합니다.

예를 들어 다음 쿼리를 사용합니다.

SELECT al.AlbumTitle, so.SongName
FROM Albums AS al, Songs AS so
WHERE al.SingerId = so.SingerId AND al.AlbumId = so.AlbumId;

(이 쿼리에서는 AlbumsSongs에 인터리브 처리되었다고 가정합니다.)

결과는 다음과 같습니다.

+-----------------------+--------------------------+
| AlbumTitle            | SongName                 |
+-----------------------+--------------------------+
| Nothing To Do With Me | Not About The Guitar     |
| Green                 | The Second Time          |
| Green                 | Starting Again           |
| Green                 | Nothing Is The Same      |
| Green                 | Let's Get Back Together  |
| Green                 | I Knew You Were Magic    |
| Green                 | Blue                     |
| Green                 | 42                       |
| Terrified             | Fight Story              |
+-----------------------+--------------------------+

다음은 실행 계획입니다.

공동 배치된 조인 쿼리 실행 계획

이 실행 계획은 Albums 테이블의 분할을 포함한 원격 서버로 하위 계획을 배포하는 분산 통합으로 시작됩니다. SongsAlbums의 인터리브 처리된 테이블이므로, 각 원격 서버는 다른 서버에 조인할 필요 없이 각 원격 서버에서 전체 하위 계획을 실행할 수 있습니다.

하위 계획에는 교차 적용이 포함됩니다. 각 교차 적용은 Albums 테이블에서 테이블 스캔을 수행하여 SingerId, AlbumId, AlbumTitle을 검색합니다. 그런 다음 교차 적용은 테이블 스캔 출력의 SingerId와 일치하는 색인에서 SingerId필터를 적용해 테이블 스캔 출력을 색인 SongsBySingerAlbumSongNameDesc의 색인 스캔 출력에 매핑합니다. 각 교차 적용은 AlbumTitleSongName 데이터를 직렬화하고 결과를 로컬 분산 통합으로 반환하는 결과 직렬화 연산자로 결과를 보냅니다. 분산 통합은 로컬 분산 통합의 결과를 집계하여 쿼리 결과로 반환합니다.

색인 및 백 조인 쿼리

위 예에서는 하나씩 번갈아 끼워 넣는 식으로 두 테이블에서 하나의 조인을 사용했습니다. 테이블 두 개 또는 테이블과 색인을 인터리빙하지 않으면 실행 계획이 더욱 복잡하고 덜 효율적입니다.

다음 명령어로 만들어진 색인을 고려해 보겠습니다.

CREATE INDEX SongsBySongName ON Songs(SongName)

다음 쿼리에서 이 색인을 사용합니다.

SELECT s.SongName, s.Duration
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");

결과는 다음과 같습니다.

+----------+----------+
| SongName | Duration |
+----------+----------+
| Blue     |      238 |
+----------+----------+

다음은 실행 계획입니다.

백 조인 쿼리 실행 계획

SongsBySongName 인덱스에 Duration 열이 포함되어 있지 않으므로 결과 실행 계획이 복잡해집니다. Duration 값을 얻기 위해 Cloud Spanner는 인덱싱된 결과를 Songs 테이블로 백 조인해야 합니다. Songs 테이블 및 SongsBySongName 글로벌 인덱스가 인터리브 처리되지 않으므로 이 조인은 동일한 위치에 있지 않습니다. 데이터가 공동 배치되지 않은 경우 Cloud Spanner가 실행 속도를 높이기 위해 최적화를 수행하므로 결과 실행 계획은 공동 배치된 조인 예시보다 복잡합니다.

맨 위 연산자는 분산 교차 적용입니다. 이 연산자의 입력측은 STARTS_WITH(s.SongName, "B") 조건자를 충족하는 SongsBySongName 인덱스의 행 배치입니다. 그러면 분산 교차 적용은 분할에 Duration 데이터가 있는 원격 서버에 이러한 배치를 매핑합니다. 원격 서버는 테이블 스캔을 사용하여 Duration 열을 검색합니다. 테이블 스캔은 Songs 테이블의 TrackIdSongsBySongName 인덱스에서 배치 처리된 행의 TrackId에 조인하는 Condition:($Songs_key_TrackId' = $batched_Songs_key_TrackId) 필터를 사용합니다.

결과가 최종 쿼리 응답으로 집계됩니다. 그러면 STARTS_WITH 조건자를 충족하는 색인의 행을 평가하기 위해 분산 교차 적용의 입력 측에 분산 통합/로컬 분산 통합 쌍이 포함됩니다.

s.Duration 열을 선택하지 않는 약간 다른 쿼리를 살펴보겠습니다.

SELECT s.SongName
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");

이 쿼리는 이 실행 계획에 표시된 색인을 완전하게 활용할 수 있습니다.

간단한 쿼리 실행 계획

쿼리가 요청한 모든 열이 색인에 있으므로 실행 계획에 백 조인이 필요하지 않습니다.

다음 단계