PostgreSQL 索引特色功能
函数索引
与 Oracle 数据库类似,PostgreSQL 也支持函数索引。实际上,PostgreSQL 索引的键除了可以是一个函数外,还可以是从一个或多个字段计算出来的标量表达式(MySQL 8.0 也支持部分函数索引)。
问题场景
经常需要对一个表的字段做大小写无关比较时,常用的方法是使用 lower 函数:
sql
SELECT * FROM mytest WHERE lower(note) = 'hello world';
但因为使用了函数,无法利用到 note 字段上的普通索引。
示例
sql
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name VARCHAR(50),
age INTEGER
);
INSERT INTO students VALUES (2, 'hello world', 19);
INSERT INTO students VALUES (3, 'hello world11', 13);
INSERT INTO students VALUES (4, 'hello awardld11', 20);
-- 创建函数索引
CREATE INDEX mytest_lower_name_idx ON students (lower(name));
-- 查看执行计划
EXPLAIN SELECT * FROM students WHERE lower(name) = 'hello world';
部分索引
部分索引只对一个表中的部分行进行索引,由条件表达式(谓词)筛选出这部分行。
案例1:IP 访问系统
- 内网 IP 固定、访问量大,外网 IP 多样。
- 需求:经常查询外网访问记录。
建表与索引
sql
CREATE TABLE access_log (
url VARCHAR,
client_ip inet
);
-- 部分索引:排除内网 IP
CREATE INDEX access_log_client_ip_ix ON access_log (client_ip)
WHERE NOT (client_ip > inet '192.168.100.0' AND client_ip < inet '192.168.100.255');
查询示例
sql
-- 外网 IP 查询,可以使用部分索引
SELECT * FROM access_log WHERE client_ip = inet '114.113.220.27';
-- 内网 IP 查询,不会走部分索引
SELECT * FROM access_log WHERE client_ip = inet '192.168.100.55';
案例2:排除不感兴趣的数值
假设订单表中未付款订单占少数且频繁查询,可以创建部分索引优化。
sql
CREATE TABLE orders (
order_nr INT,
amount DECIMAL(12,2),
billed BOOLEAN
);
-- 创建部分索引
CREATE INDEX orders_unbilled_index ON orders (order_nr) WHERE billed IS NOT TRUE;
插入数据
sql
INSERT INTO orders
SELECT t.seq, t.seq + 2.44, TRUE
FROM generate_series(1, 500000) AS t(seq);
INSERT INTO orders
SELECT t.seq, t.seq + 2.44, FALSE
FROM generate_series(500001, 1000000) AS t(seq);
查询示例
sql
-- 使用索引
EXPLAIN SELECT * FROM orders WHERE billed IS NOT TRUE AND order_nr < 10000;
-- 不使用索引
EXPLAIN SELECT * FROM orders WHERE billed IS TRUE AND order_nr < 10000;
-- 也可用于不涉及索引键的查询
EXPLAIN SELECT * FROM orders WHERE billed IS NOT TRUE AND amount > 40105960.00;
注意事项
谓词必须与查询条件完全匹配,系统无法识别数学上等价但形式不同的谓词。
sql
-- 以下查询不会使用索引
EXPLAIN SELECT * FROM orders WHERE (NOT billed) AND order_nr < 10000;
案例3:部分唯一索引
sql
CREATE TABLE tests (
subject TEXT,
target TEXT,
success BOOLEAN
);
CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target) WHERE success;
GiST 索引
GiST 是 Generalized Search Trees 的缩写,是一种平衡树结构的访问方法,是用户建立自定义索引的基础模板。
特点
- 底层为平衡树结构。
- 支持
@>、&&等复杂运算符,而 BTree 只支持<、=、>。 - 用户只需实现一组回调函数即可自定义索引。
支持 GiST 的操作函数
consistent:判断索引项是否与查询相容union:组合多个索引项compress/decompress:压缩与解压缩索引项penalty:计算插入代价picksplit:决定索引分裂策略same:判断两个索引项是否相等distance:计算索引项与查询值之间的距离fetch:将压缩索引项转换为原始数据项
案例:Point 类型 GiST 索引
sql
CREATE TABLE pts (id INT, p POINT);
INSERT INTO pts
SELECT t.d, point(ceil(random() * 1000), ceil(random() * 1000))
FROM generate_series(1, 1000000) AS t(d);
-- 无索引查询
EXPLAIN ANALYZE SELECT * FROM pts WHERE circle '((100,100) 100)' @> p;
-- 创建 GiST 索引
CREATE INDEX ON pts USING gist(p);
-- 再次查看执行计划
EXPLAIN ANALYZE SELECT * FROM pts WHERE circle '((100,100) 100)' @> p;
案例:inet 类型 GiST 索引
sql
CREATE TABLE vector (id INT, ip INET);
INSERT INTO vector
SELECT t.d, inet(ceil(random() * 255) || '.' || ceil(random() * 255) || '.' || ceil(random() * 255) || '.' || ceil(random() * 255))
FROM generate_series(1, 1000000) AS t(d);
-- BTree 索引
CREATE INDEX vector_btree_inx ON vector USING btree(ip);
EXPLAIN ANALYZE SELECT * FROM vector WHERE ip = '77.80.250.123'::inet;
-- GiST 索引
CREATE INDEX vector_gist_inx ON vector USING gist(ip inet_ops);
EXPLAIN ANALYZE SELECT * FROM vector WHERE ip = '77.80.250.123'::inet;
-- 子网查询(BTree 不支持 << 操作符)
EXPLAIN ANALYZE SELECT * FROM vector WHERE ip << '192.0.2.0/24';
总结:GiST 查询效率与 BTree 相当,但支持更多复杂运算符。
SP-GiST 索引
SP-GiST 表示空间分区 GiST,适用于可递归分割为非相交区域的结构(如四叉树、k-d 树、基数树)。
创建方法
sql
CREATE TABLE test01 (p POINT);
CREATE INDEX ON test01 USING spgist (p);
GIN 索引
GIN 主要用于加速全文搜索。
案例:全文搜索
sql
CREATE TABLE ts (doc TEXT, doc_tsv TSVECTOR);
INSERT INTO ts(doc) VALUES
('Can a sheet slitter slit sheets?'),
('How many sheets could a sheet slitter slit?'),
('I slit a sheet, a sheet I slit.'),
('Upon a slitted sheet I slit.'),
('Whoever slit the sheets is a good sheet slitter.'),
('I am a sheet slitter.'),
('I slit sheets.'),
('I am the sleekest sheet slitter that ever slit sheets.'),
('She slits the sheet she sits on.');
-- 更新 TSVECTOR 列
UPDATE ts SET doc_tsv = to_tsvector(doc);
-- 创建 GIN 索引
CREATE INDEX ON ts USING gin(doc_tsv);
-- 禁用全表扫描
SET enable_seqscan TO off;
-- 全文搜索
EXPLAIN ANALYZE SELECT doc FROM ts WHERE doc_tsv @@ to_tsquery('many & slitter');
GIN 索引优缺点
优点:
- 适合模糊查询、正则查询、全文搜索。
缺点:
- 插入/更新效率低。
- 建议批量插入时先删除索引,插入后再重建。
- 增大
maintenance_work_mem可加速索引创建。
BRIN 索引
BRIN 是 Block Range Index 的缩写,将相邻磁盘块组合,计算每组的取值范围,适合数值线性增长、删除较少的列。
特点
- 按数据块分组(默认每组 128 页)。
- 查找时先排除范围外的分组,再用位图扫描。
- 空间占用小,性能低于 BTree。
示例
sql
CREATE TABLE test01 (id INT, info TEXT);
CREATE INDEX idx_test01_id ON test01 USING brin(id);
适用场景
- 数据按顺序插入
- 列值线性增长
- 删除操作较少
总结
| 索引类型 | 适用场景 | 特点 |
|---|---|---|
| 函数索引 | 函数/表达式查询 | 支持计算列索引 |
| 部分索引 | 部分数据频繁查询 | 减少索引大小 |
| GiST | 空间、范围、复杂操作符 | 支持自定义索引 |
| SP-GiST | 空间分区结构 | 递归分割存储 |
| GIN | 全文搜索、数组、JSON | 反向索引 |
| BRIN | 大表、顺序插入、范围查询 | 按块索引,空间小 |