PostgreSQL笔记11:安装部署阶段常见问题排查与解决方案

纲要

  • PostgreSQL 数据目录权限要求(0700
  • PostgreSQL 端口占用问题(port5432
  • PostgreSQL 监听地址与连接拒绝(listen_addresseslocalhostUnix-domain socket
  • PostgreSQL 内存不足问题(shared_buffersmax_connectionsSystem V shared memory
  • PostgreSQL 系统表索引损坏与恢复(ignore_system_indexesREINDEXpg_class
  • 插件文件缺失与数据库坏块
  • 日志排查方法论(启动日志、CSV 日志、操作系统日志)

目录权限:数据目录必须为 0700

PostgreSQL 对数据目录的权限有严格要求。当数据目录的权限过于宽松(例如 07550777)时,启动时会报错:

FATAL: data directory "/path/to/data" has group or world access
DETAIL: Permissions should be u=rwx (0700)

该检查机制是为了防止其他用户读取数据库的敏感文件,包括 postgresql.confpg_hba.conf、日志文件以及表数据文件。PostgreSQL 要求数据目录的属主必须为数据库运行用户(通常为 postgres),且权限必须为 0700

解决方案

# 确认当前数据目录的属主和权限
ls -ld $PGDATA

# 修正属主(如需要)
chown -R postgres:postgres $PGDATA

# 修正权限为 0700
chmod 0700 $PGDATA

PostgreSQL 11 及以上版本还支持 0750(即 u=rwx,g=rx),但前提是使用了 --allow-group-access 选项进行初始化。生产环境建议统一使用 0700

端口占用:Address already in use

PostgreSQL 默认监听端口为 5432。当该端口已被其他进程占用时,启动失败并报错:

LOG: could not bind IPv4 address "0.0.0.0": Address already in use
HINT: Is another postmaster already running on port 5432? If not, wait a few seconds and retry.

该错误的常见原因包括:

  • 另一个 PostgreSQL 实例已在运行
  • 其他非 PostgreSQL 进程占用了该端口
  • 终止 PostgreSQL 后立即重启,操作系统尚未释放端口

排查与解决

# 查看端口占用情况(推荐使用 ss)
ss -lntp | grep 5432

# 或使用 netstat
netstat -tulpn | grep 5432

# 确认占用进程后,可停止该进程或修改端口

同一台主机上可以运行多个 PostgreSQL 实例,只需为每个实例配置不同的端口即可。修改 postgresql.conf 中的 port 参数:

# postgresql.conf
port = 5433

修改端口后需要重启数据库服务。

监听地址与连接拒绝:listen_addresses

默认情况下,PostgreSQL 仅监听 localhost127.0.0.1),即仅允许本地回环地址的连接。这意味着通过 TCP/IP 从其他主机或使用 -h 指定 IP 地址连接时,会收到连接拒绝错误:

psql: error: connection to server at "192.168.1.100", port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?

这是由于 listen_addresses 参数仅设置为 localhost,PostgreSQL 没有在指定的 IP 接口上监听。listen_addresses 参数只能在服务器启动时设置,修改后必须重启。

解决方案

# postgresql.conf
listen_addresses = '*'          # 监听所有可用 IP 接口
# 或指定具体 IP
listen_addresses = '0.0.0.0'    # 监听所有 IPv4 地址
# 或
listen_addresses = '192.168.1.100,localhost'

修改后重启 PostgreSQL:

pg_ctl restart -D $PGDATA

验证监听状态:

ss -lntp | grep postgres
# 或
netstat -tulpn | grep postgres

需要注意的是,listen_addresses 控制的是服务器监听的网络接口,而 pg_hba.conf 控制的是客户端认证。两者需配合使用,仅修改 listen_addresses 而不配置 pg_hba.conf 仍可能导致连接失败。

内存不足:shared_buffers 与 max_connections

PostgreSQL 使用 System V 共享内存(或 mmap)进行进程间通信。shared_buffers 参数控制数据库服务器用于共享内存缓冲区的内存量。当 shared_buffers 设置值超过系统可用内存时,启动时会报错:

FATAL: could not map anonymous shared memory: Cannot allocate memory
HINT: This error usually means that PostgreSQL's request for a shared memory segment exceeded available memory, swap space, or huge pages. To reduce the request size, reduce PostgreSQL's shared memory usage, perhaps by reducing "shared_buffers" or "max_connections".

该错误的核心原因是 PostgreSQL 请求的共享内存段超出了系统可用内存、交换空间或大页内存的限制。

配置建议

根据官方文档推荐:

  • 对于专用的数据库服务器(内存 ≥ 1GB),shared_buffers 的合理起始值为系统内存的 25%
  • 超过 40% 的分配通常不会带来更好的性能
  • 内存小于 1GB 的系统应使用更小的比例,为操作系统预留足够空间

max_connections 参数也会影响共享内存的分配。每个连接作为一个独立进程,会占用一定的内存资源。增大 max_connections 会导致共享内存分配增加。

排查步骤

# 查看系统内存总量
free -h

# 查看当前 shared_buffers 设置
psql -c "SHOW shared_buffers;"

# 查看当前 max_connections 设置
psql -c "SHOW max_connections;"

# 查看 PostgreSQL 进程内存占用
top -u postgres
# 或
ps aux | grep postgres

系统表索引损坏与恢复

PostgreSQL 在启动时需要读取系统表(如 pg_class)的索引来完成数据库对象的加载。pg_class 是 PostgreSQL 中最重要的系统表之一,所有数据库对象在其中都有记录。如果系统表索引的底层数据文件损坏(例如因磁盘坏块、突然断电等),数据库将无法正常启动。

模拟损坏场景

以下示例演示了如何定位并人为损坏系统表索引的底层文件(仅用于学习目的,切勿在生产环境执行):

-- 查询 pg_class_oid_index 的 OID
SELECT oid, relname FROM pg_class WHERE relname = 'pg_class_oid_index';
-- 假设返回 oid = 34352
# 获取索引对应的物理文件路径
# PGDATA/base/<数据库OID>/<索引OID>
# 使用 dd 人为损坏该文件(危险操作,仅演示)
dd if=/dev/zero of=$PGDATA/base/5/34352 bs=8192 count=1 conv=notrunc

损坏后尝试启动或连接:

psql: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed: FATAL:  invalid page in block 0 of relation base/5/34352

恢复方法:ignore_system_indexes

PostgreSQL 提供了一个开发者选项 ignore_system_indexes,用于在系统索引损坏时临时绕过索引读取。该参数只能在服务器启动时设置,不能在会话开始后修改。

# 以忽略系统索引的方式启动
pg_ctl start -D $PGDATA -o "-c ignore_system_indexes=on"

启动成功后,连接数据库并重建损坏的索引:

-- 重建损坏的系统索引
REINDEX INDEX pg_class_oid_index;

-- 或重建所有系统索引
REINDEX SYSTEM postgres;

重建完成后,关闭数据库并以正常模式重启:

pg_ctl stop -D $PGDATA
pg_ctl start -D $PGDATA

注意事项

  • ignore_system_indexes 属于开发者选项,位于 Developer Options 章节,不应在生产环境中常规使用
  • 该参数设置为 on 时,读取系统表会走顺序扫描而非索引扫描,性能会显著下降
  • 修改系统表索引时,索引仍会被更新,因此使用该参数是安全的

其他常见问题

插件动态库文件缺失

PostgreSQL 通过扩展(插件)实现诸多功能。如果在 shared_preload_librarieslocal_preload_libraries 中指定了未安装的插件,启动时会报错找不到对应的 .so 文件。解决方案是安装对应的扩展包,或从配置中移除该条目。

数据库坏块

磁盘坏块可能导致数据文件损坏。排查方式包括检查 PostgreSQL 启动日志和操作系统日志。

日志排查方法论

面对启动失败或连接异常,第一原则是查看日志

日志位置

  • PostgreSQL 启动日志:默认位于 $PGDATA/log/ 目录,或由 log_directory 参数指定
  • PostgreSQL CSV 日志:如启用了 log_destination = 'csvlog',日志文件为 postgresql-*.csv
  • 操作系统日志
    # 查看内核日志
    dmesg -T | tail -50
    
    # 查看系统日志
    tail -100 /var/log/messages
    # 或
    journalctl -xe | tail -50
    

排查流程

启动/连接失败

查看 PostgreSQL 启动日志

日志中有明确报错?

根据报错信息定位问题

查看操作系统日志

是否有磁盘/内存相关错误?

检查磁盘健康状态、内存

检查环境变量、配置文件语法

应用对应解决方案

重启验证

API 速览

本节梳理本文涉及的核心 PostgreSQL 配置参数与管理命令。

配置参数

参数类型默认值说明适用版本
portinteger5432服务器监听的 TCP 端口所有版本
listen_addressesstringlocalhost服务器监听的 TCP/IP 地址列表所有版本
shared_buffersinteger128MB共享内存缓冲区大小所有版本
max_connectionsinteger100最大并发连接数所有版本
ignore_system_indexesbooleanoff读取系统表时忽略系统索引所有版本

管理命令

pg_ctl

# 启动数据库
pg_ctl start -D $PGDATA

# 停止数据库
pg_ctl stop -D $PGDATA

# 重启数据库
pg_ctl restart -D $PGDATA

# 查看数据库状态
pg_ctl status -D $PGDATA

# 带参数启动(如忽略系统索引)
pg_ctl start -D $PGDATA -o "-c ignore_system_indexes=on"

psql

# 连接数据库(默认通过 Unix-domain socket)
psql -d postgres

# 通过 TCP/IP 连接
psql -h 127.0.0.1 -p 5432 -d postgres -U postgres

# 查看配置参数
psql -c "SHOW listen_addresses;"
psql -c "SHOW shared_buffers;"

系统工具

# 查看端口占用
ss -lntp | grep 5432
# 或
netstat -tulpn | grep 5432

# 查看进程
ps aux | grep postgres

# 查看内存
free -h

# 查看磁盘
df -h

Demo 简单示例

运行说明

以下 Demo 演示了一个完整的 PostgreSQL 实例启动、配置修改与故障模拟流程。使用 Node.js + node-postgrespg 库)进行数据库操作。

环境要求

  • PostgreSQL 12 及以上版本
  • Node.js 16 及以上版本
  • 已初始化的 PostgreSQL 数据目录

安装依赖

npm init -y
npm install pg

代码说明

import { Client } from 'pg';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';

const execAsync = promisify(exec);

// PostgreSQL 连接配置
const config = {
  host: 'localhost',
  port: 5432,
  database: 'postgres',
  user: 'postgres',
  password: 'postgres',
};

// PGDATA 路径(请根据实际环境修改)
const PGDATA = '/var/lib/pgsql/data';

/**
 * 1. 测试数据库连接
 */
async function testConnection(): Promise<boolean> {
  const client = new Client(config);
  try {
    await client.connect();
    const res = await client.query('SELECT version()');
    console.log('✅ 连接成功:', res.rows[0].version);
    await client.end();
    return true;
  } catch (err) {
    console.error('❌ 连接失败:', (err as Error).message);
    return false;
  }
}

/**
 * 2. 查看当前配置参数
 */
async function showConfig(param: string): Promise<string> {
  const client = new Client(config);
  try {
    await client.connect();
    const res = await client.query(`SHOW ${param}`);
    await client.end();
    return res.rows[0][param];
  } catch (err) {
    console.error(`❌ 获取参数 ${param} 失败:`, (err as Error).message);
    return '';
  }
}

/**
 * 3. 修改 postgresql.conf 中的配置
 * @param key 配置键名
 * @param value 配置值
 */
async function updateConfig(key: string, value: string): Promise<void> {
  const confPath = path.join(PGDATA, 'postgresql.conf');
  let content = fs.readFileSync(confPath, 'utf-8');
  
  // 替换或添加配置项
  const regex = new RegExp(`^${key}\\s*=\\s*.*$`, 'm');
  const newLine = `${key} = ${value}`;
  
  if (regex.test(content)) {
    content = content.replace(regex, newLine);
  } else {
    content += `\n${newLine}`;
  }
  
  fs.writeFileSync(confPath, content, 'utf-8');
  console.log(`✅ 已更新配置: ${key} = ${value}`);
}

/**
 * 4. 重启 PostgreSQL
 */
async function restartPostgres(): Promise<void> {
  console.log('🔄 正在重启 PostgreSQL...');
  try {
    await execAsync(`pg_ctl restart -D ${PGDATA} -w -t 30`);
    console.log('✅ PostgreSQL 重启成功');
  } catch (err) {
    console.error('❌ 重启失败:', (err as Error).message);
    throw err;
  }
}

/**
 * 5. 主流程:演示端口修改与验证
 */
async function main() {
  console.log('=== PostgreSQL 安装部署问题排查 Demo ===\n');
  
  // Step 1: 测试当前连接
  console.log('【步骤 1】测试当前连接');
  const connected = await testConnection();
  if (!connected) {
    console.log('⚠️ 当前无法连接,请检查 PostgreSQL 是否正常运行\n');
    return;
  }
  
  // Step 2: 查看当前端口
  console.log('\n【步骤 2】查看当前端口配置');
  const currentPort = await showConfig('port');
  console.log(`当前端口: ${currentPort}`);
  
  // Step 3: 修改端口(模拟端口冲突场景)
  console.log('\n【步骤 3】修改端口为 5433');
  await updateConfig('port', '5433');
  await restartPostgres();
  
  // Step 4: 使用新端口连接
  console.log('\n【步骤 4】使用新端口 5433 连接');
  const newConfig = { ...config, port: 5433 };
  const newClient = new Client(newConfig);
  try {
    await newClient.connect();
    const res = await newClient.query('SELECT port FROM pg_settings WHERE name = $1', ['port']);
    console.log(`✅ 新端口连接成功,当前端口: ${res.rows[0].port}`);
    await newClient.end();
  } catch (err) {
    console.error('❌ 新端口连接失败:', (err as Error).message);
  }
  
  // Step 5: 恢复端口
  console.log('\n【步骤 5】恢复端口为 5432');
  await updateConfig('port', '5432');
  await restartPostgres();
  
  console.log('\n✅ Demo 执行完成');
}

main().catch(console.error);

对应的 PostgreSQL 原生指令注释

-- 查看当前端口
SHOW port;

-- 修改端口(需修改 postgresql.conf)
-- port = 5433

-- 重启数据库
-- pg_ctl restart -D /var/lib/pgsql/data

-- 使用新端口连接
-- psql -p 5433 -d postgres -U postgres

技术点总结

该 Demo 演示了以下核心技术:

  1. Node.js 连接 PostgreSQL:使用 pg 库通过 TCP/IP 连接数据库
  2. 配置参数查询:通过 SHOW 命令获取运行时配置
  3. 配置文件修改:程序化修改 postgresql.conf 中的 port 参数
  4. 服务管理:通过 pg_ctl 命令重启数据库服务
  5. 端口变更验证:修改端口后使用新端口建立连接

多语言 Demo 示例

本部分基于 Node.js 示例,提供 Go、Python、Java 三种语言的完整实现。所有示例均演示了修改 postgresql.conf 中的 port 参数、重启 PostgreSQL 并验证新端口连接的能力。运行前请确保 PGDATA 环境变量指向正确的数据目录,且 pg_ctl 命令可用。

Go 版本

运行说明
  • 环境:Go 1.18+,已安装 lib/pq 驱动
  • 安装依赖:go get github.com/lib/pq
  • 执行:go run demo.go
代码
package main

import (
    "database/sql"
    "fmt"
    "os"
    "os/exec"
    "strings"
    "time"

    _ "github.com/lib/pq"
)

const (
    host     = "localhost"
    port     = 5432
    user     = "postgres"
    password = "postgres"
    dbname   = "postgres"
)

// 获取 PGDATA 环境变量,若未设置则使用默认路径
func getPgData() string {
    pgdata := os.Getenv("PGDATA")
    if pgdata == "" {
        pgdata = "/var/lib/pgsql/data"
    }
    return pgdata
}

// 测试连接
func testConnection(p int) error {
    connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
        host, p, user, password, dbname)
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        return err
    }
    defer db.Close()
    // 设置超时
    db.SetConnMaxLifetime(5 * time.Second)
    if err := db.Ping(); err != nil {
        return err
    }
    var version string
    err = db.QueryRow("SELECT version()").Scan(&version)
    if err != nil {
        return err
    }
    fmt.Println("✅ 连接成功:", version)
    return nil
}

// 查看当前端口配置
func showPort(p int) (string, error) {
    connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
        host, p, user, password, dbname)
    db, err := sql.Open("postgres", connStr)
    if err != nil {
        return "", err
    }
    defer db.Close()
    var portVal string
    err = db.QueryRow("SHOW port").Scan(&portVal)
    if err != nil {
        return "", err
    }
    return portVal, nil
}

// 修改 postgresql.conf 中的端口
func updateConfig(pgdata, key, value string) error {
    confPath := pgdata + "/postgresql.conf"
    content, err := os.ReadFile(confPath)
    if err != nil {
        return err
    }
    lines := strings.Split(string(content), "\n")
    found := false
    for i, line := range lines {
        if strings.HasPrefix(strings.TrimSpace(line), key+" =") {
            lines[i] = key + " = " + value
            found = true
            break
        }
    }
    if !found {
        lines = append(lines, key+" = "+value)
    }
    newContent := strings.Join(lines, "\n")
    err = os.WriteFile(confPath, []byte(newContent), 0644)
    if err != nil {
        return err
    }
    fmt.Printf("✅ 已更新配置: %s = %s\n", key, value)
    return nil
}

// 重启 PostgreSQL
func restartPostgres(pgdata string) error {
    fmt.Println("🔄 正在重启 PostgreSQL...")
    cmd := exec.Command("pg_ctl", "restart", "-D", pgdata, "-w", "-t", "30")
    output, err := cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("重启失败: %v, 输出: %s", err, output)
    }
    fmt.Println("✅ PostgreSQL 重启成功")
    return nil
}

func main() {
    pgdata := getPgData()
    fmt.Println("=== PostgreSQL 安装部署问题排查 Demo (Go) ===\n")

    // 步骤1: 测试当前连接
    fmt.Println("【步骤 1】测试当前连接")
    if err := testConnection(port); err != nil {
        fmt.Println("⚠️ 当前无法连接,请检查 PostgreSQL 是否正常运行")
        return
    }

    // 步骤2: 查看当前端口
    fmt.Println("\n【步骤 2】查看当前端口配置")
    curPort, err := showPort(port)
    if err != nil {
        fmt.Println("❌ 获取端口失败:", err)
        return
    }
    fmt.Printf("当前端口: %s\n", curPort)

    // 步骤3: 修改端口为 5433
    fmt.Println("\n【步骤 3】修改端口为 5433")
    if err := updateConfig(pgdata, "port", "5433"); err != nil {
        fmt.Println("❌ 修改配置失败:", err)
        return
    }
    if err := restartPostgres(pgdata); err != nil {
        fmt.Println("❌", err)
        return
    }

    // 步骤4: 使用新端口连接
    fmt.Println("\n【步骤 4】使用新端口 5433 连接")
    if err := testConnection(5433); err != nil {
        fmt.Println("❌ 新端口连接失败:", err)
    } else {
        // 查询当前端口
        newPort, _ := showPort(5433)
        fmt.Printf("当前端口: %s\n", newPort)
    }

    // 步骤5: 恢复端口
    fmt.Println("\n【步骤 5】恢复端口为 5432")
    if err := updateConfig(pgdata, "port", "5432"); err != nil {
        fmt.Println("❌ 修改配置失败:", err)
        return
    }
    if err := restartPostgres(pgdata); err != nil {
        fmt.Println("❌", err)
        return
    }

    fmt.Println("\n✅ Demo 执行完成")
}

对应的 PostgreSQL 原生指令注释:

-- 查看当前端口
SHOW port;

-- 修改端口(需修改 postgresql.conf)
-- port = 5433

-- 重启数据库
-- pg_ctl restart -D /var/lib/pgsql/data

-- 使用新端口连接
-- psql -p 5433 -d postgres -U postgres
技术点总结
  • 使用 lib/pq 驱动连接 PostgreSQL
  • 通过 sql.DB 执行 SHOW 查询
  • 文件读写修改配置文件
  • 调用外部命令 pg_ctl 重启服务
  • 错误处理与超时设置

Python 版本

运行说明
  • 环境:Python 3.8+,已安装 psycopg2python-dotenv(可选)
  • 安装依赖:pip install psycopg2-binary
  • 执行:python demo.py
代码
#!/usr/bin/env python3
import os
import subprocess
import sys
import psycopg2
from psycopg2 import sql

HOST = 'localhost'
PORT = 5432
USER = 'postgres'
PASSWORD = 'postgres'
DBNAME = 'postgres'

def get_pgdata():
    pgdata = os.environ.get('PGDATA')
    if not pgdata:
        pgdata = '/var/lib/pgsql/data'
    return pgdata

def test_connection(port):
    try:
        conn = psycopg2.connect(
            host=HOST,
            port=port,
            user=USER,
            password=PASSWORD,
            dbname=DBNAME,
            connect_timeout=5
        )
        cur = conn.cursor()
        cur.execute("SELECT version()")
        version = cur.fetchone()[0]
        cur.close()
        conn.close()
        print(f"✅ 连接成功: {version}")
        return True
    except Exception as e:
        print(f"❌ 连接失败: {e}")
        return False

def show_port(port):
    try:
        conn = psycopg2.connect(
            host=HOST,
            port=port,
            user=USER,
            password=PASSWORD,
            dbname=DBNAME,
            connect_timeout=5
        )
        cur = conn.cursor()
        cur.execute("SHOW port")
        port_val = cur.fetchone()[0]
        cur.close()
        conn.close()
        return port_val
    except Exception as e:
        print(f"❌ 获取端口失败: {e}")
        return None

def update_config(pgdata, key, value):
    conf_path = os.path.join(pgdata, 'postgresql.conf')
    with open(conf_path, 'r') as f:
        lines = f.readlines()
    found = False
    for i, line in enumerate(lines):
        if line.strip().startswith(key + ' ='):
            lines[i] = f"{key} = {value}\n"
            found = True
            break
    if not found:
        lines.append(f"{key} = {value}\n")
    with open(conf_path, 'w') as f:
        f.writelines(lines)
    print(f"✅ 已更新配置: {key} = {value}")

def restart_postgres(pgdata):
    print("🔄 正在重启 PostgreSQL...")
    try:
        subprocess.run(
            ['pg_ctl', 'restart', '-D', pgdata, '-w', '-t', '30'],
            check=True,
            capture_output=True,
            text=True
        )
        print("✅ PostgreSQL 重启成功")
    except subprocess.CalledProcessError as e:
        print(f"❌ 重启失败: {e.stderr}")
        sys.exit(1)

def main():
    pgdata = get_pgdata()
    print("=== PostgreSQL 安装部署问题排查 Demo (Python) ===\n")

    # 步骤1
    print("【步骤 1】测试当前连接")
    if not test_connection(PORT):
        print("⚠️ 当前无法连接,请检查 PostgreSQL 是否正常运行")
        return

    # 步骤2
    print("\n【步骤 2】查看当前端口配置")
    cur_port = show_port(PORT)
    if cur_port is None:
        return
    print(f"当前端口: {cur_port}")

    # 步骤3
    print("\n【步骤 3】修改端口为 5433")
    update_config(pgdata, 'port', '5433')
    restart_postgres(pgdata)

    # 步骤4
    print("\n【步骤 4】使用新端口 5433 连接")
    if test_connection(5433):
        new_port = show_port(5433)
        if new_port:
            print(f"当前端口: {new_port}")

    # 步骤5
    print("\n【步骤 5】恢复端口为 5432")
    update_config(pgdata, 'port', '5432')
    restart_postgres(pgdata)

    print("\n✅ Demo 执行完成")

if __name__ == '__main__':
    main()

对应的 PostgreSQL 原生指令注释同 Go 版本,此处省略。

技术点总结
  • 使用 psycopg2 驱动连接 PostgreSQL
  • 通过游标执行 SHOW 查询
  • 文件读写修改配置文件
  • 使用 subprocess 调用 pg_ctl
  • 异常处理与超时控制

Java 版本

运行说明
  • 环境:JDK 11+,Maven 或 Gradle 管理依赖
  • 依赖:org.postgresql:postgresql:42.7.3
  • 编译运行:javac -cp "postgresql-42.7.3.jar" Demo.java && java -cp ".:postgresql-42.7.3.jar" Demo(Linux/Mac)或 ; 分隔(Windows)
代码
import java.sql.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class Demo {
    private static final String HOST = "localhost";
    private static final int PORT = 5432;
    private static final String USER = "postgres";
    private static final String PASSWORD = "postgres";
    private static final String DBNAME = "postgres";

    private static String getPgData() {
        String pgdata = System.getenv("PGDATA");
        if (pgdata == null || pgdata.isEmpty()) {
            pgdata = "/var/lib/pgsql/data";
        }
        return pgdata;
    }

    private static boolean testConnection(int port) {
        String url = String.format("jdbc:postgresql://%s:%d/%s", HOST, port, DBNAME);
        Properties props = new Properties();
        props.setProperty("user", USER);
        props.setProperty("password", PASSWORD);
        props.setProperty("connectTimeout", "5");
        try (Connection conn = DriverManager.getConnection(url, props)) {
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT version()");
            if (rs.next()) {
                System.out.println("✅ 连接成功: " + rs.getString(1));
            }
            rs.close();
            stmt.close();
            return true;
        } catch (SQLException e) {
            System.err.println("❌ 连接失败: " + e.getMessage());
            return false;
        }
    }

    private static String showPort(int port) {
        String url = String.format("jdbc:postgresql://%s:%d/%s", HOST, port, DBNAME);
        Properties props = new Properties();
        props.setProperty("user", USER);
        props.setProperty("password", PASSWORD);
        props.setProperty("connectTimeout", "5");
        try (Connection conn = DriverManager.getConnection(url, props);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SHOW port")) {
            if (rs.next()) {
                return rs.getString(1);
            }
        } catch (SQLException e) {
            System.err.println("❌ 获取端口失败: " + e.getMessage());
        }
        return null;
    }

    private static void updateConfig(String pgdata, String key, String value) throws IOException {
        Path confPath = Paths.get(pgdata, "postgresql.conf");
        List<String> lines = Files.readAllLines(confPath);
        boolean found = false;
        for (int i = 0; i < lines.size(); i++) {
            String line = lines.get(i).trim();
            if (line.startsWith(key + " =")) {
                lines.set(i, key + " = " + value);
                found = true;
                break;
            }
        }
        if (!found) {
            lines.add(key + " = " + value);
        }
        Files.write(confPath, lines, StandardOpenOption.TRUNCATE_EXISTING);
        System.out.println("✅ 已更新配置: " + key + " = " + value);
    }

    private static void restartPostgres(String pgdata) throws IOException, InterruptedException {
        System.out.println("🔄 正在重启 PostgreSQL...");
        ProcessBuilder pb = new ProcessBuilder("pg_ctl", "restart", "-D", pgdata, "-w", "-t", "30");
        pb.redirectErrorStream(true);
        Process p = pb.start();
        boolean finished = p.waitFor(30, TimeUnit.SECONDS);
        if (!finished) {
            p.destroyForcibly();
            throw new InterruptedException("重启超时");
        }
        int exitCode = p.exitValue();
        if (exitCode != 0) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.err.println(line);
                }
            }
            throw new RuntimeException("pg_ctl 返回非零退出码: " + exitCode);
        }
        System.out.println("✅ PostgreSQL 重启成功");
    }

    public static void main(String[] args) {
        String pgdata = getPgData();
        System.out.println("=== PostgreSQL 安装部署问题排查 Demo (Java) ===\n");

        // 步骤1
        System.out.println("【步骤 1】测试当前连接");
        if (!testConnection(PORT)) {
            System.out.println("⚠️ 当前无法连接,请检查 PostgreSQL 是否正常运行");
            return;
        }

        // 步骤2
        System.out.println("\n【步骤 2】查看当前端口配置");
        String curPort = showPort(PORT);
        if (curPort == null) return;
        System.out.println("当前端口: " + curPort);

        // 步骤3
        System.out.println("\n【步骤 3】修改端口为 5433");
        try {
            updateConfig(pgdata, "port", "5433");
            restartPostgres(pgdata);
        } catch (Exception e) {
            System.err.println("❌ 操作失败: " + e.getMessage());
            return;
        }

        // 步骤4
        System.out.println("\n【步骤 4】使用新端口 5433 连接");
        if (testConnection(5433)) {
            String newPort = showPort(5433);
            if (newPort != null) {
                System.out.println("当前端口: " + newPort);
            }
        }

        // 步骤5
        System.out.println("\n【步骤 5】恢复端口为 5432");
        try {
            updateConfig(pgdata, "port", "5432");
            restartPostgres(pgdata);
        } catch (Exception e) {
            System.err.println("❌ 操作失败: " + e.getMessage());
            return;
        }

        System.out.println("\n✅ Demo 执行完成");
    }
}

对应的 PostgreSQL 原生指令注释同 Go 版本。

技术点总结
  • 使用 JDBC 驱动连接 PostgreSQL
  • DriverManager 获取连接,Statement 执行查询
  • 文件读写使用 FilesPaths API
  • 使用 ProcessBuilder 调用外部命令
  • 超时处理与异常捕获

所有示例均遵循相同的逻辑流程:连接测试 → 查询端口 → 修改配置 → 重启 → 验证新端口 → 恢复原端口。可根据实际环境调整 PGDATA 和数据库认证信息。

官方文档

参考链接

总结

本文系统梳理了 PostgreSQL 安装部署阶段最常遇到的五类问题:目录权限错误、端口冲突、监听配置不当、内存不足以及系统表索引损坏,并给出了每个问题的报错特征、排查步骤和解决方案。核心技术点包括 0700 权限要求、listen_addressesport 配置、shared_buffersmax_connections 的内存调优,以及使用 ignore_system_indexes 紧急恢复损坏的系统表索引。

同时强调了日志(启动日志、CSV 日志、操作系统日志)是定位一切启动与连接问题的首要手段。文中提供的 Node.js 示例演示了如何编程化地修改配置并验证连接,可与原生 pg_ctlpsql 命令互为补充。掌握这些基础故障排查方法,能够显著提高数据库运维的效率和信心。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Wang's Blog

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

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

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

打赏作者

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

抵扣说明:

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

余额充值