第1章 认识 PostgreSQL 与环境搭建
🎯 本章学习目标
学完本章,你应当能够:
- 说清 PostgreSQL 的定位,以及它相对 MySQL 的气质差异:更贴标准、类型更丰富、扩展性更强、单机功能更「重」。
- 建立 PG 的层级心智:
实例(Instance) → 数据库(Database) → 模式(Schema) → 表/对象,并能和 MySQL 的「实例 → 库 → 表」两级对齐,理解 schema 这一层 MySQL 基本没有。 - 牢记并解释一条与直觉相反的铁律:PG 中未加引号的标识符(表名/列名)会折叠为小写,加双引号才保留原样——这与达梦「转大写」相反,与 MySQL 依操作系统而异也不同。
- 完成环境搭建两条路:Windows 安装包 / Docker 容器;说清超级用户
postgres、默认端口5432、pg_hba.conf与postgresql.conf两大配置文件。 - 用 psql 命令行完成「连库 → 看结构 → 跑 SQL」三板斧,并知道
\l \dt \d这些反斜杠命令。 - 用 pgJDBC 跑通第一个 Java 连接,打印数据库版本,理解
jdbc:postgresql://host:5432/db的构成。
1.1 PostgreSQL 是什么,和 MySQL 有何气质差异
PostgreSQL(官方常简称 PG,读作「P-G」,昵称 Postgres)是一个对象-关系型数据库,起源于加州大学伯克利分校,许可证是宽松的 BSD 类许可,可自由商用。它是很多场景下 MySQL 的「升级选项」,两者的整体气质对比:
| 维度 | MySQL | PostgreSQL |
|---|---|---|
| 定位气质 | 轻量、易上手、Web 应用装机量第一 | 功能强、严谨、贴标准、「开源里的 Oracle」 |
| 存储引擎 | 可插拔(InnoDB/MyISAM...) | 单一统一引擎(+扩展),不用选引擎 |
| 类型系统 | 常规类型 + JSON | 极丰富:JSONB、数组、范围、UUID、网络/几何、自定义类型 |
| SQL 标准符合度 | 部分(8.0 后大幅改善) | 非常高 |
| 扩展生态 | 插件较少 | 丰富:PostGIS、pg_trgm、TimescaleDB、pgvector... |
| 复制/HA | 主从成熟、工具多 | 流复制 + 逻辑复制,生态同样成熟 |
| Java 生态 | 极普及 | 同样一等公民,Spring/Hibernate 支持完善 |
给 MySQL 同学的定心丸:你在 MySQL 里 90% 的直觉(索引加速事务、
WHERE/GROUP BY/JOIN、连接池、主从)都能直接迁过来。本课程的重点是那 10% 会坑你的差异。
1.2 层级架构:比 MySQL 多了一层 Schema
1.2.1 结构对照图
MySQL 世界 PostgreSQL 世界
────────── ──────────────────────────
MySQL 实例 (mysqld, :3306) PG 实例 (postmaster, :5432)
├── 数据库 db1 ├── 数据库 postgres(默认自带)
│ ├── 表 t_user ├── 数据库 mydb
│ └── 表 t_order │ └── 模式(schema)
├── 数据库 db2 │ ├── public ← 默认模式,建表落这里
│ └── ... │ │ ├── 表 t_user
(库下直接就是表) │ │ └── 表 t_order
│ └── shop(可自建模式做隔离)
│ └── 表 ...- 实例:一个跑着的 PG 服务进程(术语叫
postmaster),管着若干数据库,监听 5432 端口(对应 MySQL 的 3306)。 - 数据库(Database):概念和 MySQL 一致。一个实例下多个库,跨库不能直接 JOIN(和 MySQL 跨库不同,PG 跨库连查都做不到,需
dblink/扩展)。 - 模式(Schema):⚠️ 这是 MySQL 几乎没有的一层。库下面是模式,模式下面才是表。完整定位一张表要写三段:
数据库.模式.表,日常用两段schema.table即可。- 每个库自带一个
public模式,你不指定时表就建在public里。 - 模式常用于:多团队共用一个库时的命名隔离、把「系统对象」和用户对象分开。
- 每个库自带一个
search_path:类似 MySQL 的「当前库」概念,但作用在模式上。它是一个模式搜索列表,默认"$user", public——建/查表不带前缀时,PG 按这个顺序找。Java 连接里可用currentSchema参数指定(第 5 章)。
心智转换:MySQL 里你习惯的「切换到某个 db」,在 PG 里更常见的是「在同一个 db 里切换到某个 schema,或用 schema 前缀限定」。连接串一次只能连一个 database。
1.2.2 用户与角色:MySQL 的 user 在 PG 里是 role
MySQL 有 user@host 这套账号体系。PG 把「谁能登录」和「有什么权限」统一抽象成 role(角色):
CREATE USER= 创建一个能登录的 role(等价CREATE ROLE ... LOGIN)。- 超级用户安装时默认叫
postgres(对应 MySQL 的 root)。 - 权限、
GRANT/REVOKE语法和 MySQL 类似,但第 7 章会讲 PG 特有的连接认证文件pg_hba.conf——它决定「哪个 IP、用哪种认证方式、能连哪个库」,是 MySQL 没有的独立机制。
1.3 铁律:未加引号的标识符折叠为小写
这是 MySQL 同学(甚至达梦同学)最容易翻车的一条,务必单独记住。
PG 对标识符(表名、列名、schema 名)的处理规则:
- 不加引号:PG 会把它统一转成小写后再解析。sql
CREATE TABLE T_User (Id INT, Name TEXT); -- 实际创建的是表 t_user,列 id、name SELECT * FROM T_USER; -- ✅ 能查到,因为又折叠成小写 t_user SELECT * FROM "T_User"; -- ❌ 报错 relation "T_User" does not exist - 加双引号
"...":区分大小写、原样保留。sqlCREATE TABLE "T_User" ("Id" INT, "Name" TEXT); -- 真的建了一个大写表名 SELECT "Id" FROM "T_User"; -- 之后每次都得带引号且大小写一致
与 MySQL / 达梦 的关键差异:
| 数据库 | 未加引号标识符 | 后果 |
|---|---|---|
| MySQL | 依操作系统(Windows 不区分、Linux 表名区分) | 跨平台迁移常踩坑 |
| 达梦 DM8 | 默认折叠为大写 | 与 PG 正好相反 |
| PostgreSQL | 恒折叠为小写(跨平台一致) | 加引号才保留原样 |
实践建议(贯穿全课程):表名、列名一律用小写 + 下划线命名,永远不加双引号。这样最省心,也符合 PG 社区惯例。除非你有强需求,否则不要制造大小写混合的对象——一旦建了
"MyTable",之后每条 SQL 都得记得加引号,Java 里拼 SQL 更是灾难。
1.4 安装环境
1.4.1 Windows(官方安装包)
- 到 EnterpriseDB Downloader 下载 Windows x86-64 安装包。
- 图形化安装向导中依次设置:安装目录、组件(勾选 PostgreSQL Server / pgAdmin 4 / Command Line Tools)、数据目录、超级用户
postgres的密码(记住它)、端口(默认 5432,一般不改)、区域(locale)选默认。 - 完成后 PG 作为 Windows 服务自动运行。开始菜单里有 SQL shell (psql) 快捷方式和 pgAdmin 4。
1.4.2 Docker(推荐做实验,最干净)
# 起了就用,数据存 named volume,超管密码 postgres
docker run -d --name pg \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=mydb \
-p 5432:5432 \
-v pgdata:/var/lib/postgresql/data \
postgres:16
# 进入容器内的 psql
docker exec -it pg psql -U postgres环境变量对照:POSTGRES_PASSWORD(超管密码,必填)、POSTGRES_DB(初始化建的库,默认 postgres)、POSTGRES_USER(默认 postgres)。数据目录在容器内是 /var/lib/postgresql/data,务必挂出来。
常用镜像 tag:
postgres:16、postgres:17(大版本)、postgres:16-alpine(更瘦)。生产建议明确固定大版本,别用latest。
1.4.3 Linux(yum/apt)
# Debian/Ubuntu 系
sudo apt install postgresql postgresql-contrib
sudo systemctl status postgresql # 装完即开机自启
sudo -u postgres psql # 用系统 postgres 账号免密切入 psql(peer 认证)sudo -u postgres psql 能免密进入,靠的正是 pg_hba.conf 里对本地 socket 的 peer 认证(第 7 章详解)——这是 PG 和 MySQL 很不同的地方:操作系统用户到数据库用户的映射。
1.5 psql 三板斧
psql 是官方命令行客户端,类比 MySQL 的 mysql 命令。
psql -h localhost -p 5432 -U postgres -d mydb
# 提示符变为: postgres=# (#=超级用户,$=普通用户)进去后的两类操作——反斜杠命令(psql 自己的,不在 SQL 里)和 SQL 语句:
\l -- 列出所有数据库
\c mydb -- 切换连接到一个新库(Connect)
\dn -- 列出当前库的所有 schema
\dt -- 列出当前 schema(通常 public)下的所有表
\dt shop.* -- 列出 shop 模式下的表
\d t_user -- 描述一张表:列、类型、索引、约束(最常用)
\du -- 列出所有角色(用户)
\encoding UTF8 -- 查看客户端编码
-- 以下为真正的 SQL
SELECT version(); -- 查 PG 版本
SHOW server_version; -- 也能看版本
SHOW search_path; -- 当前模式搜索路径
SELECT current_database(), current_user; -- 当前库、当前用户
CREATE TABLE demo (id serial primary key, note text); -- 建表(serial=自增,第2章讲)
INSERT INTO demo(note) VALUES ('第一条');
SELECT * FROM demo;几个和 MySQL 不同的手感点:
- SQL 必须以分号结尾,且 psql 里
\x可切换扩展(竖排)显示结果,看宽表很清楚。 - 想退出用
\q(对应 MySQL 的exit/quit)。 \d信息密度极高,一张表的列/类型/默认值/索引/约束一次看全,务必养成用它替代 MySQL 里SHOW CREATE TABLE的习惯。
1.6 跑通第一个 Java 连接
1.6.1 引入 pgJDBC
Maven 坐标(官方驱动,纯 Java):
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.4</version>
</dependency>版本以 42.7.x 系列为准;Spring Boot 3.x 会传递引入适配好的版本,通常无需你手写死版本号。驱动类名
org.postgresql.Driver(JDBC 4.0+ 已可省略Class.forName,Spring Boot 场景更是自动处理)。
1.6.2 最小连接 Demo
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class PgFirstConnect {
public static void main(String[] args) throws Exception {
// URL 三段式:协议:子协议://主机:端口/数据库名
String url = "jdbc:postgresql://localhost:5432/mydb";
String user = "postgres";
String pwd = "postgres";
try (Connection conn = DriverManager.getConnection(url, user, pwd);
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT version(), current_database()")) {
if (rs.next()) {
System.out.println("连接成功,服务端版本:" + rs.getString(1));
System.out.println("当前数据库:" + rs.getString(2));
}
}
}
}跑通即说明环境 OK。对照 MySQL:URL 从 jdbc:mysql://host:3306/db 换成 jdbc:postgresql://host:5432/db,其余 JDBC 编程模型(DriverManager/Connection/Statement/ResultSet)完全一致,你会的 JDBC 可 100% 复用。
1.6.3 冒烟测试:建库建表一条龙
# 先建一个专供练习的库(在 psql 里执行,或用下面的命令行走)
docker exec -it pg psql -U postgres -c "CREATE DATABASE shopdb;"
docker exec -it pg psql -U postgres -d shopdb -c \
"CREATE TABLE t_product(id serial primary key, name text, price numeric(10,2));"
docker exec -it pg psql -U postgres -d shopdb -c \
"INSERT INTO t_product(name, price) VALUES('键盘', 199.00) RETURNING id;"
docker exec -it pg psql -U postgres -d shopdb -c "SELECT * FROM t_product;"RETURNING id 是 PG(及标准 SQL)的特性:插入后直接返回生成的列,比 MySQL 里再 SELECT LAST_INSERT_ID() 优雅,第 2、5 章还会反复用到。
1.7 本章小结
- PG 比 MySQL 多一层 schema(模式):
库 → 模式 → 表,日常表默认落在public模式,跨库不能直接 JOIN。 - 铁律:未加引号标识符折叠为小写,加
"..."才保留原样且区分大小写——命名请全程小写下划线。 - 用户体系是 role,超管默认
postgres;端口 5432;两大配置文件postgresql.conf(参数)与pg_hba.conf(认证)第 7 章展开。 - 环境搭建推荐 Docker(
postgres:16);命令行工具是 psql,\dt \d \l是日常三件套。 - Java 侧:
org.postgresql:postgresql+jdbc:postgresql://host:5432/db,JDBC 编程模型与 MySQL 完全通用,RETURNING更好用。
✏️ 课后练习
- 用 Docker 起一个
postgres:16,进 psql 执行\l\dn\dt,观察默认有哪些库(postgres/template0/template1)和public模式。 - 亲手复现「小写折叠」:分别执行
CREATE TABLE A_b(c int);和CREATE TABLE "A_B"(C int);,用\dt看两张表的真实名字,再尝试不带引号 SELECT,记录哪些成功哪些报错。 - 把 1.6 的 Java Demo 跑通,并在连接后执行
SHOW search_path;打印当前模式搜索路径。 - 建一个
shop模式,在其中建表并在 Java 里用两段名shop.xxx查询它,体会 schema 隔离。
📖 参考答案
本章题目都是「环境+观察」类,答案给的是完整命令 + 预期回显。假设容器名
pg、超管postgres、密码postgres(与 1.4.2 一致);Windows 本地安装的话把docker exec -it pg换成 psql 直接连。
第 1 题:Docker 起 postgres:16,用 \l \dn \dt 观察默认库与 public 模式
第 1 步,起容器(首次会拉镜像,名字和 1.4.2 一致方便后面章节沿用):
docker run -d --name pg -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=mydb -p 5432:5432 postgres:16
docker ps --filter name=pg --format "{{.Names}} {{.Image}} {{.Status}} {{.Ports}}"
# pg postgres:16 Up 5 seconds 0.0.0.0:5432->5432/tcp第 2 步,进去看库(\l 是 list databases):
docker exec -it pg psql -U postgres -c '\l' List of databases
Name | Owner | Encoding | Collate | Ctype | ICU Locale | Locale Provider | Access privileges
-----------+----------+----------+------------+------------+------------+-----------------+---------------------------
mydb | postgres | UTF8 | C.utf8 | C.utf8 | | libc |
postgres | postgres | UTF8 | C.utf8 | C.utf8 | | libc |
template0 | postgres | UTF8 | C.utf8 | C.utf8 | | libc | =c/postgres +
| | | | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | C.utf8 | C.utf8 | | libc | =c/postgres +
| | | | | | | postgres=CTc/postgres
(4 rows)第 3 步,背下来这三个默认库的用途(面试与运维都用得上):
| 库 | 作用 | 能直连吗 |
|---|---|---|
postgres | 安装自带的「工作库」,运维连接默认落这 | 能 |
template1 | 建库时的克隆模板,你装在它里的东西会被新库继承 | 能(但不建议往它里写业务对象) |
template0 | 原始空模板,用来建「编码/排序规则不同」的干净库 | 否,datallowconn = f |
验证最后一列:
SELECT datname, datallowconn FROM pg_database ORDER BY 1;
-- datname | datallowconn
-- -------------+--------------
-- mydb | t
-- postgres | t
-- template0 | f ← 连不上,所以只能这么用:CREATE DATABASE x TEMPLATE template0 ENCODING 'LATIN1';
-- template1 | t第 4 步,切到 mydb 看模式与表:
docker exec -it pg psql -U postgres -d mydb -c '\dn'
-- List of schemas
-- Name | Owner
-- --------+-------------------
-- public | pg_database_owner
-- (1 row)
-- 注意两点:① 每个库自带且仅自带 public 这一个模式;② PG15 起它的 owner 是角色组 pg_database_owner,
-- 而且默认**撤掉了 PUBLIC 的 CREATE 权限**——这就是很多新手用普通账号建表报
-- “permission denied for schema public” 的根因(第 7 章 GRANT 一节会修它)。
docker exec -it pg psql -U postgres -d mydb -c '\dt'
-- Did not find any relations. ← 新库里 public 下确实一张表都没有第 5 步,顺手把 1.2 的层级心智坐实(实例 → 库 → 模式 → 表 四级各一个查询):
SELECT version(); -- 实例(哪个服务端)
SELECT current_database(), current_user; -- 当前库 + 当前角色(注意是 user 不是 username)
SELECT nspname FROM pg_namespace WHERE nspname NOT LIKE 'pg%'; -- 当前库里的模式列表
SELECT schemaname, tablename FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema');
-- 刚建好的 mydb 里除了 public 什么都没有——这就是「库不跨、模式可隔离」的起点第 2 题:亲手复现「小写折叠」,记录哪些语句成功、哪些报错
第 1 步,建两张表(一个不加引号、一个给表名加双引号):
CREATE TABLE A_b(c int); -- 表名未引号 → 存为 a_b;列名 c 未引号 → 存为 c
CREATE TABLE "A_B"(C int); -- 表名引号 → 原样存 A_B;列名 C 未引号 → 仍然存为 c
-- NOTICE(可选观察):两张表的真实名字看 pg_class
SELECT relname FROM pg_class
WHERE relname IN ('A_b','a_b','A_B','a_B','AB') AND relkind = 'r';
-- relname
-- ---------
-- a_b ← 表 1 存成全小写
-- A_B ← 表 2 存成你写的原样(只有它用了引号)这一行就能看出铁律的细节:引号只保护它包住的那个标识符。同一句里
"A_B"保留了大写,而同句未加引号的列C照样被折叠成c。
第 2 步,\dt 看列表(反斜杠命令不参与折叠,参数必须是「已存好的真实名字」):
\dt
-- List of relations
-- Schema | Name | Type | Owner
-- --------+------+-------+----------
-- public | A_B | table | postgres
-- public | a_b | table | postgres
-- (2 rows)
\d a_b ✅ 看得到表 1 的结构
\d "A_B" ✅ 看得到表 2(名字含大写,给 psql 时也带上引号最稳)
\d A_b ❌ Did not find any relation named "A_b" ← 反斜杠命令不做小写折叠!第 3 步,把四种 SELECT 写法全试一遍,结果如下图(这才是本题真正要记的东西):
SELECT * FROM a_b; ✅ 命中表 1(真实名就是 a_b)
SELECT * FROM A_b; ✅ 命中表 1(折叠后还是 a_b)
SELECT * FROM A_B; ✅ 仍然命中表 1!A_B 被折叠成 a_b——看着不同、实则同一个表,这就是坑
SELECT * FROM "A_B"; ✅ 命中表 2(引号强制区分大小写)
SELECT * FROM "a_b"; ✅ 命中表 1(它的真名就是小写 a_b)
SELECT * FROM "AB"; ❌ ERROR: relation "AB" does not exist (SQLSTATE 42P01)第 4 步,列名的对称实验(很多人只记得表名折叠,列名会坑得更狠):
INSERT INTO "A_B"(C) VALUES(1); ✅ 列 C 未引号 → 折叠成 c → 正好是表 2 的真列名
SELECT c FROM "A_B"; ✅ → 1
SELECT "C" FROM "A_B"; ❌ ERROR: column "C" does not exist ← 列真名是小写 c对比另一种写法(建表时就给列名加了引号,以后每条 SQL 都得带着):
CREATE TABLE "T_Case"("Id" int, Name text); -- 列真名:Id(带引号)、name(不带→小写)
SELECT Id FROM "T_Case"; ❌ column "id" does not exist
SELECT "Id" FROM "T_Case"; ✅
SELECT name FROM "T_Case"; ✅ 这一列反而不用引号(建表时就没加)第 5 步,结论与做法(跟 1.3 的铁律呼应):
PG:未加引号 → 无条件折叠为小写(跨平台一致,不像 MySQL 看操作系统,也和达梦「折叠为大写」相反)
实际危害:不是「查不到」,而是「查错了表不报错」——A_B 与 a_b 在 PG 眼里是同一个对象,
而带引号的 "A_B" 是**另一个**对象,迁移时表名大小写一混就会出现「数据好像没同步」的假象。
做法:表名列名一律小写下划线,永远不加双引号;真迫不得已建了大写对象,之后每条 SQL (包括 Java 里拼的)都必须带引号。第 3 题:跑通 1.6 的 Java Demo,并打印 SHOW search_path;
第 1 步,先准备库(用第 1 题建的 mydb,确保驱动能连到它):
docker exec -it pg psql -U postgres -c "SELECT 1 FROM pg_database WHERE datname='mydb';"
-- 若空集:docker exec -it pg psql -U postgres -c "CREATE DATABASE mydb;"第 2 步,拉驱动 jar(最小路子,不建工程也能跑):
mvn dependency:get -Dartifact=org.postgresql:postgresql:42.7.4
cp ~/.m2/repository/org/postgresql/postgresql/42.7.4/postgresql-42.7.4.jar ./
# Windows 上用:copy %USERPROFILE%\.m2\repository\org\postgresql\postgresql\42.7.4\postgresql-42.7.4.jar .\第 3 步,完整可编译的单文件 Demo(补上 1.6 里没有的 search_path 打印):
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class PgFirstConnect {
public static void main(String[] args) throws Exception {
String url = "jdbc:postgresql://localhost:5432/mydb";
String user = "postgres";
String pwd = "postgres";
try (Connection conn = DriverManager.getConnection(url, user, pwd);
Statement st = conn.createStatement()) {
try (ResultSet rs = st.executeQuery("SELECT version(), current_database()")) {
if (rs.next()) {
System.out.println("连接成功,服务端版本:" + rs.getString(1));
System.out.println("当前数据库:" + rs.getString(2));
}
}
// 新增:查当前模式搜索路径(两种写法等价)
try (ResultSet rs = st.executeQuery("SHOW search_path")) {
rs.next();
System.out.println("search_path = " + rs.getString(1));
}
try (ResultSet rs = st.executeQuery(
"SELECT current_setting('search_path'), current_user")) {
rs.next();
System.out.println("函数写法:" + rs.getString(1) + " | 当前角色:" + rs.getString(2));
}
System.out.println("驱动版本:" + conn.getMetaData().getDriverVersion());
}
}
}第 4 步,编译并运行(Windows 的 classpath 分隔符是分号):
javac -encoding UTF-8 PgFirstConnect.java
java -cp ".;postgresql-42.7.4.jar" PgFirstConnect
# Linux/macOS 用冒号:java -cp ".:postgresql-42.7.4.jar" PgFirstConnect第 5 步,预期输出(对账点:search_path 默认就是那个带 "$user" 的字符串):
连接成功,服务端版本:PostgreSQL 16.4 on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
当前数据库:mydb
search_path = "$user", public
函数写法:"$user", public | 当前角色:postgres
驱动版本:42.7.4第 6 步,把输出读明白(这一步才是本题的知识点):
"$user"指「与当前登录角色同名的模式」。现在角色是postgres,而mydb里没有叫postgres的模式,所以这一项直接落空;- 于是表名不带前缀时只去
public找——这解释了为什么 1.6.3 的建表全落在public; - 驱动版本能打印出来,说明 JDBC 4.0+ 自动发现驱动生效,确实不用写
Class.forName。
加分实验:把 URL 改成 jdbc:postgresql://localhost:5432/mydb?currentSchema=shop 重跑,search_path 会直接变成 shop(第 5 章靠的就是这个参数)。
第 4 题:建 shop 模式,在其中建表,并在 Java 里用两段名 shop.xxx 查询
第 1 步,建模式 + 建表 + 播种(可手算的 3 行):
CREATE SCHEMA IF NOT EXISTS shop;
CREATE TABLE shop.t_book (
id serial PRIMARY KEY,
title text NOT NULL,
price numeric(10,2) NOT NULL
);
INSERT INTO shop.t_book(title, price) VALUES
('Java实战', 99.00), ('MySQL入门', 59.00), ('PG精解', 89.00) RETURNING id;
-- 返回 id 1、2、3第 2 步,先在 psql 里把「不带前缀会失败」坐实(这就是 schema 隔离的直接证据):
SELECT * FROM t_book; -- ❌ ERROR: relation "t_book" does not exist
SELECT * FROM shop.t_book ORDER BY id; -- ✅ 三行都出来
-- id | title | price
-- ----+----------+-------
-- 1 | Java实战 | 99.00
-- 2 | MySQL入门 | 59.00
-- 3 | PG精解 | 89.00
SELECT sum(price) FROM shop.t_book; -- → 247.00(99+59+89,手算可验)第 3 步,再验「两个模式可以各有一张同名表且互不干扰」(schema 的真正价值):
CREATE TABLE public.t_book (id int, note text);
INSERT INTO public.t_book VALUES (1, '我在 public');
SELECT * FROM public.t_book; -- → 1 | 我在 public (只有 1 行,列是 note)
SELECT * FROM shop.t_book; -- → shop 那三行,列是 title/price
\dt shop.* -- 列出 shop 下的表:t_book两张 t_book 共存不报错——MySQL 里没有这一层(它只有库级隔离,跨库 JOIN 还能写,PG 跨库连查都做不到)。
第 4 步,Java 里用两段名查(在第 3 题的工程上改一句 SQL 即可):
try (ResultSet rs = st.executeQuery(
"SELECT id, title, price FROM shop.t_book WHERE price > 60 ORDER BY id")) {
while (rs.next()) {
System.out.println(rs.getInt("id") + " | " + rs.getString("title")
+ " | " + rs.getBigDecimal("price"));
}
}
// 预期输出(price > 60 滤掉了 59.00 的 MySQL入门):
// 1 | Java实战 | 99.00
// 3 | PG精解 | 89.00第 5 步,把两段名换成 currentSchema,体会「同一份代码两种配置」:
// 方案 A:SQL 里写全 shop.t_book(优点:不依赖连接参数,多人多环境都不得错)
// 方案 B:URL 加参数,SQL 里只写 t_book
String url = "jdbc:postgresql://localhost:5432/mydb?currentSchema=shop";
// 此时 st.executeQuery("SELECT count(*) FROM t_book") → 3(直接命中 shop 那张)也可以不开新连接,会话内临时改:
SET search_path = shop;(只对当前连接有效,连接池场景下必须设回,否则下个使用者会查到意外模式)。工程里推荐 5.1.3 的做法:URL 统一currentSchema=shop,同时 DDL 脚本里带shop.前缀。
第 6 步,收尾清理(避免影响后面章节的示例):
DROP SCHEMA shop CASCADE; -- CASCADE 会连着模式里的表一起删,psql 会警告并列出将被删的对象
DROP TABLE public.t_book;