PostgreSQL笔记16: 索引基础——概念、类型、创建与最佳实践

一分钟了解下PostgreSQL中的6种索引 在PostgreSQL数据库系统中,有多种不同的索引类型可供选择,每种类型都具有不同的特点和适用场景。 阅读详情

概述

索引是关系型数据库中最为核心的性能优化手段。对于查询操作而言,索引如同书籍的目录,能够帮助数据库快速定位到目标数据所在的位置,而无需逐页翻阅整本“书”。在没有索引的情况下,PostgreSQL 必须执行顺序扫描(Sequential Scan)——即逐行遍历整个表,直到找到匹配的行。随着表数据量的增长,这种扫描方式的效率呈线性下降。

本文将从索引的基本概念、常见类型、创建语法及注意事项入手,帮助读者建立索引知识的基础框架。后续文章将深入探讨索引的底层实现原理(如 B-Tree 结构、页分裂机制)以及更高级的优化策略。

索引的作用与性能对比

顺序扫描 vs. 索引扫描

在没有索引的情况下,PostgreSQL 对表的查询只能采用顺序扫描方式,即从表的第一个数据块开始,逐行读取直至末尾。这种方式的时间复杂度为 O ( n ) O(n) O(n),其中 n n n 为表的数据行数。

而有了索引之后,查询优化器可以选择索引扫描(Index Scan),通过索引结构快速定位到目标行的物理存储位置(即 CTID,包含数据块编号与块内偏移量),从而将查询复杂度降低至 O ( log ⁡ n ) O(\log n) O(logn) 级别。

性能对比演示

以下通过一个简单的实验来直观感受索引带来的性能提升:

-- 开启查询计时
\timing on

-- 创建测试表
CREATE TABLE test_index (
    id INT
);

-- 插入100万行数据(利用 generate_series)
INSERT INTO test_index (id)
SELECT generate_series(1, 1000000);

-- 无索引时的查询(顺序扫描)
SELECT * FROM test_index WHERE id = 99999;
-- 执行时间示例:约 55 ms(取决于硬件配置)

此时表中尚无索引,PostgreSQL 只能进行顺序扫描。接下来创建索引并再次执行相同的查询:

-- 创建 B-Tree 索引(默认索引类型)
CREATE INDEX idx_test_index_id ON test_index (id);

-- 再次执行查询
SELECT * FROM test_index WHERE id = 99999;
-- 执行时间示例:约 0.5 ms

从 55 ms 降至 0.5 ms,性能提升超过 100 倍。随着数据量的进一步增长和查询条件的复杂化,这一差距会更加显著。

CTID:索引与数据行的纽带

PostgreSQL 中每个表行都有一个隐式的系统列 CTID,它记录了该行所在的数据块编号(Block Number) 与块内偏移量(Offset)。索引中存储的并非完整的数据行,而是“索引键值 + 对应的 CTID”这样一个键值对。当查询通过索引找到匹配的键值后,数据库便可根据 CTID 直接定位到数据行的物理存储位置,从而完成数据的检索。

索引类型

PostgreSQL 提供了多种索引方法(Index Methods),包括 B-tree、Hash、GiST、SP-GiST、GIN、BRIN 以及扩展模块中的 Bloom。默认情况下,CREATE INDEX 命令创建的是 B-tree 索引,它适用于绝大多数常见场景。

B-Tree 索引

B-tree(多路平衡树)是 PostgreSQL 的默认索引类型。任何可以被排序为明确定义的线性顺序的数据类型都可以使用 B-tree 索引。

B-tree 索引支持以下运算符的查询条件:

  • 等值比较:=
  • 范围比较:<、<=、>、>=
  • 组合条件:BETWEEN、IN
  • IS NULL 与 IS NOT NULL
  • 前缀匹配的模式查询:LIKE 'foo%' 或 ~ '^foo'(需注意 locale 设置)

B-tree 索引还可以用于返回已排序的数据,在某些场景下可避免额外的显式排序操作。

PostgreSQL B-tree 索引是一种多级树结构,树的每一层均可作为页面的双向链表。索引的第一个段文件起始位置存储着一个固定的元页面(Metapage)。树的最底层为叶子页面(Leaf Pages),存储指向表行的指针(即 CTID);其余层级为内部页面(Internal Pages),存储指向下一层级的指针。通常情况下,超过 99% 的页面为叶子页面。

Hash 索引

Hash 索引存储的是索引列值的 32 位哈希码,因此仅支持等值比较(= 运算符)。它不适用于范围查询或排序操作。

GiST、SP-GiST、GIN、BRIN

这些索引类型分别适用于不同的特殊场景:

  • GiST(Generalized Search Tree):一种通用的索引基础设施,支持几何数据类型、全文搜索、最近邻搜索等。
  • SP-GiST(Space-Partitioned GiST):适用于非平衡数据结构,如四叉树、kd-tree 等。
  • GIN(Generalized Inverted Index):通用倒排索引,适用于数组、全文搜索等包含多个键值的数据类型。
  • BRIN(Block Range Index):块范围索引,适用于数据物理分布与值域高度相关的超大表。

常见索引类型详解

主键索引(Primary Key Index)

主键约束会自动在对应列上创建一个唯一索引。主键索引具有以下特性:

  • 值必须唯一(不可重复)
  • 值不可为空(NOT NULL)
CREATE TABLE test_pk (
    id INT PRIMARY KEY
);

-- 查看表上的索引
\d test_pk
-- 输出中可见 "test_pk_pkey" PRIMARY KEY, btree (id)

尝试插入重复值或空值将触发错误:

INSERT INTO test_pk VALUES (1);  -- 成功
INSERT INTO test_pk VALUES (1);  -- ERROR: duplicate key value violates unique constraint
INSERT INTO test_pk VALUES (NULL);  -- ERROR: null value in column "id" violates not-null constraint

唯一索引(Unique Index)

唯一索引与主键索引类似,要求列值唯一,但允许为空值(NULL):

CREATE TABLE test_unique (
    id INT
);

CREATE UNIQUE INDEX idx_unique_id ON test_unique (id);

INSERT INTO test_unique VALUES (1);    -- 成功
INSERT INTO test_unique VALUES (1);    -- ERROR: duplicate key value violates unique constraint
INSERT INTO test_unique VALUES (NULL); -- 成功
INSERT INTO test_unique VALUES (NULL); -- 成功(NULL 不等于 NULL)

需要注意的是,在 SQL 标准中,NULL 不等于任何值(包括 NULL 本身),因此唯一索引允许存在多个 NULL 值。

表达式索引(Expression Index / Functional Index)

表达式索引(亦称函数索引)的索引列不一定是表的原始列,而是基于一个或多个列计算的函数或标量表达式。当查询条件中使用了相同的表达式时,优化器便可利用该索引。

适用场景:查询中经常对列应用函数(如 LOWER、UPPER、日期截断等),且这些函数为不可变(IMMUTABLE) 的。

CREATE TABLE test_expr (
    info TEXT
);

-- 常规索引无法支持大小写不敏感的查询
CREATE INDEX idx_info ON test_expr (info);

-- 查询无法使用 idx_info(因为条件中使用了函数)
SELECT * FROM test_expr WHERE LOWER(info) = 'hello';

-- 创建表达式索引
CREATE INDEX idx_info_lower ON test_expr (LOWER(info));

-- 此时查询可以使用 idx_info_lower
SELECT * FROM test_expr WHERE LOWER(info) = 'hello';

表达式索引的语法要求:当表达式仅为函数调用时,括号可省略;否则需要用括号将表达式括起。

-- 函数调用:括号可省略
CREATE INDEX idx1 ON test_expr (LOWER(info));

-- 一般表达式:必须加括号
CREATE INDEX idx2 ON test_expr ((info || 'suffix'));

表达式索引的维护成本相对较高,因为每次插入或更新(非 HOT 更新)时都需要重新计算表达式值。但在查询时,表达式值已存储在索引中,无需重新计算。

多列索引(Multicolumn Index)

多列索引是在表的多个列上定义的索引。当查询条件同时涉及这些列时,多列索引可以一次性过滤多个条件,避免多次索引扫描或回表操作。

CREATE TABLE test_multi (
    id INT,
    info TEXT
);

-- 创建多列索引(最多支持 32 列)
CREATE INDEX idx_multi ON test_multi (id, info);

-- 以下查询可以利用该索引
SELECT * FROM test_multi WHERE id = 100 AND info = 'xxx';
SELECT * FROM test_multi WHERE id = 100;  -- 也可利用(最左前缀原则)
-- 以下查询无法利用该索引(未使用索引的最左列)
SELECT * FROM test_multi WHERE info = 'xxx';

多列索引遵循最左前缀匹配原则:查询条件必须包含索引定义中最左侧的列,才能使用该索引。在设计多列索引时,应将选择性较高(即唯一值较多)的列放在前面。

部分索引(Partial Index)

部分索引(Partial Index)是在表的子集上建立的索引,该子集由一个条件表达式(称为谓词 Predicate)定义。索引中仅包含满足该谓词的行。

适用场景:

  1. 排除常见值:如果某个值出现在大多数行中,查询优化器通常不会使用索引(因为选择性太低),将这些行排除在索引之外可以减小索引体积。
  2. 仅索引热点数据:如果业务查询只关注表中的一小部分数据(如“未结订单”),可仅对该子集建立索引。
CREATE TABLE test_partial (
    id INT
);

-- 插入100万行数据(假设大部分查询只关注 id < 50000 的行)
INSERT INTO test_partial (id) SELECT generate_series(1, 1000000);

-- 创建部分索引:仅索引 id < 50000 的行
CREATE INDEX idx_partial ON test_partial (id) WHERE id < 50000;

-- 查询可以使用该部分索引
SELECT * FROM test_partial WHERE id = 100;

-- 查询无法使用该部分索引(id 不在索引的谓词范围内)
SELECT * FROM test_partial WHERE id = 60000;

部分索引的优势在于节省存储空间和降低维护成本——索引更新操作仅需在满足谓词条件时进行。

索引的创建与删除

创建索引

CREATE INDEX 命令的基本语法如下:

CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ]
    ON table_name [ USING method ]
    ( { column_name | ( expression ) } [ ASC | DESC ] [, ...] )
    [ WHERE predicate ];

各参数说明:

  • UNIQUE:创建唯一索引,系统会在索引创建时及每次数据变更时检查重复值。
  • CONCURRENTLY:在线创建索引,不阻塞表的并发写入操作。
  • IF NOT EXISTS:若同名索引已存在,则不抛出错误。
  • USING method:指定索引方法,默认为 btree。
  • WHERE predicate:创建部分索引的谓词条件。

命名规则:若未显式指定索引名称,PostgreSQL 会自动生成一个名称,规则为 {表名}_{列名}_idx(或类似模式)。需要注意的是,PostgreSQL 中的标识符(包括索引名)最大长度为 63 个字符,超长名称会被自动截断。

删除索引

DROP INDEX index_name;

由于 PostgreSQL 中索引名在同一 Schema 下是唯一的,因此无需指定表名。

索引的维护成本与创建时机

索引的两面性

索引虽然能大幅提升查询性能,但并非“多多益善”。索引的维护成本体现在以下方面:

  1. 存储空间:索引本身需要占用额外的磁盘空间。
  2. 写入性能损耗:每次 INSERT、UPDATE、DELETE 操作都需要同步更新索引。更新操作即使只修改了未被索引的列,也可能需要更新索引中的物理指针(指向新行版本)。
  3. MVCC 版本链:在 PostgreSQL 的 MVCC 机制下,每次更新都会产生新的行版本,索引也需要为每个新版本维护对应的索引项,这可能导致“版本抖动(Version Churn)”。

何时应该创建索引

以下场景适合创建索引:

  • 高选择性列:列中唯一值较多(如身份证号、邮箱、ID 等),索引能够有效过滤大部分数据。
  • **频繁出现在 WHERE、JOIN、ORDER BY、GROUP BY 子句中的列。
  • 稀疏访问模式:业务查询仅关注表中的部分数据,适合使用部分索引。

以下场景不适合创建索引:

  • 低选择性列:列中唯一值极少(如性别,只有“男”/“女”),索引无法有效过滤数据,反而增加了维护开销。
  • 频繁更新的表:索引的维护成本可能超过查询收益。
  • 小表:顺序扫描的开销本身很低,索引带来的收益微乎其微。

在线创建索引:CREATE INDEX CONCURRENTLY

默认情况下,CREATE INDEX 会持有表上的排他锁(Access Exclusive Lock),阻塞所有写入操作(INSERT、UPDATE、DELETE),直到索引创建完成。对于生产环境中的大表,这可能导致长时间的写入阻塞。

PostgreSQL 提供了 CONCURRENTLY 选项来在线创建索引:

CREATE INDEX CONCURRENTLY idx_name ON table_name (column);

使用 CONCURRENTLY 时,PostgreSQL 不会阻塞表的并发写入操作。但需要注意以下限制:

  1. 创建速度较慢:需要执行两次表扫描,并等待所有可能修改或使用该索引的事务结束。
  2. 创建失败时可能留下无效索引,需要手动清理。
  3. 不能在事务块内执行。

API 速览

本节梳理了本文涉及的核心 PostgreSQL SQL 命令与系统功能。

CREATE INDEX

属性说明
所属PostgreSQL DDL 命令
语法CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON table_name [ USING method ] ( { column_name | ( expression ) } [ ASC | DESC ] [, ...] ) [ WHERE predicate ];
参数UNIQUE:唯一索引;CONCURRENTLY:在线创建;IF NOT EXISTS:条件创建;USING method:索引方法(默认 btree);WHERE predicate:部分索引谓词
返回值无(DDL 命令)
适用版本PostgreSQL 8.0+(CONCURRENTLY 自 8.2 引入,持续增强)

示例:

-- 基本 B-Tree 索引
CREATE INDEX idx_employees_last_name ON employees (last_name);

-- 唯一索引
CREATE UNIQUE INDEX idx_employees_email ON employees (email);

-- 表达式索引
CREATE INDEX idx_employees_lower_email ON employees (LOWER(email));

-- 多列索引
CREATE INDEX idx_employees_name_dept ON employees (last_name, department_id);

-- 部分索引
CREATE INDEX idx_employees_active ON employees (last_name) WHERE status = 'ACTIVE';

-- 在线创建索引
CREATE INDEX CONCURRENTLY idx_employees_hire_date ON employees (hire_date);

DROP INDEX

属性说明
所属PostgreSQL DDL 命令
语法DROP INDEX [ CONCURRENTLY ] [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ];
参数CONCURRENTLY:在线删除;IF EXISTS:条件删除;CASCADE:级联删除依赖对象
返回值无(DDL 命令)
适用版本PostgreSQL 8.0+

示例:

DROP INDEX idx_employees_last_name;
DROP INDEX CONCURRENTLY IF EXISTS idx_employees_hire_date;

\d — 查看表结构(psql 元命令)

属性说明
所属psql 客户端元命令
语法\d table_name
功能显示指定表的列信息、索引、约束等

示例:

\d test_index
-- 输出示例:
--         Table "public.test_index"
--  Column |  Type   | Collation | Nullable | Default
-- --------+---------+-----------+----------+---------
--  id     | integer |           |          |
-- Indexes:
--     "idx_test_index_id" btree (id)

系统列 CTID

属性说明
所属PostgreSQL 系统列(隐式)
类型tid(Tuple ID)
内容数据块编号(Block Number)+ 块内偏移量(Offset)
用途索引中存储的物理行指针,用于精确定位数据行

示例:

SELECT ctid, * FROM test_index WHERE id = 99999;
-- ctid 示例输出:(100, 5) 表示第100个数据块的第5个偏移位置

Demo 简单示例

以下是一个完整的 Node.js 示例,演示了 PostgreSQL 索引的创建、查询性能对比以及各类索引的使用。

运行说明

环境要求:

  • Node.js 16+
  • PostgreSQL 12+
  • 已安装 pg 包

安装依赖:

npm init -y
npm install pg

启动方式:

node index.js

完整代码

const { Client } = require('pg');

// PostgreSQL 连接配置
const client = new Client({
    host: 'localhost',
    port: 5432,
    database: 'testdb',
    user: 'postgres',
    password: 'postgres',
});

async function runDemo() {
    await client.connect();
    console.log('Connected to PostgreSQL');

    try {
        // ============================================================
        // 1. 创建测试表并插入数据
        // ============================================================
        await client.query(`
            DROP TABLE IF EXISTS demo_index CASCADE;
            CREATE TABLE demo_index (
                id INT,
                info TEXT,
                status VARCHAR(20)
            );
        `);
        console.log('Table "demo_index" created.');

        // 插入 50 万行测试数据
        await client.query(`
            INSERT INTO demo_index (id, info, status)
            SELECT
                generate_series(1, 500000),
                'info_' || generate_series(1, 500000),
                CASE WHEN generate_series(1, 500000) % 10 = 0 THEN 'INACTIVE' ELSE 'ACTIVE' END
        `);
        console.log('Inserted 500,000 rows.');

        // ============================================================
        // 2. 无索引时的顺序扫描(Sequential Scan)
        // ============================================================
        const beforeNoIndex = Date.now();
        const res1 = await client.query(
            'SELECT * FROM demo_index WHERE id = 123456;'
        );
        const afterNoIndex = Date.now();
        console.log(`[No Index] Query time: ${afterNoIndex - beforeNoIndex} ms`);

        // ============================================================
        // 3. 创建 B-Tree 索引
        // ============================================================
        // PostgreSQL 原生指令:
        // CREATE INDEX idx_demo_index_id ON demo_index (id);
        await client.query('CREATE INDEX idx_demo_index_id ON demo_index (id);');
        console.log('Index "idx_demo_index_id" created.');

        const beforeWithIndex = Date.now();
        const res2 = await client.query(
            'SELECT * FROM demo_index WHERE id = 123456;'
        );
        const afterWithIndex = Date.now();
        console.log(`[With Index] Query time: ${afterWithIndex - beforeWithIndex} ms`);

        // ============================================================
        // 4. 表达式索引(Expression Index)
        // ============================================================
        // PostgreSQL 原生指令:
        // CREATE INDEX idx_demo_index_lower_info ON demo_index (LOWER(info));
        await client.query(`
            CREATE INDEX idx_demo_index_lower_info ON demo_index (LOWER(info));
        `);
        console.log('Expression index "idx_demo_index_lower_info" created.');

        const beforeExpr = Date.now();
        const res3 = await client.query(
            "SELECT * FROM demo_index WHERE LOWER(info) = 'info_123456';"
        );
        const afterExpr = Date.now();
        console.log(`[Expression Index] Query time: ${afterExpr - beforeExpr} ms`);

        // ============================================================
        // 5. 部分索引(Partial Index)
        // ============================================================
        // PostgreSQL 原生指令:
        // CREATE INDEX idx_demo_index_active ON demo_index (id) WHERE status = 'ACTIVE';
        await client.query(`
            CREATE INDEX idx_demo_index_active ON demo_index (id)
            WHERE status = 'ACTIVE';
        `);
        console.log('Partial index "idx_demo_index_active" created.');

        // 查询可以使用部分索引(status = 'ACTIVE' 且 id 在索引范围内)
        const beforePartial = Date.now();
        const res4 = await client.query(
            "SELECT * FROM demo_index WHERE id = 123456 AND status = 'ACTIVE';"
        );
        const afterPartial = Date.now();
        console.log(`[Partial Index - ACTIVE] Query time: ${afterPartial - beforePartial} ms`);

        // 查询无法使用部分索引(status = 'INACTIVE' 不在索引谓词范围内)
        const beforePartialMiss = Date.now();
        const res5 = await client.query(
            "SELECT * FROM demo_index WHERE id = 123456 AND status = 'INACTIVE';"
        );
        const afterPartialMiss = Date.now();
        console.log(`[Partial Index - INACTIVE] Query time: ${afterPartialMiss - beforePartialMiss} ms`);

        // ============================================================
        // 6. 查看索引信息
        // ============================================================
        // PostgreSQL 原生指令:\d demo_index
        const indexInfo = await client.query(`
            SELECT
                indexname,
                indexdef
            FROM pg_indexes
            WHERE tablename = 'demo_index';
        `);
        console.log('\n=== Indexes on "demo_index" ===');
        indexInfo.rows.forEach(row => {
            console.log(`  ${row.indexname}: ${row.indexdef}`);
        });

        // ============================================================
        // 7. 查看查询计划(EXPLAIN)
        // ============================================================
        const explainResult = await client.query(`
            EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
            SELECT * FROM demo_index WHERE id = 123456;
        `);
        console.log('\n=== EXPLAIN (with index) ===');
        console.log(explainResult.rows.map(r => r['QUERY PLAN']).join('\n'));

    } catch (err) {
        console.error('Error:', err.message);
    } finally {
        await client.end();
    }
}

runDemo();

代码说明

  1. 连接与建表:使用 pg 驱动连接 PostgreSQL,创建 demo_index 表并插入 50 万行测试数据。
  2. 性能对比:分别在无索引和有索引的情况下执行相同的 WHERE id = ? 查询,对比执行时间。
  3. 表达式索引:在 LOWER(info) 上创建索引,演示函数索引的用法。
  4. 部分索引:仅对 status = 'ACTIVE' 的行创建索引,演示部分索引的过滤效果。
  5. 索引信息查询:通过 pg_indexes 系统视图查看表上的所有索引定义。
  6. 执行计划分析:使用 EXPLAIN (ANALYZE, BUFFERS) 查看索引扫描的详细执行计划。

技术点总结

  • 顺序扫描 vs. 索引扫描:通过实际计时数据直观对比两种扫描方式的性能差异。
  • B-Tree 索引:PostgreSQL 默认索引类型,支持等值和范围查询。
  • 表达式索引:在函数或表达式结果上建立索引,支持大小写不敏感查询等场景。
  • 部分索引:仅在表子集上建立索引,节省空间并降低维护成本。
  • 执行计划分析:使用 EXPLAIN 验证索引是否被使用。

多语言示例程序扩展

本文基于上面的 Node.js 示例,扩展提供 Go、Python 和 Java 三种语言的完整实现。所有示例均演示了 PostgreSQL 索引的创建、查询性能对比以及表达式索引、部分索引的使用方式。

通用环境要求

  • PostgreSQL 12+,已创建测试数据库(如 testdb)
  • 各语言对应的数据库驱动(详见各节)
  • 表结构统一为:
CREATE TABLE demo_index (
    id INT,
    info TEXT,
    status VARCHAR(20)
);

插入 50 万行测试数据:

INSERT INTO demo_index (id, info, status)
SELECT
    generate_series(1, 500000),
    'info_' || generate_series(1, 500000),
    CASE WHEN generate_series(1, 500000) % 10 = 0 THEN 'INACTIVE' ELSE 'ACTIVE' END;

索引创建语句(按顺序执行):

CREATE INDEX idx_demo_index_id ON demo_index (id);
CREATE INDEX idx_demo_index_lower_info ON demo_index (LOWER(info));
CREATE INDEX idx_demo_index_active ON demo_index (id) WHERE status = 'ACTIVE';

Go 示例程序

运行说明
  • 环境:Go 1.18+,安装驱动:go get github.com/lib/pq
  • 启动:go run main.go
  • 修改连接字符串中的数据库凭据。
完整代码
package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/lib/pq"
)

func main() {
    // 连接字符串:host=localhost port=5432 user=postgres password=postgres dbname=testdb sslmode=disable
    connStr := "postgres://postgres:postgres@localhost:5432/testdb?sslmode=disable"
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    if err := db.Ping(); err != nil {
        log.Fatal(err)
    }
    fmt.Println("Connected to PostgreSQL")

    // 1. 删除并重建表(若需要)
    _, err = db.Exec(`DROP TABLE IF EXISTS demo_index CASCADE`)
    if err != nil {
        log.Fatal(err)
    }
    _, err = db.Exec(`CREATE TABLE demo_index (
        id INT,
        info TEXT,
        status VARCHAR(20)
    )`)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Table created")

    // 插入 50 万行
    _, err = db.Exec(`
        INSERT INTO demo_index (id, info, status)
        SELECT
            generate_series(1, 500000),
            'info_' || generate_series(1, 500000),
            CASE WHEN generate_series(1, 500000) % 10 = 0 THEN 'INACTIVE' ELSE 'ACTIVE' END
    `)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Inserted 500,000 rows")

    // 2. 无索引查询
    start := time.Now()
    row := db.QueryRow(`SELECT * FROM demo_index WHERE id = 123456`)
    var id int
    var info string
    var status string
    if err := row.Scan(&id, &info, &status); err != nil {
        log.Fatal(err)
    }
    elapsed := time.Since(start)
    fmt.Printf("[No Index] Query time: %v ms\n", elapsed.Milliseconds())

    // 3. 创建索引 (id)
    _, err = db.Exec(`CREATE INDEX idx_demo_index_id ON demo_index (id)`)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Index idx_demo_index_id created")

    start = time.Now()
    row = db.QueryRow(`SELECT * FROM demo_index WHERE id = 123456`)
    if err := row.Scan(&id, &info, &status); err != nil {
        log.Fatal(err)
    }
    elapsed = time.Since(start)
    fmt.Printf("[With Index] Query time: %v ms\n", elapsed.Milliseconds())

    // 4. 表达式索引
    _, err = db.Exec(`CREATE INDEX idx_demo_index_lower_info ON demo_index (LOWER(info))`)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Expression index idx_demo_index_lower_info created")

    start = time.Now()
    row = db.QueryRow(`SELECT * FROM demo_index WHERE LOWER(info) = 'info_123456'`)
    if err := row.Scan(&id, &info, &status); err != nil {
        log.Fatal(err)
    }
    elapsed = time.Since(start)
    fmt.Printf("[Expression Index] Query time: %v ms\n", elapsed.Milliseconds())

    // 5. 部分索引
    _, err = db.Exec(`CREATE INDEX idx_demo_index_active ON demo_index (id) WHERE status = 'ACTIVE'`)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Partial index idx_demo_index_active created")

    start = time.Now()
    row = db.QueryRow(`SELECT * FROM demo_index WHERE id = 123456 AND status = 'ACTIVE'`)
    if err := row.Scan(&id, &info, &status); err != nil {
        log.Fatal(err)
    }
    elapsed = time.Since(start)
    fmt.Printf("[Partial Index - ACTIVE] Query time: %v ms\n", elapsed.Milliseconds())

    // 查询无法使用部分索引 (INACTIVE)
    start = time.Now()
    row = db.QueryRow(`SELECT * FROM demo_index WHERE id = 123456 AND status = 'INACTIVE'`)
    if err := row.Scan(&id, &info, &status); err != nil {
        // 可能无数据,忽略
    }
    elapsed = time.Since(start)
    fmt.Printf("[Partial Index - INACTIVE] Query time: %v ms\n", elapsed.Milliseconds())

    // 6. 查看索引定义
    rows, err := db.Query(`
        SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'demo_index'
    `)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()
    fmt.Println("\n=== Indexes on 'demo_index' ===")
    for rows.Next() {
        var idxName, idxDef string
        if err := rows.Scan(&idxName, &idxDef); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("  %s: %s\n", idxName, idxDef)
    }

    // 7. EXPLAIN 分析
    var plan string
    err = db.QueryRow(`
        EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
        SELECT * FROM demo_index WHERE id = 123456
    `).Scan(&plan)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("\n=== EXPLAIN (with index) ===")
    fmt.Println(plan)
}
技术点总结
  • 使用 database/sql + lib/pq 驱动操作 PostgreSQL。
  • 时序测量采用 time.Since。
  • 执行 EXPLAIN 并获取文本结果。
  • 错误处理遵循 Go 惯例。

Python 示例程序

运行说明
  • 环境:Python 3.8+,安装依赖:pip install psycopg2-binary
  • 启动:python demo.py
  • 修改连接参数(host, port, database, user, password)。
完整代码
import psycopg2
import time

def main():
    conn = psycopg2.connect(
        host="localhost",
        port=5432,
        database="testdb",
        user="postgres",
        password="postgres"
    )
    conn.autocommit = True
    cur = conn.cursor()
    print("Connected to PostgreSQL")

    # 1. 创建表
    cur.execute("DROP TABLE IF EXISTS demo_index CASCADE")
    cur.execute("""
        CREATE TABLE demo_index (
            id INT,
            info TEXT,
            status VARCHAR(20)
        )
    """)
    print("Table created")

    # 插入 50 万行
    cur.execute("""
        INSERT INTO demo_index (id, info, status)
        SELECT
            generate_series(1, 500000),
            'info_' || generate_series(1, 500000),
            CASE WHEN generate_series(1, 500000) % 10 = 0 THEN 'INACTIVE' ELSE 'ACTIVE' END
    """)
    print("Inserted 500,000 rows")

    # 2. 无索引查询
    start = time.time()
    cur.execute("SELECT * FROM demo_index WHERE id = 123456")
    row = cur.fetchone()
    elapsed = (time.time() - start) * 1000
    print(f"[No Index] Query time: {elapsed:.2f} ms")

    # 3. 创建索引 (id)
    cur.execute("CREATE INDEX idx_demo_index_id ON demo_index (id)")
    print("Index idx_demo_index_id created")

    start = time.time()
    cur.execute("SELECT * FROM demo_index WHERE id = 123456")
    row = cur.fetchone()
    elapsed = (time.time() - start) * 1000
    print(f"[With Index] Query time: {elapsed:.2f} ms")

    # 4. 表达式索引
    cur.execute("CREATE INDEX idx_demo_index_lower_info ON demo_index (LOWER(info))")
    print("Expression index idx_demo_index_lower_info created")

    start = time.time()
    cur.execute("SELECT * FROM demo_index WHERE LOWER(info) = 'info_123456'")
    row = cur.fetchone()
    elapsed = (time.time() - start) * 1000
    print(f"[Expression Index] Query time: {elapsed:.2f} ms")

    # 5. 部分索引
    cur.execute("CREATE INDEX idx_demo_index_active ON demo_index (id) WHERE status = 'ACTIVE'")
    print("Partial index idx_demo_index_active created")

    start = time.time()
    cur.execute("SELECT * FROM demo_index WHERE id = 123456 AND status = 'ACTIVE'")
    row = cur.fetchone()
    elapsed = (time.time() - start) * 1000
    print(f"[Partial Index - ACTIVE] Query time: {elapsed:.2f} ms")

    start = time.time()
    cur.execute("SELECT * FROM demo_index WHERE id = 123456 AND status = 'INACTIVE'")
    row = cur.fetchone()
    elapsed = (time.time() - start) * 1000
    print(f"[Partial Index - INACTIVE] Query time: {elapsed:.2f} ms")

    # 6. 查看索引定义
    cur.execute("SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'demo_index'")
    print("\n=== Indexes on 'demo_index' ===")
    for idx_name, idx_def in cur.fetchall():
        print(f"  {idx_name}: {idx_def}")

    # 7. EXPLAIN 分析
    cur.execute("EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT * FROM demo_index WHERE id = 123456")
    plan = cur.fetchone()[0]
    print("\n=== EXPLAIN (with index) ===")
    print(plan)

    cur.close()
    conn.close()

if __name__ == "__main__":
    main()
技术点总结
  • 使用 psycopg2 驱动,autocommit=True 简化事务管理。
  • 时间测量使用 time.time() 转换为毫秒。
  • EXPLAIN 结果以文本形式获取并打印。
  • 简洁的错误处理(实际生产需增强)。

Java 示例程序

运行说明
  • 环境:JDK 11+,Maven 或直接添加 PostgreSQL JDBC 驱动(org.postgresql:postgresql:42.6.0)
  • 编译运行:使用 Maven 或 javac -cp postgresql-42.6.0.jar Demo.java 并 java -cp .:postgresql-42.6.0.jar Demo(Windows 用 ; 分隔)
  • 修改连接 URL 中的用户名/密码/数据库名。
完整代码
import java.sql.*;
import java.util.Properties;

public class Demo {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/testdb";
        Properties props = new Properties();
        props.setProperty("user", "postgres");
        props.setProperty("password", "postgres");

        try (Connection conn = DriverManager.getConnection(url, props)) {
            System.out.println("Connected to PostgreSQL");

            // 1. 创建表
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("DROP TABLE IF EXISTS demo_index CASCADE");
                stmt.execute("""
                    CREATE TABLE demo_index (
                        id INT,
                        info TEXT,
                        status VARCHAR(20)
                    )
                """);
                System.out.println("Table created");

                // 插入 50 万行
                stmt.execute("""
                    INSERT INTO demo_index (id, info, status)
                    SELECT
                        generate_series(1, 500000),
                        'info_' || generate_series(1, 500000),
                        CASE WHEN generate_series(1, 500000) % 10 = 0 THEN 'INACTIVE' ELSE 'ACTIVE' END
                """);
                System.out.println("Inserted 500,000 rows");
            }

            // 2. 无索引查询
            long start = System.currentTimeMillis();
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT * FROM demo_index WHERE id = 123456")) {
                if (rs.next()) {
                    // 读取数据(略)
                }
            }
            long elapsed = System.currentTimeMillis() - start;
            System.out.println("[No Index] Query time: " + elapsed + " ms");

            // 3. 创建索引 (id)
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CREATE INDEX idx_demo_index_id ON demo_index (id)");
                System.out.println("Index idx_demo_index_id created");
            }

            start = System.currentTimeMillis();
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT * FROM demo_index WHERE id = 123456")) {
                if (rs.next()) {}
            }
            elapsed = System.currentTimeMillis() - start;
            System.out.println("[With Index] Query time: " + elapsed + " ms");

            // 4. 表达式索引
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CREATE INDEX idx_demo_index_lower_info ON demo_index (LOWER(info))");
                System.out.println("Expression index idx_demo_index_lower_info created");
            }

            start = System.currentTimeMillis();
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT * FROM demo_index WHERE LOWER(info) = 'info_123456'")) {
                if (rs.next()) {}
            }
            elapsed = System.currentTimeMillis() - start;
            System.out.println("[Expression Index] Query time: " + elapsed + " ms");

            // 5. 部分索引
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("CREATE INDEX idx_demo_index_active ON demo_index (id) WHERE status = 'ACTIVE'");
                System.out.println("Partial index idx_demo_index_active created");
            }

            start = System.currentTimeMillis();
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT * FROM demo_index WHERE id = 123456 AND status = 'ACTIVE'")) {
                if (rs.next()) {}
            }
            elapsed = System.currentTimeMillis() - start;
            System.out.println("[Partial Index - ACTIVE] Query time: " + elapsed + " ms");

            start = System.currentTimeMillis();
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT * FROM demo_index WHERE id = 123456 AND status = 'INACTIVE'")) {
                if (rs.next()) {}
            }
            elapsed = System.currentTimeMillis() - start;
            System.out.println("[Partial Index - INACTIVE] Query time: " + elapsed + " ms");

            // 6. 查看索引定义
            System.out.println("\n=== Indexes on 'demo_index' ===");
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'demo_index'")) {
                while (rs.next()) {
                    System.out.printf("  %s: %s%n", rs.getString(1), rs.getString(2));
                }
            }

            // 7. EXPLAIN 分析
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT * FROM demo_index WHERE id = 123456")) {
                System.out.println("\n=== EXPLAIN (with index) ===");
                while (rs.next()) {
                    System.out.println(rs.getString(1));
                }
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
技术点总结
  • 使用 JDBC 驱动,通过 DriverManager 获取连接。
  • 使用 try-with-resources 自动释放资源。
  • 时间测量使用 System.currentTimeMillis()。
  • 执行 EXPLAIN 并逐行打印结果。

多语言对比总结

语言驱动/库特点
Golib/pq编译型,错误处理显式,性能优异
Pythonpsycopg2动态类型,代码简洁,适合快速原型
JavaJDBC类型安全,资源管理规范,适合企业应用

以上三种语言实现均与 Node.js 示例逻辑一致,可直接运行并观察索引带来的性能提升。

项目难点与解决方案

核心难点

  • 索引维护成本与查询收益的权衡:在生产环境中,不恰当的索引设计可能导致写入性能严重下降,而查询性能却未获得显著提升。
  • 在线建索引的复杂性:CREATE INDEX CONCURRENTLY 虽然不阻塞写入,但创建速度较慢、可能留下无效索引,且不能在事务块中执行。
  • 标识符长度限制:PostgreSQL 对象名最大长度为 63 个字符,超长名称会被自动截断,可能导致不同对象生成相同的截断名称,引发命名冲突。

解决方案

  1. 索引设计原则:优先在高选择性列上创建索引;对于低选择性列(如性别、状态枚举),评估是否适合使用部分索引。
  2. 部分索引与表达式索引的合理运用:针对特定的查询模式,使用部分索引缩小索引范围,使用表达式索引支持函数查询。
  3. 在线建索引的规范流程:在生产环境中使用 CREATE INDEX CONCURRENTLY 时,需监控创建进度,并在失败后及时清理无效索引。
  4. 命名规范:制定索引命名规范(如 idx_{表名}_{列名}),避免依赖自动命名,同时确保总长度不超过 63 个字符。

广度

本文覆盖了 PostgreSQL 索引的基本概念、主要类型(B-Tree、唯一索引、表达式索引、多列索引、部分索引)、创建与删除语法、在线建索引以及索引的选择性评估,为读者建立了完整的索引知识入门框架。

深度

深入探讨了 B-Tree 索引的底层数据结构(多级树结构、元页面、叶子/内部页面)、MVCC 下的版本链与索引维护机制以及部分索引与表达式索引的适用场景与限制。

复杂度

索引设计涉及存储空间、写入性能、查询性能三者的平衡,需要结合业务查询模式、数据分布特征和系统负载进行综合评估。CREATE INDEX CONCURRENTLY 的引入进一步增加了索引管理的复杂度。

官方文档

参考链接

总结

本文系统性地介绍了 PostgreSQL 索引的核心概念、主要类型及其适用场景。索引通过 B-Tree 等多级树结构将查询复杂度从 O ( n ) O(n) O(n) 降低至 O ( log ⁡ n ) O(\log n) O(logn),但同时也带来了存储空间和写入维护的额外开销。

在实际生产中,索引设计需遵循“高选择性优先”原则,并结合部分索引、表达式索引等高级特性进行针对性优化。CREATE INDEX CONCURRENTLY 提供了在线建索引的能力,但需谨慎处理其性能与失败恢复问题。

索引的底层实现(如 B-Tree 的页分裂、MVCC 版本链下的索引维护)是深入理解索引行为的关键。后续文章将深入探讨索引的内部原理、扫描方式及查询优化器的索引选择策略。

进阶数据库系列(十二):PostgreSQL 索引技术详解 索引主要被用来提升数据库性能,不当的使用会导致性能变差。PostgreSQL 提供了多种索引类型: B-tree、Hash、GiST、SP-GiST 、GIN 和 BRIN。每一种索引类型使用了一种不同的算法来适应不同类型的查询。默认情况下,CREATE INDEX 命令创建适合于大部分情况的 B-tree 索引。B-树(默认):B-树是一个自平衡树(self-balancing tree),按照顺序存储数据,支持对数时间复杂度(O(logN))的搜索、插入、删除和顺序访问。 阅读详情

相关推荐

POSTGRESQL (PG) 性能优化之索引使用技巧(复合多列索引、局部索引、表达式索引、覆盖索引、唯一索引)

索引优化技巧:复合索引(多列索引)、部分索引(局部索引)、表达式索引、覆盖索引和唯一索引,概念描述以及使用实例,优缺点等

helenbi的博客 8971

PostgresSQL中的索引

索引是数据库中的一种数据结构,它能够加快数据的检索速度并提高查询性能。在 PostgreSQL 中,索引是通过 B-树(B-Tree)或哈希(Hash)等算法实现的。下面是关于 PostgreSQL 索引的一些重要信息:1. B-树索引(B-Tree Index):- B-树索引是 PostgreSQL 最常用的索引类型,适用于各种查询类型。- B-树索引按照排序顺序存储索引键和关联的行指针。- B-树索引可用于等值查找、范围查找和排序等操作。- 在创建表时,可以指定将哪些列创建为索引列。

Xia13225498444的博客 7273

Postgresql杂谈 04—Postgresql中的五种常规索引

一、索引的分类 Postgresql中索引一共分为5种,每一种都有它合适的应用场景,我们在使用时要根据不同业务的特点,选择合适的索引,这样才能加快sql语句的查询效率。下面,我们将就每种不同的索引,介绍其特点。 2.1 B树索引 这是我们最常用的索引结构了,B树是一颗多路平衡查找树,每个节点包含多个键,而且这些键对应的指针一般指向磁盘上同一个数据块,目的是一次从磁盘读取一个数据块,减少磁盘IO操作,加快查询的效率。 B树索引的结构如下所示: ...

Hyman的博客 3319

在 PostgreSQL 中加快索引创建速度# PG初级

到目前为止,索引是每个通用关系数据库提供的性能相关最多的功能。没有索引,就没有高效的搜索操作,没有唯一约束,也没有主键。因此,在没有索引的情况下,在合理数量的数据上实现良好的性能是完全不可能的。

PGCCC的博客 1667

PostgreSQL: 九. 索引

九. 索引 9.1 索引简介 1、索引概念:索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可以提高数据库中特定数据的查询速度。 2、索引的分类: B-tree索引:比较常用,适合处理能够按顺序存储的数据,可以使用create index创建; Hash索引:只能处理简单的等于比较; GiST索引:不是单独的一种索引类型,而是一种架构,可以在这种架构上实现很多不同的索引策略; GIN...

数据知道的博客 3万+

ISCE+Mintpy进行SBASInSAR处理(持续更新)

通过调用pairs_misregistration方法,利用safe_dict中的数据信息,对每对SLC数据进行配准误差的计算和分析,以确定数据之间的精确差异,为后续的时间序列处理提供基础。给定同一地区的两景SAR影像,经过配准之后,对应像元进行共轭相乘得到相位差,并形成干涉图,但是从干涉图中得到的相位差实际上是主值,即在(-π,π)呈周期性变化,为了还原其真实的相位差,必须在主值的基础上加或减整数倍的相位周期2π,把被卷叠的相位展开,才能顺利地反演出地面目标的高程和形变。

Cyy2511250589的博客 3737

PostgreSQL 16 部署 pgvector:向量相似度检索与 HNSW、IVFFlat 索引调优实战

推荐服务每夜要对 500 万条 embedding 做相似度检索,而业务数据本来就躺在 Postgres 里,再引一套独立向量数据库意味着多一套运维。pgvector 把向量相似度检索做成了 PostgreSQL 16 的原生扩展,向量与业务表同库、同事务。本文覆盖从编译部署、最小可运行查询,到 HNSW 与 IVFFlat 索引选型、故障定位和生产调优的完整链路。 ## 项目定位:向量检索长在

gitblog_00600的博客 809

IMU自动校准论文与代码实现A Robust and Easy to Implement Method for IMU Calibration without External Equipments

PART1 摘要 低成本的惯性测量单元会受到系统误差的影响,系统误差由标度因子和轴偏差造成,这会降低位置和姿态估计的精度。 本文提出了一种无需任何外部设备的,鲁棒性强,易于实现的IMU校准方法。 该程序基于多放置方案,再估计传感器偏差的同时,为加速度和陀螺仪三轴的尺度和偏差系数。我们的方法只需要用手移动传感器,并将其放置在一组不同的静态位置(姿态)。 论文描述了一个鲁棒的快速校准协议,...

qq_29313679的博客 5309

PostgreSQL——索引

索引是一个单独的、存储在磁盘上的数据库结构,它们包含着对数据表里所有记录的引用指针。使用索引可以快速找出在某个或多个列中有一特定值的行,所有PostgreSQL列类型都可以被索引,对相关列使用索引是提高查询操作效率的最佳途径。。如果没有索引,必须遍历整个表,直到num等于10000的这一行被找到为止:如果在num列上创建索引,PostgreSQL不需要任何扫描,直接在索引里面找10000,就可以得知这一行的位置。可见,索引的建立可以提高数据库的查询速度。

吴声子夜歌的博客 2119

ECC椭圆曲线加解密原理详解(配图)

椭圆曲线加解密原理详解 本文主要参照:ECC椭圆曲线详解(有具体实例)[1] 前言: 椭圆曲线(ECC)加密原理跟RSA加解密原理比起来,可真是晦涩难懂。拜读了大神Kalafinaian文章“ECC椭圆曲线详解(有具体实例)”,终于看到了参透的曙光。 由于原文中有微小的瑕疵,也为了便于阅读起来更加流畅,我重新整理了一下这篇文章。 为什么使用椭圆曲线加密算法? RSA的解决分解整数问题需要亚指数...

Mark的博客 7万+

滑模控制概述

滑模控制概述1.滑模控制概念2.滑模控制原理3.滑模控制设计步骤4.研究现状5.发展趋势5.1自适应滑模变结构控制5.2模糊滑模控制5.3神经网络与滑模控制的结合 1.滑模控制概念   滑模控制(Sliding Mode Control,SMC)是一种特殊类型的变结构控制(Variable Structure Control,VSC),因此又称之为滑模变结构控制,是近年来广泛应用和发展的一种控制方法。滑模控制本质上是一种非线性控制,即控制结构随时间变化而变化。滑模变结构控制是非线性系统中普遍采用的一种分析方

shiyi20190828的博客 2万+

在PostgreSQL中管理索引

在 PostgreSQL 中,索引是用于加速查询性能的关键数据库对象。没有索引,数据库可能需要对整个表进行扫描以找到相关的行,这通常被称为全表扫描,对于大型表来说,这种操作可能非常慢。通过使用索引,数据库可以更快地定位到所需的数据行。

whdxlxl的专栏 1036

全志F1C100S/F1C200S学习笔记(11)——spi-flash 启动全流程适配烧录及踩坑指南

SPI Flash 系统编译 spi-flash 启动适配 分区规划: 分区序号 分区大小 分区作用 地址空间及分区名 mtd0 1MB (0x100000) spl+uboot 0x0000000-0x0100000 : “uboot” mtd1 64KB (0x10000) dtb文件 0x0100000-0x0110000 : “dtb” mtd2 4MB (0x400000) linux内核 0x0110000-0x0510000 : “kernel” mtd3 剩余 (

丨晋丨 1万+

PostgreSQL 基础概念与操作指南

✅ PostgreSQL 基础摘要 1️⃣ 核心概念 实例:1个PostgreSQL服务进程(含多个数据库) 数据库:实例中的独立数据单元(如postgres/myapp) 用户=角色:分超级用户(postgres)和普通用户 2️⃣ psql操作 连接:psql -U 用户 -d 数据库 常用命令: \l查看数据库 \c切换连接 \d查看表 \q退出 3️⃣ 权限管理 创建用户:CREATE USER 名 WITH PASSWORD '密码' 创建数据库:CREATE DATABASE 名 OWNER 用

csdn_tom_168的博客 1296

点云去噪:基于点云拟合的方法

点云处理是计算机视觉中的重要任务之一,而点云去噪是点云处理的关键步骤之一。在实际应用中,由于采集设备的噪声、运动估计误差或者环境干扰等因素,点云数据中常常包含有噪声点,这些噪声点会对后续的点云分析和处理造成不良影响。一种简单的方法是计算点到拟合曲面的距离,并将距离超过某个阈值的点定义为噪声点,进行过滤。一种简单的方法是计算点到拟合曲面的距离,并将距离超过某个阈值的点定义为噪声点,进行过滤。该方法通过拟合点云数据的曲面模型,对噪声点进行识别和过滤,从而得到更加干净的点云数据。

AuSwift的博客 640

第三章-python基础语法

有C语言基础应该这样学 Python。

qq_46187594的博客 1177

PostgreSQL 入门学习教程,从入门到精通,PostgreSQL 16 索引详解 —— 语法、案例与实战(含新特性)(10)

PostgreSQL 16 索引详解摘要 PostgreSQL 16 提供了多种索引类型(B-Tree、Hash、GIN、GiST等)来加速数据检索。索引设计应遵循高频查询优先、避免低基数字段等原则。本文详细介绍了索引创建语法,并通过学生数据库案例演示了普通索引、联合索引、唯一索引、部分索引、表达式索引和GIN索引的实战应用。PostgreSQL 16新增了INCLUDE子句支持覆盖索引和并行创建索引功能,显著提升查询性能并减少生产环境影响。文章还涵盖索引管理操作和性能监控方法,帮助开发者优化数据库性能。

qq_45746668的博客 515

C/C++小宇宙代码(完整代码)

C语言实现小宇宙的完整代码。

AIGC 2526

MyBatis Plus 中 PostgreSQL UUID 类型主键的最佳实践

在分布式系统和高并发场景下,传统的自增ID(Auto Increment)主键面临着诸多挑战:单点性能瓶颈、分库分表困难、数据迁移复杂等。PostgreSQL 的 UUID(Universally Unique Identifier,通用唯一标识符)类型主键为解决这些问题提供了优雅的解决方案。结合 MyBatis Plus 的强大功能,我们可以实现高效、安全的 UUID 主键管理。 本文将深入探...

gitblog_01413的博客 810

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

PostgreSQL的基础概念与特性介绍

作为一款开源的关系型数据库,PostgreSQL具有丰富的特性和优越的性能,成为众多企业和开发者的首选。本文将带你走进PostgreSQL的世界,从入门到实践,让你全面了解这款数据库的基本概念、核心特性、应用场景等。PostgreSQL始于1987年,由加州大学伯克利分校开发。它是一款成熟、稳定、功能强大的关系型数据库,被广泛用于各种应用场景。随着技术的不断发展,PostgreSQL在数据存储、查...

fastboot 747

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

03.PostgreSQL常用索引与优化

name text如果没有索引,数据库需要扫描整个表才能找到相应的数据。利用EXPLAINParallel Seq Scan 表示并行顺序扫描,执行消耗了 12 s;由于表中有包含大量数据,而查询只返回一行数据,显然这种方法效率很低。关于执行计划的更多信息,可以参考这篇文章。此时,如果在 id 列上存在索引,则可以通过索引快速找到匹配的结果。创建索引需要消耗一定的时间。Index Scan 表示索引扫描,执行消耗了 20 ms;

qq_42009262的博客 665

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

postgresql16分区表解析

PostgreSQL 16 引入了对分区表的多项改进,增强了其性能和可用性。本文介绍PostgreSQL 16 中分区表功能,包括基本概念、创建方法、管理技巧以及一些最佳实践。

专注于数据库技术分享,包含但不限于Oracle,MySQL,PostgreSQL,ElasticSearch及国产数据库等 1030

PostgreSQL学习总结(16)—— PostgreSQL 插件之 pgvector

pgvector作为PostgreSQL的向量搜索扩展,正在成为AI应用的关键组件。它通过持续迭代优化,从0.4.x到0.8.x版本逐步完善了IVFFlat和HNSW索引、量化支持、并行构建等核心功能。0.8.0引入的IterativeIndexScan有效解决了带过滤条件的向量查询难题,平衡了速度与准确性。pgvector支持多种量化方案和索引类型,适用于不同场景:IVFFlat适合批量搜索,HNSW适合实时应用。随着多模态支持增强,pgvector正从文本嵌入扩展到更广泛的AI应用领域,成为Postgr

科技D人生 3489

postgresql 索引类型

postgresql提供了B-tree,R-tree,GiST和hash索引类型。不同的索引类型适合特定的查询类型。 绝大多数数据库都支持B-tree索引类型,postgresql默认的create index语句也是创建B-tree索引。   R-tree: R树是一种用于处理多维数据的数据结构,用来访问二维或者更高维区域对象组成的空间数据.R树是一棵平衡树。树上有两类结点:叶子结点和非

shixiaoguo90的专栏 2956
上一篇: PostgreSQL笔记15: 高级查询全面解析——多表连接、窗口函数、CTE与序列生成
下一篇: PostgreSQL笔记17:逻辑备份工具链与性能优化实践
Wang's Blog
博客等级 码龄12年 7438粉丝 2422原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值