跳转至

索引类型详解

核心问题:PostgreSQL 有哪些索引类型?什么场景下选哪种索引?


它解决了什么问题?

MySQL 主要只有 B-tree 索引,而 PostgreSQL 提供了多种索引类型,针对不同数据特征做了专项优化。选对索引类型,能让查询性能提升数十倍。


索引类型全景图

mindmap
    root((PostgreSQL 索引类型))
        B-tree
            默认索引类型
            支持等值/范围/排序查询
            适用于大多数场景
        Hash
            仅支持等值查询
            比 B-tree 更快的等值查找
            不支持范围查询
        GIN 广义倒排索引
            全文检索 tsvector
            JSONB 字段查询
            数组包含查询
            多值字段
        GiST 广义搜索树
            地理信息 PostGIS
            范围类型查询
            几何图形查询
        BRIN 块范围索引
            超大表的范围查询
            数据物理有序时效果好
            索引体积极小

各索引类型适用场景

索引类型 适用场景 示例 为什么选它
B-tree 通用场景,等值/范围/排序 WHERE age > 18 ORDER BY name 最通用,大多数场景首选
Hash 仅等值查询,高频精确匹配 WHERE user_id = 12345 等值查询比 B-tree 更快,但不支持范围
GIN JSONB 字段、全文检索、数组 WHERE tags @> ARRAY['java'] 多值字段,每个值单独建索引项
GiST 地理位置、几何图形、范围类型 WHERE location <-> point < 1000 支持空间查询,B-tree 无法处理
BRIN 超大表、数据物理有序(如时间序列) 日志表按时间范围查询 索引极小(只存块范围),适合有序大表

JSONB + GIN 索引详解

为什么 GIN 适合 JSONB:JSONB 是多值字段(一个字段包含多个键值对),GIN(广义倒排索引)将每个键值对单独建立索引项,支持包含查询(@>)、键存在查询(?)等操作。B-tree 只能对整个 JSONB 值建索引,无法高效查询内部字段。

-- 创建 JSONB 字段
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    profile JSONB
);

-- 插入数据
INSERT INTO users (profile) VALUES 
('{"name": "张三", "skills": ["Java", "Redis"], "age": 28}');

-- 创建 GIN 索引(不建索引则全表扫描)
CREATE INDEX idx_profile_gin ON users USING GIN (profile);

-- ✅ 高效查询:查找 skills 包含 Java 的用户(走 GIN 索引)
SELECT * FROM users WHERE profile @> '{"skills": ["Java"]}';

-- ✅ 查询 JSON 字段值
SELECT profile->>'name' AS name FROM users WHERE profile->>'age' = '28';

-- ❌ 未建索引时,以下查询会全表扫描
SELECT * FROM users WHERE profile->>'name' = '张三';
-- ✅ 建立表达式索引解决
CREATE INDEX idx_profile_name ON users ((profile->>'name'));

BRIN 索引适用场景

-- 日志表:按时间顺序插入,数据物理有序
CREATE TABLE access_logs (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMP,
    user_id INT,
    action TEXT
);

-- BRIN 索引:只存每个数据块的时间范围,索引极小
CREATE INDEX idx_logs_brin ON access_logs USING BRIN (created_at);

-- 按时间范围查询,BRIN 索引高效过滤数据块
SELECT * FROM access_logs 
WHERE created_at BETWEEN '2024-01-01' AND '2024-01-31';

BRIN 的原理:不记录每行的索引值,只记录每个数据块(Page)中的最小值和最大值。查询时跳过不在范围内的数据块,大幅减少 IO。索引体积是 B-tree 的 1/1000,但只适合数据物理有序的场景。


工作中的坑

错误 原因 解决方案
JSONB 查询慢 未建 GIN 索引 CREATE INDEX USING GIN (jsonb_col)
GIN 索引写入慢 GIN 维护成本高,每次写入都要更新倒排索引 批量写入,或使用 fastupdate 参数
BRIN 索引不生效 数据插入顺序不规律,物理无序 改用 B-tree,BRIN 只适合物理有序数据

面试高频问题

Q:GIN 索引和 B-tree 索引有什么区别?

B-tree 适合单值字段的等值/范围/排序查询,是最通用的索引类型;GIN(广义倒排索引)适合多值字段,如 JSONB、数组、全文检索。GIN 将每个元素单独建立索引项,支持包含查询(@>)、交集(&&)等操作。代价是写入时维护成本更高。

Q:什么场景下用 BRIN 索引?

BRIN 适合超大表且数据物理有序的场景,如按时间顺序插入的日志表。BRIN 只存每个数据块的值范围,索引体积极小(是 B-tree 的 1/1000),但只有数据物理有序时才高效。