文件预览

sort-key-and-indexes.md

查看 Alibabacloud Emr Starrocks Assistant 技能包中的文件内容。

文件内容

references/schema/sort-key-and-indexes.md

# Detailed Sort Key and Index Design Guide

## Table of Contents

1. [Sort Key and Prefix Index](#sort-key-and-prefix-index)
2. [ORDER BY Syntax](#order-by-syntax)
3. [Bitmap Index](#bitmap-index)
4. [Bloom Filter Index](#bloom-filter-index)
5. [N-Gram Bloom Filter Index](#n-gram-bloom-filter-index)
6. [Full-Text Inverted Index](#full-text-inverted-index)
7. [Built-in Indexes](#built-in-indexes)
8. [Index Selection Decision](#index-selection-decision)

---

## Sort Key and Prefix Index

### How the Prefix Index Works

StarRocks builds the prefix index automatically during data ingestion:
- One index entry is generated every 1,024 rows
- Index content = the prefix of the sort key values from the first row
- **Maximum length 36 bytes**; anything beyond is truncated

At query time, the prefix index quickly locates the row ranges that contain the target data and skips irrelevant data blocks.

### Sort Key Design Principles

| Principle | Description |
|------|------|
| **At most 3 columns** | More than 3 columns increases write-time sorting overhead and may exceed the 36-byte prefix-index limit |
| **High-frequency filter columns first** | The first column of the sort key should be the most frequently used equality/range filter in query WHERE |
| **High-cardinality columns first** | High-cardinality columns filter better (low-cardinality columns like gender/status filter poorly) |
| **VARCHAR/STRING last** | String columns have variable length; the prefix index truncates at the first VARCHAR |
| **Follow leftmost-prefix matching** | Query conditions must match the sort key from the first column contiguously to use the prefix index |

### Leftmost-Prefix Matching Example

Sort key `ORDER BY (dt, user_id, action)`:

| Query Condition | Prefix Index | Reason |
|---------|---------|------|
| `WHERE dt = '2024-01-01'` | Hits | Matches the first column |
| `WHERE dt = '2024-01-01' AND user_id = 123` | Hits | Matches the first two columns |
| `WHERE user_id = 123` | Misses | Skips the first column dt |
| `WHERE action = 'click'` | Misses | Skips the first two columns |
| `WHERE dt > '2024-01-01' AND user_id = 123` | Partial hit | dt is a range condition; subsequent columns cannot benefit |

### Verifying Prefix Index Effectiveness

Check `ShortKeyFilterRows` in the query Profile:
- Larger values → more rows filtered by the prefix index → better effect
- A value of 0 → prefix index did not take effect; check whether the query conditions match the sort key

---

## ORDER BY Syntax

### Version Support

| Version | Scope of Support |
|------|---------|
| v3.0+ | Primary Key tables support independent ORDER BY |
| v3.3+ | All table types support independent ORDER BY |
| Before v3.3 | The sort key is determined by the KEY columns (DUPLICATE KEY / AGGREGATE KEY / UNIQUE KEY) |

### Relationship with KEY Columns

**v3.3+ (recommended):** ORDER BY is independent of KEY columns; physical sorting is determined by ORDER BY.

```sql
-- Aggregate table: ORDER BY can differ in order from AGGREGATE KEY columns
CREATE TABLE agg_stats (
    user_id INT,
    dt DATE,
    region VARCHAR(20),
    pv BIGINT SUM
)
AGGREGATE KEY(user_id, dt, region)
ORDER BY (dt, region, user_id)  -- Optimize queries by time range
PARTITION BY date_trunc('day', dt)
DISTRIBUTED BY HASH(user_id) BUCKETS 16;
```

**Before v3.3 (compatibility):** The sort key is implicitly determined by the KEY definition.

```sql
-- For a Duplicate Key table, the KEY columns = the sort key
CREATE TABLE log (
    dt DATE,
    user_id BIGINT,
    action VARCHAR(32),
    detail VARCHAR(256)
)
DUPLICATE KEY(dt, user_id, action);  -- Sort key = (dt, user_id, action)
```

### Sort Key Examples

```sql
-- E-commerce orders: queried by user and time
CREATE TABLE orders (
    order_id BIGINT NOT NULL,
    user_id INT NOT NULL,
    order_date DATE NOT NULL,
    status TINYINT,
    amount DECIMAL(12,2)
)
PRIMARY KEY(order_id, order_date, user_id)
PARTITION BY date_trunc('day', order_date)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
ORDER BY (user_id, order_date);  -- Optimize equality on user_id + range on order_date

-- Log table: queried by time range
CREATE TABLE access_log (
    log_time DATETIME NOT NULL,
    service VARCHAR(64),
    level VARCHAR(8),
    message VARCHAR(1024)
)
DUPLICATE KEY(log_time, service)
ORDER BY (log_time, service)  -- Optimize time range + service filter
PARTITION BY date_trunc('hour', log_time)
DISTRIBUTED BY RANDOM;
```

---

## Bitmap Index

**Applicable scenarios:** Equality/IN queries on non-prefix columns; works for both high-cardinality and low-cardinality columns.

**Syntax:**

```sql
-- Create at table creation time
CREATE TABLE user_profile (
    user_id BIGINT,
    age INT,
    gender VARCHAR(8),
    city VARCHAR(32),
    INDEX idx_city (city) USING BITMAP
)
DUPLICATE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 16;

-- Add to an existing table
CREATE INDEX idx_gender ON user_profile (gender) USING BITMAP;

-- Drop the index
DROP INDEX idx_gender ON user_profile;
```

**Supported query patterns:**
- `WHERE city = 'Shanghai'`
- `WHERE city IN ('Shanghai', 'Beijing')`
- AND/OR combinations across multiple Bitmap-indexed columns

**Limitations:**
- Not supported on value columns of Aggregate / Unique Key tables
- Not supported for FLOAT, DOUBLE, DECIMAL types
- Extra overhead at write time for index construction

**When to use:**
- Queries frequently filter on non-sort-key columns
- Combined conditions on several low-cardinality columns (e.g., `city = X AND status = Y`)
- Equality queries on high-cardinality columns (e.g., user_id)

---

## Bloom Filter Index

**Applicable scenarios:** Equality queries on high-cardinality columns (ID-like) to quickly decide whether a data block contains the target value.

**Syntax:**

```sql
-- Specify at table creation
CREATE TABLE orders (
    order_id VARCHAR(64),
    user_id BIGINT,
    product_name VARCHAR(128),
    amount DECIMAL(12,2)
)
DUPLICATE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 32
PROPERTIES (
    "bloom_filter_columns" = "order_id, user_id"
);

-- Add to an existing table
ALTER TABLE orders SET ("bloom_filter_columns" = "order_id, user_id, product_name");
```

**How it works:**
- Each data Page maintains a Bloom Filter
- At query time the Bloom Filter is checked first; if it says the value is absent, the page is skipped
- False positives are possible, but false negatives are not

**When to use:**
- High-cardinality columns (ID-like fields such as order_id, transaction_id)
- Queries dominated by equality conditions
- The prefix index doesn't cover (column is not in the sort key or fails leftmost-prefix matching)

**Limitations:**
- Not supported for TINYINT, FLOAT, DOUBLE types
- Effective only for `=` and `IN`; not for range queries
- About 1-2% additional storage per column

---

## N-Gram Bloom Filter Index

**Applicable scenarios:** Accelerates LIKE fuzzy queries and the `ngram_search` function.

**Syntax:**

```sql
CREATE TABLE articles (
    id BIGINT,
    title VARCHAR(256),
    content STRING,
    INDEX idx_content (content) USING NGRAMBF ("gram_num" = "4", "bloom_filter_fpp" = "0.05")
)
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 16;
```

**Parameters:**
- `gram_num`: The N value of the N-gram, typically 4
- `bloom_filter_fpp`: False positive rate, default 0.05

**When to use:**
- Need to accelerate `WHERE content LIKE '%keyword%'`
- `ngram_search()` function queries

---

## Full-Text Inverted Index

**Applicable scenarios:** Keyword full-text search.

**Syntax:**

```sql
CREATE TABLE docs (
    doc_id BIGINT,
    title VARCHAR(256),
    body STRING,
    INDEX idx_body (body) USING GIN
         ("parser" = "chinese")  -- Supports standard/english/chinese
)
DUPLICATE KEY(doc_id)
DISTRIBUTED BY HASH(doc_id) BUCKETS 16;
```

**Supported tokenizers:**
- `standard`: Tokenize by whitespace/punctuation
- `english`: English tokenization with stemming
- `chinese`: Chinese tokenization

**When to use:**
- Keyword search in logs
- Document content retrieval
- Replace inefficient full table scans caused by LIKE '%keyword%'

---

## Built-in Indexes

The following indexes are maintained automatically by StarRocks; no manual creation is needed:

### ZoneMap Index
- **Level:** Segment level + Page level
- **Content:** Min, Max, HasNull, HasNotNull for each column
- **Purpose:** At query time, automatically skip data blocks that do not satisfy WHERE range conditions
- **Influencing factors:** Sort key order affects ZoneMap effectiveness — ZoneMap filtering is most effective on the first sort key column

### Ordinal Index
- **Level:** Page level
- **Content:** Row number → physical page address mapping
- **Purpose:** Locate data pages in the columnar storage format

---

## Index Selection Decision

```
Is the column in the sort key?
├── Yes → The prefix index applies automatically (when leftmost-prefix matching is satisfied)
│   └── If leftmost-prefix matching is not satisfied → consider reordering sort key columns
│       or add a Bitmap / Bloom Filter index
└── No → What is the query pattern?
    ├── Equality query (= / IN)
    │   ├── High-cardinality column (ID-like) → Bloom Filter index
    │   ├── Low-cardinality column or multi-column combination → Bitmap index
    │   └── When both apply → Bloom Filter (lower write overhead)
    ├── Fuzzy query (LIKE '%X%') → N-Gram Bloom Filter
    ├── Full-text search → Inverted index (GIN)
    └── Range query (> / < / BETWEEN)
        → Rely on ZoneMap (automatic); if not effective, consider reordering the sort key
```

**Multi-index combination example:**

```sql
CREATE TABLE ecommerce_events (
    event_time DATETIME NOT NULL,
    user_id BIGINT NOT NULL,
    event_type VARCHAR(32),
    product_id BIGINT,
    category VARCHAR(64),
    search_query VARCHAR(256),
    -- Bitmap index: filter on event type and category combinations
    INDEX idx_event_type (event_type) USING BITMAP,
    INDEX idx_category (category) USING BITMAP,
    -- N-Gram index: fuzzy match on search terms
    INDEX idx_search (search_query) USING NGRAMBF ("gram_num" = "4", "bloom_filter_fpp" = "0.05")
)
DUPLICATE KEY(event_time, user_id)
ORDER BY (event_time, user_id)
PARTITION BY date_trunc('day', event_time)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES (
    -- Bloom Filter index: equality queries on high-cardinality product_id
    "bloom_filter_columns" = "product_id"
);
```