索引与视图
本章将详细介绍PostgreSQL中的索引和视图,包括各种索引类型的创建和管理,以及视图的使用方法,帮助您提高查询性能和简化复杂查询。
6.1 索引概述
索引的作用
索引是数据库中用于提高查询性能的重要工具:
- 加速查询:通过索引可以快速定位数据,避免全表扫描
- 加速排序:索引可以加速ORDER BY操作
- 加速连接:索引可以加速表之间的连接操作
- 唯一性约束:唯一索引可以保证数据的唯一性
索引的代价
虽然索引能提高查询性能,但也会带来一些代价:
- 存储空间:索引需要额外的存储空间
- 维护成本:INSERT、UPDATE、DELETE操作需要维护索引
- 选择性:不合适的索引可能不会提高性能,反而降低性能
6.2 索引类型
B-tree索引
B-tree是PostgreSQL的默认索引类型,适用于等值查询和范围查询:
-- 创建B-tree索引
CREATE INDEX idx_users_email ON users (email);
-- 创建复合索引
CREATE INDEX idx_users_name ON users (last_name, first_name);
-- 创建部分索引
CREATE INDEX idx_active_users ON users (username) WHERE is_active = true;
Hash索引
Hash索引适用于等值查询,但不支持范围查询:
-- 创建Hash索引
CREATE INDEX idx_users_email_hash ON users USING HASH (email);
GiST索引
GiST索引适用于几何数据、全文搜索等复杂数据类型:
-- 创建GiST索引用于全文搜索
CREATE INDEX idx_products_search ON products USING GIST (search_vector);
-- 创建GiST索引用于几何数据
CREATE INDEX idx_locations_geom ON locations USING GIST (geom);
GIN索引
GIN索引适用于数组、JSON等包含多个值的数据类型:
-- 创建GIN索引用于数组
CREATE INDEX idx_students_scores ON students USING GIN (scores);
-- 创建GIN索引用于JSONB
CREATE INDEX idx_products_metadata ON products USING GIN (metadata);
-- 创建GIN索引用于全文搜索
CREATE INDEX idx_articles_content ON articles USING GIN (to_tsvector('chinese', content));
BRIN索引
BRIN索引适用于大型表的块级索引:
-- 创建BRIN索引
CREATE INDEX idx_large_table_date ON large_table USING BRIN (created_date);
6.3 索引创建和管理
创建索引
-- 基本索引创建
CREATE INDEX idx_products_name ON products (name);
-- 唯一索引
CREATE UNIQUE INDEX idx_users_username ON users (username);
-- 复合索引
CREATE INDEX idx_orders_user_date ON orders (user_id, order_date);
-- 表达式索引
CREATE INDEX idx_users_lower_email ON users (LOWER(email));
-- 部分索引
CREATE INDEX idx_products_active ON products (name) WHERE is_active = true;
-- 并发创建索引(不阻塞表操作)
CREATE INDEX CONCURRENTLY idx_users_phone ON users (phone);
查看索引
-- 查看表的索引
\d users
-- 查看索引详细信息
SELECT
indexname,
indexdef
FROM pg_indexes
WHERE tablename = 'users';
-- 查看索引使用统计
SELECT
schemaname,
tablename,
indexname,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
WHERE tablename = 'users';
删除索引
-- 删除索引
DROP INDEX idx_users_email;
-- 如果索引存在则删除
DROP INDEX IF EXISTS idx_users_email;
重建索引
-- 重建索引
REINDEX INDEX idx_users_email;
-- 重建表的所有索引
REINDEX TABLE users;
-- 重建数据库的所有索引
REINDEX DATABASE mydb;
6.4 索引优化
索引选择性
选择性高的列更适合创建索引:
-- 查看列的选择性
SELECT
COUNT(DISTINCT email) * 100.0 / COUNT(*) AS selectivity
FROM users;
复合索引顺序
复合索引的列顺序很重要:
-- 根据查询模式创建复合索引
-- 如果经常按user_id查询,再按order_date排序
CREATE INDEX idx_orders_user_date ON orders (user_id, order_date);
-- 查询示例
SELECT * FROM orders WHERE user_id = 1 ORDER BY order_date;
SELECT * FROM orders WHERE user_id = 1 AND order_date > '2023-01-01';
索引维护
-- 查看索引膨胀情况
SELECT
schemaname,
tablename,
indexname,
pg_size_pretty(pg_relation_size(indexname::regclass)) AS index_size
FROM pg_indexes
WHERE schemaname = 'public'
ORDER BY pg_relation_size(indexname::regclass) DESC;
6.5 视图
视图概述
视图是虚拟表,基于SQL查询定义:
- 简化查询:隐藏复杂查询逻辑
- 安全性:限制用户访问特定数据
- 逻辑独立性:表结构变化不影响应用程序
创建视图
-- 创建简单视图
CREATE VIEW active_users AS
SELECT id, username, email, first_name, last_name
FROM users
WHERE is_active = true;
-- 创建复杂视图
CREATE VIEW user_order_summary AS
SELECT
u.id,
u.username,
COUNT(o.id) AS order_count,
SUM(o.total_amount) AS total_spent,
MAX(o.order_date) AS last_order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.username;
-- 创建物化视图(存储查询结果)
CREATE MATERIALIZED VIEW user_stats AS
SELECT
DATE(created_at) AS signup_date,
COUNT(*) AS user_count
FROM users
GROUP BY DATE(created_at);
使用视图
-- 查询视图
SELECT * FROM active_users;
-- 带条件查询视图
SELECT * FROM user_order_summary
WHERE order_count > 5;
-- 连接视图
SELECT u.username, uos.order_count
FROM active_users u
JOIN user_order_summary uos ON u.id = uos.id;
修改视图
-- 修改视图定义
CREATE OR REPLACE VIEW active_users AS
SELECT id, username, email, first_name, last_name, created_at
FROM users
WHERE is_active = true;
删除视图
-- 删除视图
DROP VIEW active_users;
-- 删除物化视图
DROP MATERIALIZED VIEW user_stats;
刷新物化视图
-- 刷新物化视图
REFRESH MATERIALIZED VIEW user_stats;
-- 并发刷新物化视图(不阻塞查询)
REFRESH MATERIALIZED VIEW CONCURRENTLY user_stats;
6.6 物化视图
物化视图优势
- 性能提升:预计算复杂查询结果
- 减少负载:避免重复执行复杂查询
- 数据快照:提供特定时间点的数据视图
物化视图示例
-- 创建销售报告物化视图
CREATE MATERIALIZED VIEW monthly_sales_report AS
SELECT
DATE_TRUNC('month', order_date) AS month,
COUNT(*) AS order_count,
SUM(total_amount) AS total_sales,
AVG(total_amount) AS avg_order_value
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '1 year'
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;
-- 创建带有索引的物化视图
CREATE MATERIALIZED VIEW product_inventory AS
SELECT
p.id,
p.name,
p.category_id,
c.name AS category_name,
p.stock_quantity,
p.price
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE p.is_active = true;
-- 为物化视图创建索引
CREATE INDEX idx_product_inventory_category ON product_inventory (category_id);
6.7 索引和视图最佳实践
索引最佳实践
- 选择合适的列:为经常用于WHERE、JOIN、ORDER BY的列创建索引
- 复合索引顺序:将选择性高的列放在前面
- 避免过多索引:每个索引都会增加维护成本
- 定期维护:监控索引使用情况,删除无用索引
- 部分索引:对部分数据创建索引以节省空间
视图最佳实践
- 简化复杂查询:将复杂查询封装在视图中
- 数据安全:通过视图限制用户访问敏感数据
- 逻辑独立性:使用视图减少应用程序与表结构的耦合
- 物化视图:对复杂聚合查询使用物化视图提高性能
实际应用示例
-- 电商系统索引优化示例
-- 为用户表创建索引
CREATE INDEX idx_users_email ON users (email);
CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_users_active ON users (is_active) WHERE is_active = true;
-- 为订单表创建索引
CREATE INDEX idx_orders_user_date ON orders (user_id, order_date DESC);
CREATE INDEX idx_orders_status_date ON orders (status, order_date);
CREATE INDEX idx_orders_total ON orders (total_amount);
-- 为产品表创建索引
CREATE INDEX idx_products_category_active ON products (category_id, is_active);
CREATE INDEX idx_products_price ON products (price);
CREATE INDEX idx_products_name_search ON products USING GIN (to_tsvector('english', name));
-- 创建用户订单视图
CREATE VIEW user_recent_orders AS
SELECT
u.id AS user_id,
u.username,
o.id AS order_id,
o.order_date,
o.total_amount,
o.status
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.order_date >= CURRENT_DATE - INTERVAL '30 days'
ORDER BY o.order_date DESC;
-- 创建销售统计物化视图
CREATE MATERIALIZED VIEW daily_sales_summary AS
SELECT
DATE(order_date) AS sale_date,
COUNT(*) AS order_count,
SUM(total_amount) AS total_sales,
AVG(total_amount) AS avg_order_value
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY DATE(order_date);
-- 为物化视图创建索引
CREATE INDEX idx_daily_sales_date ON daily_sales_summary (sale_date);
提示:定期分析和优化索引是数据库性能调优的重要环节。使用EXPLAIN ANALYZE来分析查询计划,确定是否需要创建或调整索引。