PostgreSQL 索引类型详解

1. 索引创建基础语法

PostgreSQL 默认使用 B-tree 索引,通过 CREATE INDEX 命令创建;其他索引类型需通过 USING 关键字显式指定,通用语法如下:

sql 复制代码
-- 默认创建 B-tree 索引
CREATE INDEX 索引名 ON 表名 (列名);

-- 创建指定类型的索引
CREATE INDEX 索引名 ON 表名 USING 索引类型 (列名);

-- 示例:创建 Hash 索引
CREATE INDEX idx_user_id ON users USING HASH (user_id);

2. 核心索引类型及实操样例

2.1 B-tree 索引

2.1.1 核心特性

B-tree(平衡树)是 PostgreSQL 默认索引类型,适用于可排序数据的等值、范围查询,支持排序操作,是最通用的索引类型。

2.1.2 支持的操作符

  • 基础比较:<(小于)、<=(小于等于)、=(等于)、>=(大于等于)、>(大于)
  • 组合条件:BETWEEN(介于两值之间)、IN(匹配列表中任一值)
  • 空值判断:IS NULL(为空)、IS NOT NULL(不为空)
  • 模式匹配(有限支持):
  • LIKE 'foo%'(模糊匹配以foo开头的字符串)、~ '^foo'(正则匹配以foo开头的字符串)
  • ILIKE(不区分大小写的模糊匹配)、~*(不区分大小写的正则匹配),仅模式以非字母开头时生效

2.1.3 实操样例

步骤1:创建测试表

sql 复制代码
-- 创建用户表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    age INT,
    register_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入测试数据
INSERT INTO users (username, age) VALUES 
('zhangsan', 25), ('lisi', 30), ('wangwu', 28), ('zhaoliu', 35);

步骤2:创建 B-tree 索引

sql 复制代码
-- 为 age 列创建 B-tree 索引(默认类型,可省略 USING BTREE)
CREATE INDEX idx_users_age ON users (age);

-- 为 register_time 列创建 B-tree 索引(支持范围/排序)
CREATE INDEX idx_users_register_time ON users (register_time);

步骤3:索引生效的查询场景

sql 复制代码
-- 等值查询(命中 idx_users_age)
SELECT * FROM users WHERE age = 30;

-- 范围查询(命中 idx_users_age)
SELECT * FROM users WHERE age BETWEEN 25 AND 30;

-- 排序查询(命中 idx_users_register_time)
SELECT * FROM users ORDER BY register_time DESC LIMIT 2;

-- 模式匹配(前缀匹配,命中 idx_users_username)
CREATE INDEX idx_users_username ON users (username);
SELECT * FROM users WHERE username LIKE 'zhang%';

2.2 Hash 索引

2.2.1 核心特性

存储索引列的 32 位哈希值,仅支持简单等值比较,功能单一但等值查询效率高,适用场景有限。

2.2.2 支持的操作符

仅等值判断:=

2.2.3 实操样例

步骤1:创建 Hash 索引

sql 复制代码
-- 为 username 列创建 Hash 索引(仅支持等值)
CREATE INDEX idx_users_username_hash ON users USING HASH (username);

步骤2:索引生效的查询场景

sql 复制代码
-- 纯等值查询(命中 Hash 索引)
SELECT * FROM users WHERE username = 'lisi';

-- 注意:以下场景 Hash 索引不生效,会走全表扫描
SELECT * FROM users WHERE username LIKE 'li%'; -- 范围/模糊查询不支持
SELECT * FROM users WHERE age = 28; -- 未创建 Hash 索引的列

2.3 GiST 索引

2.3.1 核心特性

GiST(Generalized Search Tree)并非单一索引,而是通用索引框架,可实现多种索引策略,支持复杂数据类型(如空间几何、全文检索)和"最近邻"搜索。

2.3.2 支持的操作符(以二维几何类型为例)

空间关系:<<(左侧)、&<(重叠左侧)、&>(重叠右侧)、>>(右侧)、<<|(下侧)、&<|(重叠下侧)、|&>(重叠上侧)、|>>(上侧)、@>(包含)、<@(被包含)、~=(相等)、&&(相交);最近邻搜索:<->(距离运算符,用于按距离排序)

2.3.3 实操样例(空间数据场景)

步骤1:启用 PostGIS 扩展(需先安装)

sql 复制代码
CREATE EXTENSION IF NOT EXISTS postgis;

步骤2:创建空间数据表并插入数据

sql 复制代码
-- 创建地点表(包含地理坐标)
CREATE TABLE places (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    location GEOGRAPHY(POINT) -- 地理坐标类型
);

-- 插入测试地点(纬度、经度)
INSERT INTO places (name, location) VALUES
('公园', ST_SetSRID(ST_MakePoint(116.40, 39.90), 4326)),
('商场', ST_SetSRID(ST_MakePoint(116.41, 39.91), 4326)),
('学校', ST_SetSRID(ST_MakePoint(116.39, 39.89), 4326));

步骤3:创建 GiST 索引

sql 复制代码
-- 为地理坐标列创建 GiST 索引
CREATE INDEX idx_places_location ON places USING GIST (location);

步骤4:索引生效的查询场景

sql 复制代码
-- 1. 空间包含查询(查找指定区域内的地点)
SELECT * FROM places 
WHERE ST_DWithin(location, ST_SetSRID(ST_MakePoint(116.40, 39.90), 4326), 1000); -- 1000 米范围内

-- 2. 最近邻搜索(查找离指定坐标最近的 2 个地点)
SELECT * FROM places 
ORDER BY location <-> ST_SetSRID(ST_MakePoint(116.40, 39.90), 4326) 
LIMIT 2;

2.4 SP-GiST 索引

2.4.1 核心特性

SP-GiST(Space-Partitioned Generalized Search Tree)是另一种通用索引框架,支持非平衡磁盘数据结构(四叉树、k-d 树、基数树等),适配空间分区类查询,轻量化高效。

2.4.2 支持的操作符(以二维点类型为例)

空间关系:<<(左侧)、>>(右侧)、~=(相等)、<@(被包含)、<<|(下侧)、|>>(上侧);最近邻搜索:支持(依赖具体操作符类,常用<->距离运算符)

2.4.3 实操样例(二维点数据)

步骤1:创建二维点表并插入数据

sql 复制代码
-- 创建二维点表
CREATE TABLE points (
    id SERIAL PRIMARY KEY,
    pos POINT -- 二维点类型
);

-- 插入测试数据
INSERT INTO points (pos) VALUES 
('(1,2)'), ('(3,4)'), ('(5,6)'), ('(7,8)');

步骤2:创建 SP-GiST 索引

sql 复制代码
-- 为 pos 列创建 SP-GiST 索引
CREATE INDEX idx_points_pos ON points USING SP-GiST (pos);

步骤3:索引生效的查询场景

sql 复制代码
-- 1. 空间范围查询(查找 x 轴小于 5 的点)
SELECT * FROM points WHERE pos << '(5,5)'; -- << 表示左侧(x 更小)

-- 2. 最近邻搜索(查找离 (4,4) 最近的点)
SELECT * FROM points 
ORDER BY pos <-> '(4,4)' 
LIMIT 1;

2.5 GIN 索引

2.5.1 核心特性

GIN(Generalized Inverted Index,倒排索引)专为多值数据类型设计,为每个组成值建立独立索引项,高效支持"包含/存在"类查询,是数组、JSONB、全文检索的首选索引。

2.5.2 支持的操作符(以数组类型为例)

包含/存在:<@(被包含,如数组A <@ 数组B表示A是B的子集)、@>(包含,如数组A @> 数组B表示B是A的子集)、=(相等)、&&(相交,两数组有共同元素)

2.5.3 实操样例(数组/JSONB 场景)

场景1:数组查询

sql 复制代码
-- 1. 创建带数组列的表
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(100),
    tags TEXT[] -- 标签数组
);

-- 2. 插入测试数据
INSERT INTO articles (title, tags) VALUES
('PostgreSQL 索引', ARRAY['postgres', '数据库', '索引']),
('Python 教程', ARRAY['python', '编程', '教程']),
('GIS 空间分析', ARRAY['gis', 'postgres', '空间数据']);

-- 3. 创建 GIN 索引
CREATE INDEX idx_articles_tags ON articles USING GIN (tags);

-- 4. 索引生效的查询(包含指定标签)
SELECT * FROM articles WHERE tags @> ARRAY['postgres']; -- 包含 postgres 标签
SELECT * FROM articles WHERE tags && ARRAY['数据库', '编程']; -- 交集(包含任一)

场景2:JSONB 查询

sql 复制代码
-- 1. 创建带 JSONB 列的表
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    info JSONB -- 产品信息(JSONB 类型)
);

-- 2. 插入测试数据
INSERT INTO products (info) VALUES
('{"name": "手机", "price": 2999, "tags": ["数码", "通讯"]}'),
('{"name": "电脑", "price": 5999, "tags": ["数码", "办公"]}');

-- 3. 创建 GIN 索引
CREATE INDEX idx_products_info ON products USING GIN (info);

-- 4. 索引生效的查询(JSONB 包含键/值)
SELECT * FROM products WHERE info @> '{"tags": ["数码"]}'; -- 包含数码标签
SELECT * FROM products WHERE info ->> 'name' = '手机'; -- 等值查询(需结合操作符)

2.6 BRIN 索引

2.6.1 核心特性

BRIN(Block Range Index,块范围索引)存储表物理块范围的值摘要信息(最小值/最大值),占用空间极小,适配大数据量且物理顺序与逻辑顺序高度相关的场景(如时序日志表)。

2.6.2 支持的操作符(线性排序类型)

基础比较:<(小于)、<=(小于等于)、=(等于)、>=(大于等于)、>(大于)

2.6.3 实操样例(时序日志表)

步骤1:创建日志表并插入有序数据

sql 复制代码
-- 创建访问日志表(按时间有序插入)
CREATE TABLE access_logs (
    id SERIAL,
    user_id INT,
    access_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    ip VARCHAR(20)
);

-- 插入 10 万条测试数据(模拟时间有序的日志)
INSERT INTO access_logs (user_id, ip)
SELECT 
    floor(random() * 1000)::INT, 
    '192.168.' || floor(random() * 255)::INT || '.' || floor(random() * 255)::INT
FROM generate_series(1, 100000);

步骤2:创建 BRIN 索引

sql 复制代码
-- 为 access_time 列创建 BRIN 索引(物理有序,效率高)
CREATE INDEX idx_access_logs_time ON access_logs USING BRIN (access_time);

步骤3:索引生效的查询场景

sql 复制代码
-- 范围查询(命中 BRIN 索引,快速定位块范围)
SELECT COUNT(*) FROM access_logs 
WHERE access_time BETWEEN '2026-01-01 00:00:00' AND '2026-01-01 12:00:00';

-- 注意:若数据物理无序,BRIN 索引会失效,优先选 B-tree

2.7 扩展索引:Bloom

2.7.1 核心特性

Bloom 是 PostgreSQL 扩展提供的索引类型,基于布隆过滤器实现,适用于多列等值查询,可大幅减少多列组合索引的存储空间,但存在假阳性(需回表验证)。

2.7.2 实操样例

步骤1:启用 bloom 扩展

sql 复制代码
CREATE EXTENSION IF NOT EXISTS bloom;

步骤2:创建 Bloom 索引

sql 复制代码
-- 为 user_id 和 ip 多列创建 Bloom 索引
CREATE INDEX idx_access_logs_bloom ON access_logs 
USING bloom (user_id, ip)
WITH (length=80, col1=2, col2=4); -- length:索引长度;colN:各列的位数

步骤3:索引生效的查询场景

sql 复制代码
-- 多列等值查询(命中 Bloom 索引)
SELECT * FROM access_logs WHERE user_id = 100 AND ip = '192.168.10.20';

2.7.3 Bloom 索引 vs 传统多列组合索引(B-tree)

在多列等值查询场景中,Bloom 索引和 B-tree 组合索引是两种常见方案,但设计理念、性能、适用场景差异显著,以下是详细对比:

1. 核心原理差异
维度 Bloom 索引 B-tree 组合索引
存储结构 基于布隆过滤器,存储列的哈希值位图,不存储原始数据 基于平衡树,按列的组合顺序存储原始值(如 (col1, col2))
存储空间 极小(MB 级),仅存储哈希位图,与表行数无关 较大(GB 级),需存储所有列的原始值,随行数线性增长
索引精度 存在假阳性(可能匹配到不存在的数据,需回表验证) 精准匹配,无假阳性
2. 支持的查询场景
查询类型 Bloom 索引 B-tree 组合索引
多列全等值查询 支持(如 col1 = ? AND col2 = ? 支持(且精准)
前缀列查询 不支持(如仅查 col1 = ? 无法使用) 支持(组合索引的核心优势,如 (col1, col2) 支持仅查 col1)
范围查询 不支持(仅等值) 支持(如 col1 = ? AND col2 > ?
排序/分组 不支持 支持(按组合列排序)
3. 实操对比样例

access_logs 前提:复用前文 表(10 万条数据)

sql 复制代码
-- 场景1:创建 B-tree 组合索引(col1=user_id, col2=ip)
CREATE INDEX idx_access_logs_btree ON access_logs (user_id, ip);

-- 场景2:创建 Bloom 索引
CREATE INDEX idx_access_logs_bloom ON access_logs 
USING bloom (user_id, ip)
WITH (length=80, col1=2, col2=4);

-- 对比1:多列全等值查询
-- Bloom 索引生效(需回表验证假阳性)
EXPLAIN ANALYZE SELECT * FROM access_logs WHERE user_id = 100 AND ip = '192.168.10.20';

-- B-tree 组合索引生效(精准匹配,无需回表)
EXPLAIN ANALYZE SELECT * FROM access_logs WHERE user_id = 100 AND ip = '192.168.10.20';

-- 对比2:仅前缀列查询(Bloom 失效,B-tree 生效)
-- Bloom 索引不生效(仅查 user_id),走全表扫描
EXPLAIN ANALYZE SELECT * FROM access_logs WHERE user_id = 100;

-- B-tree 组合索引生效(前缀列匹配)
EXPLAIN ANALYZE SELECT * FROM access_logs WHERE user_id = 100;

-- 对比3:范围查询(Bloom 失效,B-tree 生效)
-- Bloom 索引不生效(ip 用范围)
EXPLAIN ANALYZE SELECT * FROM access_logs WHERE user_id = 100 AND ip LIKE '192.168.10.%';

-- B-tree 组合索引生效(前缀列等值 + 后缀列范围)
EXPLAIN ANALYZE SELECT * FROM access_logs WHERE user_id = 100 AND ip LIKE '192.168.10.%';
4. 适用场景选择
选 Bloom 索引 选 B-tree 组合索引
多列全等值查询为主 需要支持前缀列查询、范围查询、排序
表数据量极大(千万/亿级),追求极小存储成本 数据量中等,追求查询精准度和多功能性
可接受少量假阳性(回表验证的性能损耗) 对查询精度要求高,不接受假阳性
多列无明显"前缀优先级"(无单列表查询) 有明确的前缀列查询需求(如仅查 user_id)

3. 索引类型选择速查表

索引类型 核心优势 典型适用场景 核心样例语句 备注
B-tree 通用、支持排序/范围 常规等值/范围查询 CREATE INDEX idx ON tbl (col); 默认选择,覆盖 80%+ 场景
Hash 等值查询效率高 纯等值查询 CREATE INDEX idx ON tbl USING HASH (col); 功能单一,适用场景有限
GiST 复杂数据、最近邻 空间数据、全文检索 CREATE INDEX idx ON tbl USING GIST (geo_col); 需配合扩展(如 PostGIS)
SP-GiST 空间分区、轻量化 二维点/多维数据 CREATE INDEX idx ON tbl USING SP-GiST (pos_col); 非平衡结构,适配分区查询
GIN 多值数据、倒排索引 数组、JSONB、全文检索 CREATE INDEX idx ON tbl USING GIN (array_col); 多值数据首选索引
BRIN 低存储、大数据量有序表 日志表、时序数据 CREATE INDEX idx ON tbl USING BRIN (time_col); 仅适用于数据物理有序场景
Bloom 多列等值、低存储 多列组合等值查询(无前缀/范围需求) CREATE INDEX idx ON tbl USING bloom (col1, col2); 对比 B-tree 组合索引:存储小但功能单一,有假阳性

4. 总结

  1. 优先选 B-tree:无特殊需求时,B-tree 可覆盖 80% 以上的常规查询场景,是默认且最通用的选择。
  2. 专用场景选专用索引:处理空间数据用 GiST/SP-GiST,多值数据(数组/JSONB)用 GIN,大数据量有序表用 BRIN。
  3. 多列查询选对索引:需支持前缀列、范围查询 → 选 B-tree 组合索引;仅多列全等值查询、追求低存储 → 选 Bloom 索引(接受假阳性)。
  4. 样例可直接复用:文档中所有 SQL 样例均基于 PostgreSQL 14+ 编写,启用扩展(如 postgis、bloom)后可直接执行,便于快速验证索引效果。
相关推荐
运维行者_13 小时前
跨境企业 OPM:多币种订单与物流同步管理,依靠网络自动化与 snmp 软件
大数据·运维·网络·数据库·postgresql·跨境企业
茶本无香18 小时前
PostgreSQL 序列循环使用与性能优化综合指南
postgresql
光蛋1 天前
PostgreSQL 多列索引(组合索引)
postgresql
java_logo2 天前
Docker 部署 PostgreSQL 数据库教程
数据库·docker·postgresql·postgresql部署·postgresql部署文档·postgresql部署方案·postgresql部署教程
观测云2 天前
腾讯云 PostgreSQL 最佳实践
postgresql·云计算·腾讯云
小虾爬滑丫爬2 天前
postgresql使用uuid作为主键
数据库·sql·postgresql
oMcLin2 天前
如何在 Debian 11 服务器上部署并优化高可用性 PostgreSQL 集群,确保跨地域数据同步
服务器·postgresql·debian
阿楠不会敲代码2 天前
Postgresql八级锁
数据库·postgresql
Shi_haoliu2 天前
SolidTime 本地与服务器环境搭建指南(Laragon + Laravel + Vue3 + PostgreSQL)
服务器·vue.js·ai·postgresql·php·laravel