PostgreSQL的若干扩展安装和使用

一、环境

操作系统: Ubuntu 22.04.4 LTS

内核版本: 6.8.0-117-generic

辅助工具: 1panel v2.1.13

虚拟环境: Docker v29.5.2

管理工具: pgAdmin 4

二、扩展安装与使用

1. PostGIS

bash 复制代码
docker run -d \
  --name postgis-container \
  -e POSTGRES_PASSWORD=yourpassword \
  -p 5432:5432 \
  postgis/postgis:17-3.4

本质是给 PostgreSQL 增加空间存储、空间计算、空间索引的扩展插件

具体的数据类型和相关函数见:PostGIS 3.5.2dev 手册 - PostGIS 空间数据库

2. TimescaleDB

bash 复制代码
# 创建并运行容器
docker run -d --name timescaledb \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=your_password \
  -e POSTGRES_DB=your_database \
  -e POSTGRES_USER=your_user \
  timescale/timescaledb:latest-pg17

超表是 TimescaleDB 的核心,本质是普通表 + 自动时间分区,数据按时间范围拆分为多个 Chunk(默认 7 天),查询时自动跳过无关 Chunk,性能远优于单表。

sql 复制代码
-- 步骤1:创建普通表(必须含时间字段,建议 TIMESTAMPTZ)
CREATE TABLE sensor_data (
  time        TIMESTAMPTZ       NOT NULL,  -- 时间主键(分区键)
  sensor_id   TEXT              NOT NULL,  -- 设备/传感器ID
  temperature DOUBLE PRECISION  NULL,       -- 温度
  humidity    DOUBLE PRECISION  NULL,       -- 湿度
  location    TEXT              NULL        -- 位置
);

-- 步骤2:转为超表(按 time 分区,1天1个Chunk)
SELECT create_hypertable(
  'sensor_data',                -- 表名
  'time',                        -- 分区字段(必须是 TIMESTAMPTZ)
  chunk_time_interval => INTERVAL '1 day',  -- 分区粒度(1天)
  if_not_exists => TRUE         -- 避免重复创建报错
);

-- 启用超表压缩(按时间自动压缩旧 Chunk)
ALTER TABLE sensor_data SET (
  timescaledb.compress = true,
  timescaledb.compress_segmentby = 'sensor_id'  -- 按设备ID分组压缩
);

-- 创建保留策略:保留30天数据,自动删除更早数据
SELECT add_retention_policy('sensor_data', INTERVAL '30 days');

-- 查看保留策略
SELECT * FROM timescaledb_information.retention_policies;

3. zhparser

bash 复制代码
docker run --name pgzhparser -d -e POSTGRES_PASSWORD=somepassword  -p 5432:5432 zhparser/zhparser:bookworm-16
sql 复制代码
create extension zhparser;

CREATE TEXT SEARCH CONFIGURATION testzhcfg (PARSER = zhparser);

ALTER TEXT SEARCH CONFIGURATION testzhcfg ADD MAPPING FOR n,v,a,i,e,l WITH simple;

SELECT * FROM ts_parse('zhparser', 'hello world! 2010年保障房建设在全国范围内获全面启动');

SELECT * FROM ts_parse('zhparser', '人工智能改变世界');

SELECT to_tsvector('testzhcfg', '人工智能改变世界');

4. PGMQ

bash 复制代码
docker run -d --name pgmq-postgres \
    -e POSTGRES_PASSWORD=postgres \
    -p 5432:5432 \
    ghcr.io/pgmq/pg18-pgmq:v1.10.0

常见用法:

pgmq.send

sql 复制代码
-- 发送消息
select * from pgmq.send('my_queue', '{"hello": "world"}');


-- 发送消息带上headers信息
select * from pgmq.send('my_queue', '{"hello": "world"}', '{"trace_id": "abc123"}');


-- 发送一条延迟5s的消息,可用于超时取消/定时任务等场景
select * from pgmq.send('my_queue', '{"hello": "world"}', 5);


-- 发送一条延迟一天的消息
select * from pgmq.send('my_queue', '{"hello": "world"}', CURRENT_TIMESTAMP + INTERVAL '1 day');


-- 发送消息,带headers信息且延迟10s
select * from pgmq.send('my_queue', '{"hello": "world"}', '{"priority": "high"}', 10);

pgmq.send_batch

sql 复制代码
-- 批量发送消息
select * from pgmq.send_batch('my_queue',
    ARRAY[
        '{"hello": "world_0"}',
        '{"hello": "world_1"}'
    ]::jsonb[]
);

-- 批量发送消息带headers信息
select * from pgmq.send_batch('my_queue',
    ARRAY['{"hello": "world_0"}', '{"hello": "world_1"}']::jsonb[],
    ARRAY['{"trace_id": "abc"}', '{"trace_id": "def"}']::jsonb[]
);

-- 批量发送消息,且均延迟5s
select * from pgmq.send_batch('my_queue',
    ARRAY[
        '{"hello": "world_0"}',
        '{"hello": "world_1"}'
    ]::jsonb[],
    5
);

-- 批量发送消息,且均延迟1天
select * from pgmq.send_batch('my_queue',
    ARRAY[
        '{"hello": "world_0"}',
        '{"hello": "world_1"}'
    ]::jsonb[],
    CURRENT_TIMESTAMP + INTERVAL '1 day'
);

pgmq.send_topic

sql 复制代码
-- 创建队列并绑定topic, # 匹配0到多个分段, * 匹配一个分段
select pgmq.create('logs_all');
select pgmq.create('logs_errors');
select pgmq.bind_topic('logs.#', 'logs_all');
select pgmq.bind_topic('logs.*.error', 'logs_errors');

-- 该消息会发送到两个队列 logs_all,logs_errors
select pgmq.send_topic('logs.api.error', '{"message": "API failed"}');

-- 带headers信息
select pgmq.send_topic('logs.db.error', '{"message": "DB error"}', '{"severity": "high"}', 0);

-- 带延迟5s
select pgmq.send_topic('logs.api.info', '{"message": "Request received"}', 5);

pgmq.send_batch_topic

pgmq.send_topic的批量版本

pgmq.bind_topic

sql 复制代码
-- 创建队列
select pgmq.create('error_logs');

-- 绑定topic匹配
select pgmq.bind_topic('logs.*.error', 'error_logs');
select pgmq.bind_topic('alerts.#', 'error_logs');

-- 绑定是幂等的
select pgmq.bind_topic('logs.*.error', 'error_logs');

pgmq.unbind_topic

sql 复制代码
-- 解绑topic匹配
select pgmq.unbind_topic('logs.*.error', 'error_logs');

-- 解绑成功返回true,本身未绑定返回false
select pgmq.unbind_topic('nonexistent.pattern', 'error_logs');

pgmq.test_routing

sql 复制代码
-- 会返回该topic匹配了哪些,将会向哪些队列发送消息
select * from pgmq.test_routing('logs.api.error');

    pattern     |  queue_name  | compiled_regex
----------------+--------------+---------------------------
 logs.#         | logs_all     | ^logs\..*$
 logs.*.error   | error_logs   | ^logs\.[^.]+\.error$

pgmq.read

sql 复制代码
-- 从队列my_queue读取2条消息,延迟10s可见
select * from pgmq.read('my_queue', 10, 2);

pgmq.read_with_poll

同pgmq.read,但如果队列里无消息会等待,默认5s

pgmq.pop

sql 复制代码
-- 读取并删除2条消息
select * from pgmq.pop('my_queue', 2);

pgmq.delete

sql 复制代码
-- 删除msg_id=5的消息
select pgmq.delete('my_queue', 5);
-- 批量删除
select * from pgmq.delete('my_queue', ARRAY[2, 3]);

pgmq.purge

sql 复制代码
-- 删除整个队列的消息,返回删除的消息数量
select * from pgmq.purge_queue('my_queue');

pgmq.archive

sql 复制代码
-- 归档msg_id=1的消息,从主队列移动到归档表,用于追溯重放等功能
SELECT * FROM pgmq.archive('my_queue', 1);
-- 批量版本
SELECT * FROM pgmq.archive('my_queue', ARRAY[1, 2]);

pgmq.create

sql 复制代码
-- 创建消息队列
select from pgmq.create('my_queue');
-- 创建单表消息队列
select from pgmq.create_non_partitioned('my_queue');
-- 创建分区消息队列,需要另外安装pg_partman扩展,适合大数据量,高吞吐,冷热分离
select from pgmq.create_partitioned(
    'my_partitioned_queue',
    '100000',
    '10000000'
);
-- 创建unlogged表队列,该类型无法恢复无法同步无法集群
select pgmq.create_unlogged('my_unlogged');
-- 转换为分区消息队列
select from pgmq.convert_archive_partitioned('my_queue', '10000', '100000');

pgmq.drop_queue

sql 复制代码
-- 正式队列和归档队列均会删除
select * from pgmq.drop_queue('my_unlogged');

pgmq.list_queues

列出所有队列

pgmq.metrics

sql 复制代码
-- 获取队列的信息
select * from pgmq.metrics('my_queue');
-- 所有队列信息
select * from pgmq.metrics_all();

 queue_name | queue_length | newest_msg_age_sec | oldest_msg_age_sec | total_messages |          scrape_time
------------+--------------+--------------------+--------------------+----------------+-------------------------------
 my_queue   |           16 |               2445 |               2447 |             35 | 2023-10-28 20:23:08.406259-05

5. UNLOGGED表 + pg_cron

对模拟redis的思路说明:

用 PostgreSQL 的 UNLOGGED 表 ( cache_kv )替代 Redis,核心就一张 KV 表 + 两个 PL/pgSQL 函数 + pg_cron 定时任务。

UNLOGGED 表不写 WAL,写入性能更高,且崩溃后数据丢失更接近 Redis 的语义。

过期策略模拟

被动删除 :查询时检查过期 → WHERE 子句过滤。

定期清理 :pg_cron 每 30 秒执行一次 DELETE FROM cache_kv WHERE expire_time < NOW()。

与redis的优缺对比:

优点:

  1. 零额外组件成本,不用单独维护 Redis
  2. 数据天然可持久化
  3. 支持完整 SQL 查询,功能远超 Redis
  4. 不用担心里存容量爆炸
  5. 权限、事务、安全直接复用 PG 能力

缺点:

  1. 性能差距巨大:Redis ≈ 10 万~100 万 QPS,PG≈1 万~5 万 QPS
  2. 延迟更高
  3. 内存效率远不如 Redis
  4. redis其他的数据结构需要另外实现

docker-compose.yml

XML 复制代码
version: "3.9"

services:
  postgres:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: pedis
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: Aa123123
      POSTGRES_DB: postgres
    volumes:
      - pgdata:/var/lib/postgresql/17/main
      - ./init-cron.sql:/docker-entrypoint-initdb.d/init-cron.sql:ro
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  pgdata:

Dockerfile

bash 复制代码
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

# 安装必要工具并添加 PostgreSQL 17 官方源
RUN apt-get update \
    && apt-get install -y curl ca-certificates gnupg lsb-release \
    && curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/postgresql-archive-keyring.gpg \
    && echo "deb [signed-by=/usr/share/keyrings/postgresql-archive-keyring.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list \
    && apt-get update \
    && apt-get install -y postgresql-17 postgresql-client-17 \
    && apt-get update && apt-get upgrade -y && apt-get -y install postgresql-17-cron \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# 设置 PostgreSQL 环境变量
ENV PGDATA=/var/lib/postgresql/17/main
ENV PATH="/usr/lib/postgresql/17/bin:${PATH}"

# 先修改配置文件(启动前生效,避免需要 restart)
RUN sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" /etc/postgresql/17/main/postgresql.conf \
    && echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/17/main/pg_hba.conf \
    && echo "shared_preload_libraries = 'pg_cron'" >> /etc/postgresql/17/main/postgresql.conf \
    && echo "cron.database_name = 'postgres'" >> /etc/postgresql/17/main/postgresql.conf

# 初始化数据库并启动
RUN pg_ctlcluster 17 main start \
    && pg_ctlcluster 17 main stop

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 5432

ENTRYPOINT ["/entrypoint.sh"]

entrypoint.sh

bash 复制代码
#!/bin/bash
set -e

# 启动 PostgreSQL 服务
pg_ctlcluster 17 main start

# 根据环境变量更新 postgres 用户密码
if [ -n "$POSTGRES_PASSWORD" ]; then
    su - postgres -c "psql -c \"ALTER USER postgres PASSWORD '$POSTGRES_PASSWORD';\"" 2>/dev/null || true
fi

# 执行初始化 SQL 脚本(pg_cron 定时任务等)
if [ -f /docker-entrypoint-initdb.d/init-cron.sql ]; then
    su - postgres -c "psql -f /docker-entrypoint-initdb.d/init-cron.sql" 2>/dev/null || true
fi

# 保持容器运行并监听信号
tail -f /var/log/postgresql/postgresql-17-main.log &
PID=$!

# 使用 trap 优雅停止
trap "pg_ctlcluster 17 main stop; kill $PID 2>/dev/null; exit 0" SIGTERM SIGINT
wait $PID

init-cron.sql

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

-- 模拟 Redis KV 存储表 (UNLOGGED: 不写WAL, 性能更高, 崩溃丢数据符合缓存语义)
DROP TABLE IF EXISTS cache_kv;
CREATE UNLOGGED TABLE cache_kv (
    k VARCHAR(255) PRIMARY KEY,
    v TEXT NOT NULL,
    expire_time TIMESTAMP,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_cache_kv_expire_time ON cache_kv(expire_time);

-- 模拟 Redis GET:查不到或过期则清除残留
CREATE OR REPLACE FUNCTION cache_get(p_key VARCHAR)
RETURNS TEXT AS $$
DECLARE
    result TEXT;
BEGIN
    SELECT v INTO result
    FROM cache_kv
    WHERE k = p_key AND (expire_time IS NULL OR expire_time > NOW());

    IF result IS NULL THEN
        DELETE FROM cache_kv WHERE k = p_key;
    END IF;

    RETURN result;
END;
$$ LANGUAGE plpgsql;

-- 模拟 Redis TTL:返回剩余秒数(-1:永久, -2:不存在/已过期)
CREATE OR REPLACE FUNCTION cache_ttl(p_key VARCHAR)
RETURNS BIGINT AS $$
DECLARE
    exp TIMESTAMP;
BEGIN
    SELECT expire_time INTO exp FROM cache_kv WHERE k = p_key;

    IF exp IS NULL THEN
        RETURN -2;
    END IF;

    IF exp <= NOW() THEN
        DELETE FROM cache_kv WHERE k = p_key;
        RETURN -2;
    END IF;

    RETURN EXTRACT(EPOCH FROM (exp - NOW()))::BIGINT;
END;
$$ LANGUAGE plpgsql;

-- 每30秒定期清理过期数据
SELECT cron.schedule(
    'cleanup_expired_cache',
    '*/30 * * * *',
    $$DELETE FROM cache_kv WHERE expire_time < NOW()$$
);

SELECT cron.schedule(
    'daily_maintenance',
    '0 3 * * *',
    $$VACUUM ANALYZE cache_kv$$
);

GRANT USAGE ON SCHEMA cron TO postgres;
相关推荐
砍材农夫2 小时前
物联网实战:Spring Boot + Netty 搭建 MQTT 统一接入层
java·网络·spring boot·后端·物联网·spring
写代码的小阿帆2 小时前
英语四六级证书审核(SpringBoot+Dify+RPA)
java·spring boot
霸道流氓气质3 小时前
RabbitMQ 从零到实战:概念、配置与 Spring Boot 集成指南
spring boot·rabbitmq·java-rabbitmq
夜郎king3 小时前
SpringBoot 整合 Neo4j 实战:从零搭建经典小说知识图谱完整方案
spring boot·知识图谱·neo4j
取名好樊3 小时前
Windows Docker PostgreSQL 端口绑定失败问题记录
windows·docker·postgresql
逍遥德3 小时前
PostgreSQL --- 二进制数使用详解
数据库·sql·postgresql
仙俊红3 小时前
深入理解 ThreadLocal —— 从变量引用、强弱引用到 Spring Boot 实战
spring boot·python·算法
Jabes.yang3 小时前
互联网大厂Java求职面试实战解析(含技术场景与详解)
spring boot·微服务·面试·orm·技术栈·java se·jakarta ee
骑士雄师3 小时前
18.2 PostgreSQL 的安装
数据库·postgresql