选择正确的索引:PostgreSQL 索引类型指南
为等值查询、范围查询、JSONB、数组、全文搜索、空间查询和大规模时间序列查询选择合适的 PostgreSQL 索引类型。
选择正确的索引:PostgreSQL 索引类型指南
错误的 PostgreSQL 索引会浪费磁盘空间、拖慢写入速度,并且仍然会让你的查询扫描数百万行。正确的索引取决于 WHERE 子句中的运算符、列类型以及数据的形状。
从 B-tree 开始,用于普通的等值和范围查找。当你的查询模式需要特定的运算符支持时,再考虑使用 GIN、GiST、BRIN 或 SP-GiST。
索引在 PostgreSQL 中的重要性
在 PostgreSQL 中,索引的核心作用是减少查询时需要检查的数据量。没有索引,PostgreSQL 对许多查询将不得不执行全表扫描,这对于大表来说可能非常缓慢。索引创建了一种数据结构,使数据库能够快速定位相关行。索引的有效性在很大程度上取决于:
- 使用的索引类型: 不同的索引类型适用于不同的数据结构和查询操作。
- 数据分布: 倾斜的数据会影响索引性能。
- 查询模式: 你如何查询数据是一个重要因素。
以下是您最常需要在其中进行选择的索引类型。
PostgreSQL 索引类型详解
PostgreSQL 提供了几种索引类型。日常性能优化中最有用的是 B-tree、GIN、GiST、BRIN 和 SP-GiST。
1. B-Tree 索引
B-tree 是 PostgreSQL 的默认且最通用的索引类型。它适用于常见的比较运算符,包括 =、<、>、<= 和 >=。B-tree 索引非常适合等值检查、范围扫描、排序、唯一约束和主键。
工作原理: B-Tree 索引将数据存储在排序的树结构中。树中的每个节点都包含键和指向子节点的指针。这种结构确保了搜索、插入和删除数据的高效性,通常具有对数时间复杂度。
使用场景:
- 等值搜索(
WHERE column = value) - 范围查询(
WHERE column BETWEEN value1 AND value2或WHERE column > value) - 排序(
ORDER BY column) - 查找最小值或最大值(
ORDER BY column LIMIT 1) - 唯一约束和主键(隐式使用 B-Tree)
示例:
考虑一个包含数百万条记录的 users 表。使用 B-Tree 索引 email 列将显著加快通过电子邮件地址查找特定用户的速度。
CREATE INDEX idx_users_email ON users (email);
-- 现在,像这样的查询会快得多:
SELECT * FROM users WHERE email = '[email protected]';
提示: B-Tree 索引通常是一个很好的起点,并且对于许多常见的数据库操作来说通常已经足够。但是,对于全文搜索或地理空间数据等特定用例,其他索引类型可能性能更佳。
2. GIN(广义倒排索引)索引
GIN 索引专为索引复合值或包含多个项的值而设计,例如数组、JSON 文档或全文搜索文档(tsvector)。它们对于搜索这些复合值中是否存在特定元素的查询特别有效。
工作原理: GIN 索引将复合值中的每个元素映射到包含该元素的行列表。它是一个倒排索引,意味着它索引的是值本身,而不是直接索引行。这使得它能够高效地检查某个特定项是否存在于更大的结构中。
使用场景:
- 全文搜索(
tsvectorvs.tsquery) - 索引数组(
ANY、@>运算符) - 索引 JSONB 数据(
?、?|、?&、@>、<@运算符)
示例:
假设你有一个 documents 表,其中包含一个字符串数组类型的 tags 列。你想找到所有标记为 'database' 的文档。
CREATE INDEX idx_documents_tags ON documents USING GIN (tags);
-- 查询带有 'database' 标签的文档:
SELECT * FROM documents WHERE tags @> ARRAY['database'];
-- 或者用于 JSONB:
CREATE TABLE products (id SERIAL PRIMARY KEY, details JSONB);
CREATE INDEX idx_products_details ON products USING GIN (details);
SELECT * FROM products WHERE details ? 'manufacturer';
注意: GIN 索引的更新速度可能比 B-Tree 索引慢,因为它们需要重新索引每个元素。但是,对于涉及复合类型内元素的搜索,它们提供了卓越的查询性能。
3. GiST(广义搜索树)索引
GiST 索引是一个框架,允许创建自定义索引类型。它们通常用于索引几何数据类型和全文搜索。当数据复杂且不适合 B-Tree 结构时,GiST 索引特别有用。
工作原理: GiST 是一种高度灵活的索引方法。它通过递归划分数据空间来工作。虽然内部结构可能因使用的特定运算符类而异,但它通常以树状结构组织数据。
使用场景:
- 几何数据类型(点、线、多边形)用于空间查询(
&&、@>)。 - 范围索引。
- 使用 GiST 运算符类进行全文搜索。
示例:
对于空间索引,假设有一个兴趣点(POI)表,你想找到某个地理区域内的所有 POI。
CREATE TABLE pois (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
location GEOMETRY(Point, 4326) -- 使用 PostGIS 扩展
);
-- 在 location 列上创建 GiST 索引
CREATE INDEX idx_pois_location ON pois USING GIST (location);
-- 查找边界框内的 POI(使用 PostGIS 函数的示例)
SELECT * FROM pois WHERE ST_Intersects(location, ST_MakeEnvelope(lon1, lat1, lon2, lat2, 4326));
提示: GiST 索引对于复杂数据类型和空间查询非常强大。它们也可以用于部分索引,仅索引基于条件的行子集,这可以进一步优化性能。
4. BRIN(块范围索引)索引
BRIN 索引专为非常大的表设计,这些表的数据与其在磁盘上的物理存储位置具有自然的相关性。它们通过索引物理块地址的范围而不是单个行的值来工作。这使得它们非常小且创建速度快,但仅当索引列的值与其物理顺序相关时才有效。
工作原理: BRIN 索引存储表块范围的最小值和最大值。查询时,PostgreSQL 检查块范围的最小/最大值。如果查询条件落在此范围之外,则跳过整个块范围,从而避免全表扫描。这对于自然排序的数据(如时间戳或序列 ID)最有效。
使用场景:
- 非常大的表。
- 与其物理存储顺序有很强自然相关性的列(例如,
created_at时间戳、自增 ID)。 - 当块中的值范围明显小于该块中的行数时。
示例:
考虑一个包含数十亿条条目的日志表,按 timestamp 排序。
CREATE TABLE logs (
id BIGSERIAL PRIMARY KEY,
message TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 在 created_at 上创建 BRIN 索引
CREATE INDEX idx_logs_created_at ON logs USING BRIN (created_at);
-- 查询特定日期的日志:
SELECT * FROM logs WHERE created_at >= '2023-10-26 00:00:00' AND created_at < '2023-10-27 00:00:00';
警告: BRIN 索引仅在数据物理排序时才有效。如果数据以随机顺序插入,或者列值与其物理位置不相关,BRIN 索引将不会提供显著的性能提升,甚至可能降低性能。可以调整 pages_per_range 参数以优化 BRIN 索引效率。
5. SP-GiST(空间分区广义搜索树)索引
SP-GiST 是另一种广义搜索树,类似于 GiST,但针对以不平衡方式划分空间的算法进行了优化。它特别适用于索引非均匀数据分布和复杂的空间数据结构,如四叉树或 k-d 树。
工作原理: SP-GiST 使用多种分区策略,使其能够适应不同的数据类型和查询模式。对于某些类型的数据,尤其是在处理高度聚集或稀疏分布的数据集时,它可能比 GiST 更高效。
使用场景:
- 使用 k-d 树或四叉树的点数据。
- 网络数据。
- 地理空间数据。
- 文本搜索。
示例:
虽然通常用于复杂的几何结构,但一个常见的用例涉及索引大量的点。
-- 假设有一个包含点坐标的表
CREATE TABLE points (id SERIAL PRIMARY KEY, coord POINT);
-- 创建 SP-GiST 索引
CREATE INDEX idx_points_coord ON points USING SPGIST (coord);
-- 查询某个区域内的点
SELECT * FROM points WHERE coord <@ box '((x1,y1),(x2,y2))';
考虑因素: SP-GiST 索引可以为特定的数据结构和查询模式提供性能优势,而传统的 B-Tree 甚至 GiST 可能难以胜任。然而,它们的复杂性意味着它们通常不是首选,除非特定的基准测试表明有好处。
其他索引类型(简要介绍)
- 哈希索引: 仅支持等值比较(
=)。在现代 PostgreSQL 版本中,它们会被记录到 WAL 日志中,但 B-tree 索引仍然是通常的首选,因为它们支持更多运算符和排序。 - 部分索引: 这些索引仅索引满足
WHERE子句的表行子集。如果查询经常针对特定的数据子集,它们可以节省空间并提高性能。 - 表达式索引: 您可以基于一个或多个列的表达式或函数创建索引。这对于在
WHERE子句中频繁使用这些表达式的查询非常有用,例如lower(email)。
何时使用哪种索引类型?
选择正确的索引是 PostgreSQL 性能调优的关键部分。以下是一个快速指南,可帮助您做出决定:
| 索引类型 | 最适合用于 | 支持的运算符 | 注意事项 |
|---|---|---|---|
| B-Tree | 通用目的、等值、范围、排序 | =, <, >, <=, >= |
默认、通用、良好的全能型。 |
| GIN | 全文搜索、数组、JSONB、复合类型 | @@, @>, <@, ?, `? |
, ?&` |
| GiST | 空间数据、几何类型、全文搜索 | &&, @>, <@, @@(以及通过运算符类的其他运算符) |
灵活,适用于复杂数据结构,可能比 B-Tree 慢。 |
| BRIN | 具有物理相关数据的非常大的表 | <, >, <=, >=, = |
体积小,创建快,仅对有序数据相关有效。 |
| SP-GiST | 非均匀数据、复杂空间结构 | 因运算符类而异(例如,空间、网络) | 对于某些分区策略高效,调整起来可能更复杂。 |
需要考虑的因素:
- 查询模式: 你最常运行哪种类型的查询?是等值检查、范围扫描、全文搜索还是空间查询?
- 数据类型: 被索引的数据类型(例如,字符串、数字、数组、JSON、几何点)在很大程度上影响最佳索引选择。
- 数据分布: 你的数据是自然排序的(如时间戳)还是随机分布的?
- 更新频率: 索引列中的数据多久更新一次?GIN 和 GiST 索引的更新速度可能比 B-Tree 慢。
- 表大小: 对于非常大的表,如果存在数据相关性,BRIN 索引可能具有优势。
- 索引大小和维护: 考虑索引所需的磁盘空间以及维护它的开销。
创建和管理索引
PostgreSQL 提供了用于管理索引的简单 SQL 命令:
创建索引:
CREATE INDEX index_name ON table_name USING index_type (column_name [ASC|DESC] [NULLS FIRST|LAST], ...);删除索引:
DROP INDEX index_name;查看现有索引:
\d+ table_name;
最佳实践: 在将更改应用到生产环境之前,始终在暂存环境中测试创建或更改索引的性能影响。使用 EXPLAIN ANALYZE 来了解你的查询如何使用索引。
要点
选择与你的运算符和数据形状匹配的索引,然后使用 EXPLAIN ANALYZE 进行验证。索引也属于写入路径的一部分,因此保留那些服务于实际查询的索引,并移除那些只会增加维护成本的索引。