第七章:索引与性能

最后更新: 2024-01-01 作者: PostgreSQL Team
页面目录

第七章:索引与性能

7.1 索引概述

索引是提高数据库查询性能的关键数据结构。

┌─────────────────────────────────────────────────────────────┐
│                       索引工作原理                            │
├─────────────────────────────────────────────────────────────┤
│  无索引:  全表扫描 → O(n)                                    │
│  B-Tree: 二叉搜索 → O(log n)                                │
│  Hash:   哈希查找 → O(1)                                     │
└─────────────────────────────────────────────────────────────┘

7.2 索引类型

7.2.1 B-Tree 索引(默认)

适用于:等值查询、范围查询、排序、模糊查询(前缀)

-- 创建 B-Tree 索引(默认类型)
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_status ON users(status);

-- 复合索引
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);

-- 表达式索引
CREATE INDEX idx_users_lower_email ON users(LOWER(email));

-- 部分索引
CREATE INDEX idx_orders_completed ON orders(created_at) 
WHERE status = 'completed';

-- 查看索引使用情况
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';

7.2.2 Hash 索引

适用于:等值查询,不支持范围查询和排序

-- 创建 Hash 索引
CREATE INDEX idx_sessions_token ON sessions USING HASH (token);

-- 查看索引类型
\d sessions

7.2.3 GiST 索引

适用于:几何类型、范围类型、全文搜索

-- 空间数据索引
CREATE EXTENSION IF NOT EXISTS postgis;

-- 创建 GiST 索引用于地理位置
CREATE INDEX idx_locations_geo ON locations USING GIST (geolocation);

-- 范围类型索引
CREATE INDEX idx_reservations_period ON reservations USING GIST (stay_period);

7.2.4 GIN 索引

适用于:数组、JSONB、全文搜索

-- JSONB 索引
CREATE INDEX idx_products_attrs ON products USING GIN (attributes);

-- 数组索引
CREATE INDEX idx_tags ON posts USING GIN (tags);

-- 全文搜索索引
CREATE INDEX idx_content_fts ON articles USING GIN (to_tsvector('english', content));

7.2.5 BRIN 索引

适用于:大表的时间序列数据,按块扫描

-- BRIN 索引(块范围索引)
CREATE INDEX idx_logs_created ON logs USING BRIN (created_at);

-- 适合大表且数据有序存储的情况
CREATE INDEX idx_logs_created ON logs USING BRIN (created_at) 
WITH (pages_per_range = 128);

7.3 创建索引

7.3.1 基本语法

-- 单列索引
CREATE INDEX idx_table_column ON table_name(column);

-- 复合索引
CREATE INDEX idx_table_cols ON table_name(column1, column2);

-- 唯一索引
CREATE UNIQUE INDEX idx_user_email ON users(email);

-- 条件索引(部分索引)
CREATE INDEX idx_active_users ON users(last_login) 
WHERE status = 'active';

-- 表达式索引
CREATE INDEX idx_users_name ON users(UPPER(name));

7.3.2 并行创建索引

-- 在大数据表上并行创建索引
CREATE INDEX CONCURRENTLY idx_large_table_date 
ON large_table(created_at);

-- 重建索引
REINDEX INDEX CONCURRENTLY idx_users_email;

7.3.3 索引管理

-- 查看表的所有索引
\d+ table_name

-- 查看索引大小
SELECT 
    indexname,
    pg_size_pretty(pg_relation_size(indexname))
FROM pg_indexes
WHERE tablename = 'users';

-- 重命名索引
ALTER INDEX old_index_name RENAME TO new_index_name;

-- 删除索引
DROP INDEX idx_unused_index;

-- 检查未使用的索引
SELECT 
    schemaname || '.' || relname AS table,
    indexrelname AS index,
    pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
    idx_scan AS index_scans
FROM pg_stat_user_indexes ui
JOIN pg_index i ON ui.indexrelid = i.indexrelid
WHERE idx_scan = 0
ORDER BY pg_relation_size(i.indexrelid) DESC;

7.4 查询分析

7.4.1 EXPLAIN 分析

-- 基本分析
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';

-- 分析并显示实际执行信息
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM users WHERE email = 'test@example.com';

-- JSON 格式输出
EXPLAIN (FORMAT JSON)
SELECT * FROM orders WHERE status = 'completed';

7.4.2 执行计划解读

-- Seq Scan:全表扫描
EXPLAIN SELECT * FROM users WHERE age > 30;

-- Index Scan:索引扫描
EXPLAIN SELECT * FROM users WHERE id = 1;

-- Index Only Scan:仅索引扫描
EXPLAIN SELECT email FROM users WHERE email LIKE 'a%';

-- Nested Loop:嵌套循环连接
EXPLAIN SELECT * FROM orders o, users u WHERE o.user_id = u.id;

-- Hash Join:哈希连接
EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id;

-- Merge Join:归并连接
EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id ORDER BY u.id;

7.4.3 慢查询分析

-- 查看最近执行的慢查询
SELECT 
    pid,
    now() - query_start AS duration,
    usename,
    query,
    state
FROM pg_stat_activity
WHERE state != 'idle'
    AND now() - query_start > interval '5 minutes';

-- 终止慢查询
SELECT pg_cancel_backend(pid);  -- 优雅终止
SELECT pg_terminate_backend(pid); -- 强制终止

7.5 性能优化技巧

7.5.1 查询优化

-- 使用 LIMIT 避免全表扫描
SELECT * FROM large_table LIMIT 10;

-- 避免 SELECT *
SELECT user_id, email FROM users WHERE id = 1;

-- 使用 LIMIT OFFSET 分页(大数据量时效率低)
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 10000;

-- 使用游标分页(推荐)
SELECT * FROM users WHERE id > 10000 ORDER BY id LIMIT 10;

-- 使用 EXISTS 代替 IN
SELECT * FROM users WHERE EXISTS (
    SELECT 1 FROM orders WHERE user_id = users.id AND total > 100
);

7.5.2 批量操作优化

-- 批量插入
INSERT INTO users (username, email) VALUES
    ('user1', 'user1@example.com'),
    ('user2', 'user2@example.com'),
    ('user3', 'user3@example.com');

-- 使用 COPY 进行高效导入
COPY users(username, email) FROM '/path/to/users.csv' WITH (FORMAT csv);

-- 批量更新
UPDATE orders SET status = 'completed', updated_at = NOW()
WHERE id IN (1, 2, 3, 4, 5);

-- 分批更新大表
UPDATE orders SET status = 'completed'
WHERE id IN (SELECT id FROM orders WHERE status = 'pending' LIMIT 1000);

7.5.3 连接优化

-- 确保连接列有索引
-- orders.user_id 和 users.id 都需要有索引

-- 优先过滤小表
SELECT u.username, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'  -- 过滤小结果集
ORDER BY o.total DESC;

-- 优化多表连接顺序
EXPLAIN 
SELECT *
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id
WHERE o.created_at > '2024-01-01';

7.6 配置参数调优

7.6.1 内存配置

# postgresql.conf

# 共享缓冲区(建议为系统内存的 25%)
shared_buffers = 4GB

# 单次排序内存
work_mem = 64MB

# 维护操作内存(VACUUM、CREATE INDEX 等)
maintenance_work_mem = 512MB

# 有效缓存大小(用于查询规划)
effective_cache_size = 12GB

7.6.2 并行查询配置

# 最大并行 worker 数
max_parallel_workers_per_gather = 4

# 维护操作的并行度
max_parallel_workers_maintenance_workers = 4

# 总并行 worker 数
max_worker_processes = 16

7.6.3 WAL 配置

# WAL 缓冲区
wal_buffers = 16MB

# 异步提交
async_commit = on

# 检查点配置
checkpoint_timeout = 15min
max_wal_size = 2GB
min_wal_size = 1GB

7.7 VACUUM 维护

7.7.1 垃圾回收

-- 手动 VACUUM
VACUUM ANALYZE users;

-- VACUUM 详细输出
VACUUM VERBOSE ANALYZE users;

-- 只回收空间不更新统计信息
VACUUM users;

-- 截断表末尾空白页
VACUUM FULL users;

7.7.2 自动清理配置

# postgresql.conf

# 启用自动清理
autovacuum = on

# 自动清理触发阈值
autovacuum_vacuum_threshold = 50
autovacuum_analyze_threshold = 50

# 垃圾比例阈值
autovacuum_vacuum_scale_factor = 0.1
autovacuum_analyze_scale_factor = 0.05

# 最大工作进程数
autovacuum_max_workers = 4

7.8 本章小结

索引和性能优化要点:

主题 关键点
索引类型 B-Tree、Hash、GiST、GIN、BRIN
索引策略 复合索引、表达式索引、部分索引
查询分析 EXPLAIN、ANALYZE、BUFFERS
配置调优 shared_buffers、work_mem、并行查询
维护 VACUUM、ANALYZE、自动清理

📌 下一章预告

下一章将介绍 PostgreSQL 的视图和函数,包括存储过程、触发器和自定义函数。