Ubuntu 20.04 PostgreSQL安装失败原因与正确初始化流程

Ubuntu 20.04 PostgreSQL安装失败原因与正确初始化流程

1. 为什么 Ubuntu 20.04 用户还在为 PostgreSQL 安装卡在第一步?

“Cara Menginstal PostgreSQL pada Ubuntu 20.04 [Mulai Cepat]”——这个印尼语标题直译是“如何在 Ubuntu 20.04 上快速安装 PostgreSQL”。它背后藏着一个非常真实、高频、且被严重低估的痛点:不是不会装,而是装完根本不敢用

我见过太多人,在终端里敲下sudo apt install postgresql后,看到 “Setting up postgresql-12 (12.18-0ubuntu0.20.04.1) …” 就以为大功告成。结果一执行psql -U postgres,弹出psql: error: FATAL: role "postgres" does not exist;或者createdb myapp报错createdb: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed;更常见的是,连systemctl status postgresql都显示inactive (dead),服务压根没起来。

这不是你手残,是 Ubuntu 20.04 的 PostgreSQL 包设计逻辑和绝大多数教程默认假设之间存在一道隐形断层。官方仓库(apt)提供的postgresql元包,只负责安装二进制文件和基础配置模板,但不自动初始化数据库集群,也不启动服务。它把“创建第一个数据目录”、“生成初始配置”、“启动守护进程”这三步,全部交给了管理员手动触发——而绝大多数新手教程,恰恰跳过了这最关键的初始化环节,直接教你怎么连库、建表。

再叠加 Ubuntu 20.04 的 systemd 服务管理机制、PostgreSQL 默认的peer本地认证策略、以及postgres系统用户的 shell 被设为/bin/false这些细节,就形成了一个典型的“安装即失败”陷阱。你查百度、搜 Google、翻 Stack Overflow,看到的全是sudo -u postgres psqlsudo systemctl start postgresql,但没人告诉你:如果集群没初始化,sudo -u postgres psql会报错说找不到 socket;如果服务没启用,systemctl start可能静默失败,因为postgresql.service是一个 generator,它依赖于实际的postgresql@12-main.service实例

这就是为什么“安装教程”满天飞,但真正能跑通psql -U postgres -c "SELECT version();"的人比例远低于预期。它不是一个技术难题,而是一个流程认知偏差。本篇不讲高深原理,只聚焦于 Ubuntu 20.04 这个特定版本上,从apt install按下回车开始,到你在终端里看到 PostgreSQL 版本号为止,每一步背后的“为什么必须这样”,以及那些藏在日志里的、决定成败的关键信号。

提示:Ubuntu 20.04 的默认 PostgreSQL 版本是 12.x(具体为 12.18),而非更新的 14 或 15。强行升级到新版需切换 APT 源或编译安装,会引入额外的依赖和兼容性风险。本文所有操作均基于官方仓库的postgresql-12,这是最稳定、最符合生产环境规范的选择。

2. 初始化集群:那个被所有教程跳过的致命步骤

几乎所有中文 PostgreSQL 教程,在 Ubuntu 环境下都犯了一个共同错误:它们把sudo apt install postgresql当作安装的终点,然后立刻跳到sudo -u postgres psql。这就像买了台新电脑,拆开包装就按电源键,却忘了先插电源线——硬件齐全,但能量通路没建立。

在 PostgreSQL 的世界里,“安装”和“初始化”是两个完全独立的阶段。apt install只是把/usr/lib/postgresql/12/下的二进制文件、/usr/share/postgresql/12/下的文档和模板拷贝到系统里。它不会碰/var/lib/postgresql/12/main/这个未来存放所有数据的核心目录,因为这个目录的结构、权限、初始配置,必须由initdb工具根据你的系统环境(编码、区域设置、密码策略)来生成。这个过程,就是“初始化一个数据库集群”。

Ubuntu 20.04 的postgresql包,将这个初始化动作封装成了一个systemd服务单元:postgresql@12-main.service。注意它的命名格式:postgresql@<version>-<cluster-name>。其中12是主版本号,main是集群名(你可以自定义,但main是默认且最安全的选择)。这个服务单元的启动脚本,内部会调用/usr/lib/postgresql/12/bin/initdb来完成初始化。

所以,正确的流程链是:

apt install postgresql → 创建 /etc/postgresql/12/main/ 配置骨架 ↓ systemctl enable postgresql@12-main → 设置开机自启(但此时集群仍为空) ↓ systemctl start postgresql@12-main → 触发 initdb 初始化,并启动 postmaster 进程

如果你跳过第三步,直接sudo systemctl start postgresql,会发生什么?我们来实测一下:

# 假设刚完成 apt install $ sudo systemctl start postgresql $ sudo systemctl status postgresql ● postgresql.service - PostgreSQL RDBMS Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled) Active: inactive (dead) # 注意这里!状态是 dead,不是 active Docs: https://www.postgresql.org/docs/12/static/

它看起来“成功”了,但其实什么都没做。postgresql.service是一个“meta service”,它本身不运行任何进程,只是用来管理所有postgresql@*.service实例的集合。真正的干活者是postgresql@12-main.service

现在,我们执行正确的命令:

# 启动并初始化 main 集群 $ sudo systemctl start postgresql@12-main # 查看详细状态 $ sudo systemctl status postgresql@12-main ● postgresql@12-main.service - PostgreSQL Cluster 12-main Loaded: loaded (/lib/systemd/system/postgresql@.service; disabled; vendor preset: enabled) Active: active (running) since Mon 2024-06-10 14:22:37 CST; 5s ago Main PID: 12345 (postmaster) Tasks: 7 (limit: 9452) Memory: 25.6M CGroup: /system.slice/system-postgresql.slice/postgresql@12-main.service ├─12345 /usr/lib/postgresql/12/bin/postmaster -D /var/lib/postgresql/12/main ├─12347 postgres: 12/main: checkpointer └─12348 postgres: 12/main: background writer

看到Active: active (running)postmaster进程了吗?这才是 PostgreSQL 真正活过来的标志。此时,/var/lib/postgresql/12/main/目录已被initdb创建,并填充了global/,base/,pg_wal/等核心子目录。

注意:initdb的执行是幂等的。如果你不小心重复运行systemctl start postgresql@12-main,它会检测到/var/lib/postgresql/12/main/已存在且非空,然后直接跳过初始化,只启动服务。所以不必担心误操作。

2.1 初始化失败的典型信号与诊断

当然,初始化也可能失败。最常见的原因是磁盘空间不足或权限问题。initdb会在/var/log/postgresql/下生成日志。如果启动失败,请第一时间查看:

$ sudo tail -n 20 /var/log/postgresql/postgresql-12-main.log

你会看到类似这样的关键错误行:

  • FATAL: could not create lock file "/var/lib/postgresql/12/main/postmaster.pid": Permission denied
    → 表明/var/lib/postgresql/12/目录的所有者不是postgres用户。修复:sudo chown -R postgres:postgres /var/lib/postgresql/12/

  • FATAL: could not write lock file "/var/lib/postgresql/12/main/postmaster.pid": No space left on device
    /var分区满了。Ubuntu 20.04 默认将/var单独分区,而 PostgreSQL 数据目录就在/var/lib/下。清理/var/log/journal//var/cache/apt/archives/

  • ERROR: invalid locale name "en_US.UTF-8"
    → 系统缺失该 locale。修复:sudo locale-gen en_US.UTF-8,然后sudo update-locale

这些错误信息,比任何教程都更直接地告诉你问题出在哪。记住:systemctl start的返回码(echo $?)永远是 0,无论成功与否。判断是否成功的唯一可靠方式,是systemctl status的输出和postmaster.pid文件是否存在

3. 认证与连接:为什么psql -U postgres总是失败?

postgresql@12-main.service成功运行后,你可能会迫不及待地输入psql -U postgres,然后得到一个冰冷的错误:

psql: error: FATAL: role "postgres" does not exist

这又是一个经典误解。错误信息说“角色不存在”,但postgres用户明明是 PostgreSQL 的超级用户啊?问题出在:postgres是一个操作系统级别的用户(OS user),而postgres也是一个数据库级别的角色(DB role),两者是独立的实体,需要分别创建和关联

initdb在初始化集群时,会自动创建一个名为postgres的数据库角色,但它默认的登录方式是peer认证。peer认证的意思是:“如果当前登录的 OS 用户名,和你要连接的 DB 角色名完全一致,那么就允许无密码登录”。所以,sudo -u postgres psql是有效的,因为它以 OS 用户postgres的身份运行psql,而psql默认尝试连接同名的 DB 角色postgres

但如果你直接在自己的用户(比如ubuntu)下运行psql -U postgrespsql就会尝试用 OS 用户ubuntu的身份去连接 DB 角色postgres,这违反了peer规则,于是被拒绝。

要解决这个问题,有两条路:

3.1 路径一:使用sudo -u postgres psql(推荐给初学者)

这是最安全、最符合 Ubuntu 设计哲学的方式。它不修改任何默认配置,完全利用了peer认证的优势。

# 切换到 postgres OS 用户,并启动 psql $ sudo -u postgres psql psql (12.18 (Ubuntu 12.18-0ubuntu0.20.04.1)) Type "help" for help. postgres=# \conninfo You are connected to database "postgres" as user "postgres" via socket in "/var/run/postgresql" at port "5432".

看到\conninfo的输出了吗?as user "postgres"证明连接成功,且是通过本地 socket(/var/run/postgresql/.s.PGSQL.5432)进行的,这是最快的连接方式。

3.2 路径二:修改pg_hba.conf,启用md5密码认证(适合开发环境)

如果你想用自己的用户(如ubuntu)直接连接,就需要修改 PostgreSQL 的客户端认证配置文件pg_hba.conf。这个文件位于/etc/postgresql/12/main/pg_hba.conf

首先,为postgresDB 角色设置一个密码:

$ sudo -u postgres psql postgres=# ALTER USER postgres PASSWORD 'mysecretpassword'; ALTER ROLE postgres=# \q

然后,编辑pg_hba.conf

$ sudo nano /etc/postgresql/12/main/pg_hba.conf

找到这一行(通常在文件末尾附近):

# TYPE DATABASE USER ADDRESS METHOD local all all peer

把它改成:

# TYPE DATABASE USER ADDRESS METHOD local all all md5

保存退出。md5表示要求客户端提供经过 MD5 加密的密码。

最后,重启服务使配置生效

$ sudo systemctl restart postgresql@12-main

现在,你就可以用密码连接了:

$ psql -U postgres -h localhost Password for user postgres: # 输入上面设置的密码 psql (12.18) Type "help" for help. postgres=#

注意-h localhost参数。它强制psql使用 TCP/IP 连接(端口 5432),而不是本地 socket。因为pg_hba.conflocal行的修改只影响 socket 连接,而host行(默认是127.0.0.1/32)可能还是md5,所以加-h是最保险的做法。

提示:pg_hba.conf的语法极其严格。每一列之间必须用至少一个空格或制表符分隔。多一个空格或少一个空格,都可能导致整个文件解析失败,服务无法启动。修改后务必用sudo systemctl restart测试,如果失败,用sudo journalctl -u postgresql@12-main -n 50 --no-pager查看错误日志。

4. 创建你的第一个数据库:createdb不是魔法,而是权限的体现

当你终于成功进入psql提示符后,下一步通常是createdb myapp。但如果你是在postgres角色下执行,它大概率会失败:

createdb: error: database creation failed: ERROR: permission denied to create database

这再次揭示了一个核心概念:在 PostgreSQL 中,“创建数据库”是一项需要显式授予的特权postgres角色虽然是超级用户,但createdb命令默认尝试以当前 OS 用户名作为数据库名来创建。而postgres角色的默认权限,是允许创建数据库的,但createdb工具本身需要访问template1数据库,而template1的所有权和权限,有时会被意外修改。

更常见的场景是:你想创建一个名为myapp的数据库,供你的应用使用。这时,最佳实践不是用postgres角色,而是创建一个专用的、权限最小化的角色。

4.1 创建应用专属角色与数据库

让我们一步步来:

# 1. 以 postgres 角色登录 $ sudo -u postgres psql # 2. 创建一个新角色(用户),并赋予其创建数据库的权限 postgres=# CREATE ROLE myapp WITH LOGIN CREATEDB PASSWORD 'myapp123'; CREATE ROLE # 3. 创建一个新数据库,所有者设为 myapp postgres=# CREATE DATABASE myapp OWNER myapp; CREATE DATABASE # 4. 退出 postgres=# \q

现在,你就可以用这个新角色连接了:

$ psql -U myapp -d myapp -h localhost Password for user myapp: psql (12.18) Type "help" for help. myapp=#

-d myapp参数指定了默认连接的数据库,这样就不用每次登录后手动USE myapp

4.2createdb命令的底层逻辑

createdb并不是一个独立的程序,它是psql的一个前端工具,其内部执行的 SQL 语句等价于:

CREATE DATABASE "myapp" WITH OWNER = current_user ENCODING = 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8' TEMPLATE = template0;

它会检查当前用户是否有CREATEDB权限,然后调用CREATE DATABASE。如果你看到permission denied,第一反应不应该是“怎么赋权”,而是检查:

  • 当前连接的用户是谁?(\conninfo
  • 该用户是否被授予了CREATEDB属性?(SELECT rolcreatedb FROM pg_roles WHERE rolname='your_user';
  • template1数据库是否可读?(SELECT datallowconn FROM pg_database WHERE datname='template1';应该是t

经验:在 Ubuntu 20.04 上,template1datallowconn默认是t(true),但如果之前执行过UPDATE pg_database SET datallowconn = false WHERE datname = 'template1';,就会导致所有createdb失败。修复只需UPDATE pg_database SET datallowconn = true WHERE datname = 'template1';

5. 验证与调试:让 PostgreSQL 从“能跑”到“稳跑”

安装完成的标志,不是apt install的成功提示,而是你能稳定、可预测地执行以下三个命令,并得到预期输出:

  1. 服务状态验证

    $ sudo systemctl is-active postgresql@12-main active $ sudo systemctl is-enabled postgresql@12-main enabled

    is-active确保服务正在运行,is-enabled确保重启后能自动拉起。这是生产环境的底线。

  2. 连接与查询验证

    $ sudo -u postgres psql -t -c "SELECT version();" PostgreSQL 12.18 (Ubuntu 12.18-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit

    -t参数去掉表头,-c直接执行命令。这条命令的输出,是 PostgreSQL 进程真实响应的铁证。

  3. 网络端口验证(如果你启用了md5认证):

    $ ss -tlnp | grep :5432 LISTEN 0 128 *:5432 *:* users:(("postmaster",pid=12345,fd=6))

    ss命令显示所有监听的 TCP 端口。*:5432表示 PostgreSQL 正在监听所有网络接口的 5432 端口。如果只看到127.0.0.1:5432,说明它只监听本地回环,外部机器无法连接。

5.1 常见故障排查清单

现象最可能原因快速诊断命令修复方案
psql: error: could not connect to server: No such file or directorypostgresql@12-main服务未启动,或 socket 路径错误sudo systemctl status postgresql@12-mainls -l /var/run/postgresql/sudo systemctl start postgresql@12-main;检查/etc/postgresql/12/main/postgresql.confunix_socket_directories的值
psql: error: FATAL: password authentication failed for user "postgres"pg_hba.conflocal行的 METHOD 是peer,但你用了密码sudo cat /etc/postgresql/12/main/pg_hba.conf | grep localpeer改为md5,并sudo systemctl restart postgresql@12-main
createdb: error: database creation failed: ERROR: new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)initdb初始化时,系统 locale 不是 UTF-8localesudo locale-gen en_US.UTF-8sudo dpkg-reconfigure locales,选择en_US.UTF-8,然后重新初始化集群(先sudo systemctl stop postgresql@12-main,再sudo rm -rf /var/lib/postgresql/12/main/,最后sudo systemctl start postgresql@12-main
sudo systemctl start postgresql@12-main无响应,status显示activating (auto-restart)postmaster进程启动后立即崩溃,通常是配置文件语法错误sudo journalctl -u postgresql@12-main -n 100 --no-pager检查/etc/postgresql/12/main/postgresql.confpg_hba.conf的最后一行是否有遗漏的引号或分号

5.2 一个被忽略的性能优化点:shared_buffers

Ubuntu 20.04 的默认postgresql.conf中,shared_buffers的值是128MB。对于一台 4GB 内存的开发机,这太小了。shared_buffers是 PostgreSQL 用于缓存数据页的内存池,它直接影响查询速度。

修改它很简单:

$ sudo nano /etc/postgresql/12/main/postgresql.conf

找到#shared_buffers = 128MB,取消注释,并改为:

shared_buffers = 1GB

(规则是:物理内存的 25%,但不超过 4GB)

然后重启服务:

$ sudo systemctl restart postgresql@12-main

这个改动不需要初始化新集群,是热加载的(部分参数需要重启,shared_buffers是其中之一)。它不会让你的安装“更快”,但会让你的第一个SELECT * FROM large_table;查询快上好几倍。这才是“Mulai Cepat”(快速开始)的真正含义:不仅安装快,后续的每一次交互也快。

我在实际项目中发现,很多团队在部署初期忽略了shared_buffers,导致他们误以为 PostgreSQL 本身很慢,转而花大量时间优化 SQL,最后才发现是基础配置没调。一个简单的shared_buffers调整,往往能带来 30% 以上的查询性能提升,成本为零。