索引 #

一、索引概述 #

1.1 索引类型 #

text
CockroachDB 索引类型
├── 主键索引
│   └── 每个表必须有一个主键
│
├── 二级索引
│   ├── 唯一索引
│   └── 普通索引
│
├── 复合索引
│   └── 多列索引
│
├── 表达式索引
│   └── 基于表达式的索引
│
├── 部分索引
│   └── 带条件的索引
│
└── 倒排索引
    └── JSONB和数组索引

1.2 索引结构 #

text
索引存储结构
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   主键索引:                                                 │
│   ┌─────────────────────────────────────────────────────┐   │
│   │ Key: /Table/<table_id>/<primary_key>               │   │
│   │ Value: 行数据                                       │   │
│   │                                                      │   │
│   │ 数据按主键顺序存储                                  │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   二级索引:                                                 │
│   ┌─────────────────────────────────────────────────────┐   │
│   │ Key: /Table/<table_id>/<index_id>/<indexed_value>/<primary_key>│
│   │ Value: NULL (非唯一索引) 或 primary_key (唯一索引) │   │
│   │                                                      │   │
│   │ 索引数据按索引值顺序存储                            │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、主键索引 #

2.1 主键设计 #

sql
-- 单列主键
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100),
    email VARCHAR(100)
);

-- 复合主键
CREATE TABLE order_items (
    order_id UUID,
    item_id INT,
    product_name VARCHAR(100),
    quantity INT,
    PRIMARY KEY (order_id, item_id)
);

-- 自增主键 (不推荐,可能导致热点)
CREATE TABLE users_seq (
    id BIGINT PRIMARY KEY DEFAULT unique_rowid(),
    name VARCHAR(100)
);

-- 推荐使用UUID
CREATE TABLE users_uuid (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(100)
);

2.2 主键选择建议 #

text
主键选择建议
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   推荐:                                                     │
│   ├── UUID: 分散写入,避免热点                             │
│   ├── 复合主键: 符合业务逻辑                               │
│   └── 有序UUID: 结合顺序性和分散性                         │
│                                                             │
│   不推荐:                                                   │
│   ├── 自增ID: 导致写入热点                                 │
│   ├── 时间戳: 时间相近的数据集中                           │
│   └── 无主键: 使用隐式rowid                                │
│                                                             │
│   注意事项:                                                 │
│   ├── 主键不可修改                                         │
│   ├── 主键应尽量短                                         │
│   └── 主键应避免频繁更新                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

三、二级索引 #

3.1 创建索引 #

sql
-- 创建普通索引
CREATE INDEX idx_name ON users(name);

-- 创建唯一索引
CREATE UNIQUE INDEX idx_email ON users(email);

-- 创建复合索引
CREATE INDEX idx_name_email ON users(name, email);

-- 建表时创建索引
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID,
    order_no VARCHAR(50),
    amount DECIMAL(10, 2),
    status VARCHAR(20),
    created_at TIMESTAMP DEFAULT NOW(),
    INDEX idx_user_id (user_id),
    UNIQUE INDEX idx_order_no (order_no),
    INDEX idx_user_status (user_id, status)
);

3.2 删除索引 #

sql
-- 删除索引
DROP INDEX idx_name;

-- 使用表名删除
DROP INDEX users@idx_name;

-- 使用 ALTER TABLE
ALTER TABLE users DROP INDEX idx_name;

3.3 查看索引 #

sql
-- 查看表索引
SHOW INDEX FROM users;

-- 从系统表查询
SELECT 
    index_name,
    column_name,
    non_unique,
    seq_in_index
FROM information_schema.statistics
WHERE table_name = 'users';

-- 查看索引大小
SELECT 
    table_name,
    index_name,
    pg_size_pretty(pg_relation_size(index_name::regclass)) AS size
FROM pg_indexes
WHERE table_name = 'users';

四、复合索引 #

4.1 最左前缀原则 #

sql
-- 复合索引
CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);

-- 使用索引的查询
SELECT * FROM orders WHERE user_id = 1;  -- ✓ 使用索引
SELECT * FROM orders WHERE user_id = 1 AND status = 'paid';  -- ✓ 使用索引
SELECT * FROM orders WHERE user_id = 1 AND status = 'paid' AND created_at > '2024-01-01';  -- ✓ 使用索引

-- 不使用索引的查询
SELECT * FROM orders WHERE status = 'paid';  -- ✗ 不使用索引
SELECT * FROM orders WHERE created_at > '2024-01-01';  -- ✗ 不使用索引

-- 部分使用索引
SELECT * FROM orders WHERE user_id = 1 AND created_at > '2024-01-01';  
-- 只使用 user_id 部分

4.2 索引列顺序 #

text
索引列顺序建议
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   1. 等值条件列在前                                         │
│      ├── WHERE col = 'value' 放前面                        │
│      └── 范围条件列放后面                                  │
│                                                             │
│   2. 选择性高的列在前                                       │
│      ├── 唯一值多的列放前面                                │
│      └── 区分度高,过滤效果好                              │
│                                                             │
│   3. 排序和分组列                                           │
│      ├── ORDER BY 列可考虑放入索引                         │
│      └── GROUP BY 列可考虑放入索引                         │
│                                                             │
│   示例:                                                     │
│   SELECT * FROM orders                                     │
│   WHERE user_id = 1                                        │
│     AND status = 'paid'                                    │
│   ORDER BY created_at DESC;                                │
│                                                             │
│   推荐索引: (user_id, status, created_at)                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

五、特殊索引 #

5.1 表达式索引 #

sql
-- 创建表达式索引
CREATE INDEX idx_lower_email ON users((LOWER(email)));

-- 使用表达式索引
SELECT * FROM users WHERE LOWER(email) = 'test@example.com';

-- 函数索引
CREATE INDEX idx_year_created ON orders((EXTRACT(YEAR FROM created_at)));

SELECT * FROM orders WHERE EXTRACT(YEAR FROM created_at) = 2024;

-- 计算列索引
CREATE INDEX idx_full_name ON users((first_name || ' ' || last_name));

SELECT * FROM users WHERE first_name || ' ' || last_name = 'John Doe';

5.2 部分索引 #

sql
-- 创建部分索引
CREATE INDEX idx_active_users ON users(email)
WHERE status = 'active';

-- 使用部分索引
SELECT * FROM users WHERE email = 'test@example.com' AND status = 'active';

-- 部分索引示例
CREATE INDEX idx_pending_orders ON orders(user_id)
WHERE status = 'pending';

CREATE UNIQUE INDEX idx_unique_email ON users(email)
WHERE deleted_at IS NULL;

5.3 倒排索引 #

sql
-- JSONB索引
CREATE INDEX idx_data_gin ON users USING GIN (data);

-- JSONB路径索引
CREATE INDEX idx_data_name ON users((data->>'name'));

-- 数组索引
CREATE TABLE tags (
    id UUID PRIMARY KEY,
    tags STRING[]
);

CREATE INDEX idx_tags_gin ON tags USING GIN (tags);

-- 查询
SELECT * FROM tags WHERE tags @> ARRAY['tag1'];

六、索引优化 #

6.1 覆盖索引 #

sql
-- 覆盖索引: 查询只需要索引列,不需要回表
CREATE INDEX idx_user_status_amount ON orders(user_id, status, amount);

-- 使用覆盖索引
SELECT user_id, status, amount 
FROM orders 
WHERE user_id = 1;  -- 不需要回表,更快

-- 查看执行计划
EXPLAIN SELECT user_id, status, amount 
FROM orders 
WHERE user_id = 1;
-- IndexReader 表示使用覆盖索引

6.2 索引选择 #

sql
-- 使用 EXPLAIN 分析索引使用
EXPLAIN SELECT * FROM orders WHERE user_id = 1;

-- 强制使用索引
SELECT * FROM orders@idx_user_id WHERE user_id = 1;

-- 使用提示
SELECT /*+ Index(orders idx_user_id) */ * 
FROM orders 
WHERE user_id = 1;

-- 忽略索引
SELECT /*+ NoIndex(orders) */ * 
FROM orders 
WHERE user_id = 1;

6.3 索引下推 #

sql
-- 索引下推: 过滤条件在索引扫描时执行
CREATE INDEX idx_user_amount ON orders(user_id, amount);

-- 索引下推示例
SELECT * FROM orders 
WHERE user_id = 1 AND amount > 100;

-- CockroachDB 会将 amount > 100 下推到索引扫描
-- 减少回表次数

七、索引最佳实践 #

7.1 创建索引建议 #

text
索引创建建议
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   适合创建索引:                                             │
│   ├── WHERE 条件频繁使用的列                               │
│   ├── JOIN 连接的列                                        │
│   ├── ORDER BY / GROUP BY 的列                             │
│   ├── 选择性高的列 (唯一值多)                              │
│   └── 外键列                                               │
│                                                             │
│   不适合创建索引:                                           │
│   ├── 选择性低的列 (如性别、状态)                          │
│   ├── 频繁更新的列                                         │
│   ├── 数据量小的表                                         │
│   └── 很少使用的列                                         │
│                                                             │
│   注意事项:                                                 │
│   ├── 索引数量不宜过多                                     │
│   ├── 索引会增加写入开销                                   │
│   └── 定期检查未使用的索引                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

7.2 避免索引失效 #

sql
-- 避免在索引列上使用函数
-- ✗ 不推荐
SELECT * FROM users WHERE UPPER(name) = 'ALICE';
-- ✓ 推荐
SELECT * FROM users WHERE name = 'Alice';

-- 避免隐式类型转换
-- ✗ 不推荐 (user_id 是 UUID)
SELECT * FROM orders WHERE user_id = '1';
-- ✓ 推荐
SELECT * FROM orders WHERE user_id = 'uuid-string';

-- 避免 OR 导致索引失效
-- ✗ 不推荐
SELECT * FROM orders WHERE user_id = 'uuid-1' OR amount > 100;
-- ✓ 使用 UNION
SELECT * FROM orders WHERE user_id = 'uuid-1'
UNION
SELECT * FROM orders WHERE amount > 100;

-- 避免 NOT LIKE
-- ✗ 不推荐
SELECT * FROM users WHERE name NOT LIKE 'A%';
-- ✓ 使用其他方式
SELECT * FROM users WHERE name >= 'B' OR name < 'A';

7.3 索引监控 #

sql
-- 查看索引使用统计
SELECT 
    table_schema,
    table_name,
    index_name,
    idx_scan,
    idx_tup_read,
    idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC;

-- 查找未使用的索引
SELECT 
    schemaname,
    tablename,
    indexname
FROM pg_stat_user_indexes
WHERE idx_scan = 0
  AND indexname NOT LIKE '%pkey%';

-- 查看索引大小
SELECT 
    schemaname,
    tablename,
    indexname,
    pg_size_pretty(pg_relation_size(indexrelid)) AS size
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC;

八、总结 #

索引要点:

类型 说明
主键索引 每表必须,数据按主键组织
二级索引 辅助索引,需要回表
复合索引 多列索引,遵循最左前缀
覆盖索引 查询不需要回表
表达式索引 基于表达式的索引
部分索引 带条件的索引

下一步,让我们学习视图!

最后更新:2026-03-27