PostgreSQL(PGSQL)从零开始完整教程(小白也能看懂)
上一篇我们写了 Go 服务器,能返回写死在代码里的数据。但真实的网站数据(用户、订单、文章)必须存到数据库里——关掉服务器数据也不能丢。
这一篇学 PostgreSQL(简称 PG 或 PGSQL)——目前最流行的开源数据库之一,也是很多大厂后端的主力。
一、数据库是什么?用”仓库”理解
想象一个大型仓库(数据库),里面有好多货架(表),每个货架放一种商品(数据),每件商品有编号(主键)。
- 数据库(Database):整个仓库
- 表(Table):货架,比如”用户表”、“订单表”
- 行(Row):一条数据,比如一个用户
- 列(Column):一个字段,比如”用户名”、“年龄”
用户表(users)┌────┬──────────┬─────┐│ id │ name │ age │ ← 列(字段)├────┼──────────┼─────┤│ 1 │ 小明 │ 18 │ ← 行(一条数据)│ 2 │ 小红 │ 17 │└────┴──────────┴─────┘SQL(结构化查询语言)就是和数据库对话的语言:SELECT(查)、INSERT(增)、UPDATE(改)、DELETE(删)。任何数据库(PG、MySQL、SQLite)都基本通用。
二、PostgreSQL vs MySQL:怎么选?
| 对比 | PostgreSQL | MySQL |
|---|---|---|
| 特点 | 功能强大、标准严格、支持复杂查询 | 轻量、普及率高、上手快 |
| 性能 | 复杂查询更强 | 简单读写很快 |
| 场景 | 复杂业务、数据分析、金融 | 中小网站、经典 Web 应用 |
| 免费 | ✅ | ✅ |
选哪个? 学数据库建议直接学 PostgreSQL——它更现代、功能更全,而且学会 PG 语法,MySQL 基本无痛迁移(反过来不一定)。我们网站(Halo2 + Astro)也用的是 PG。
三、安装 PostgreSQL
macOS
brew install postgresql@16Ubuntu / Debian
sudo apt updatesudo apt install postgresql postgresql-contribWindows
去 postgresql.org 下载安装包,一路下一步(记住安装时设置的密码)。
启动服务
# macOSbrew services start postgresql@16
# Ubuntusudo systemctl start postgresql四、连接数据库:psql 命令行
PostgreSQL 装好后,默认有一个超级用户 postgres。打开终端连接:
sudo -u postgres psql # Ubuntu 下用这个# 或psql -U postgres -h localhost # 然后输入密码看到类似 postgres=# 的提示符,就说明连上了。
常用管理命令(psql 里执行)
-- 查看所有数据库\l
-- 查看当前数据库所有表\dt
-- 退出\q创建数据库
CREATE DATABASE myapp;注意: SQL 命令以分号 ; 结尾,不写分号命令不会执行。这是新手最常见的坑。
连接某个数据库
\c myapp或者直接连接:
psql -U postgres -d myapp五、建表:CREATE TABLE
表要先定义好”有哪些列、每列什么类型”。用上一篇文章的用户例子:
CREATE TABLE users ( id SERIAL PRIMARY KEY, -- 自增主键(1、2、3 自动生成) name VARCHAR(50) NOT NULL, -- 姓名,最长50字符,不能为空 age INT CHECK (age >= 0), -- 年龄,且必须大于等于0 email VARCHAR(100) UNIQUE, -- 邮箱,不能重复 created_at TIMESTAMP DEFAULT NOW() -- 创建时间,默认当前时间);常用类型速查:
| 类型 | 说明 | 例子 |
|---|---|---|
| SERIAL | 自增整数(主键常用) | 1, 2, 3… |
| INT | 整数 | 18, -5 |
| BIGINT | 大整数 | 9000000000 |
| NUMERIC(10,2) | 小数(10位整数+2位小数) | 99.99 |
| VARCHAR(n) | 变长字符串 | ”小明” |
| TEXT | 长文本 | 文章内容 |
| BOOLEAN | 布尔 | true/false |
| DATE | 日期 | 2026-08-11 |
| TIMESTAMP | 日期时间 | 2026-08-11 09:30:00 |
| JSONB | JSON 数据(PG 特色) | {“key”: “value”} |
主键(PRIMARY KEY):每行的唯一编号,不能重复、不能为空。就像身份证号。
六、增删改查:CRUD 四大金刚
1. 插入数据:INSERT
INSERT INTO users (name, age, email) VALUES ('小明', 18, 'xiaoming@example.com');
-- 插入多条INSERT INTO users (name, age, email) VALUES ('小红', 17, 'xiaohong@example.com'), ('小刚', 20, 'xiaogang@example.com');id 和 created_at 不用写——id 自动生成,created_at 有默认值。
2. 查询数据:SELECT
-- 查所有列所有行(开发时常用,线上慎用)SELECT * FROM users;
-- 只查指定列SELECT name, age FROM users;
-- 加条件:WHERESELECT * FROM users WHERE age >= 18;
-- 精确匹配SELECT * FROM users WHERE name = '小明';
-- 多个条件:AND / ORSELECT * FROM users WHERE age >= 18 AND email LIKE '%example.com';LIKE 模糊查询: % 表示任意字符,LIKE '%example.com' 意思是”结尾是 example.com”。
3. 更新数据:UPDATE
-- 把 id=1 的用户的年龄改成 19UPDATE users SET age = 19 WHERE id = 1;
-- 改多个字段UPDATE users SET age = 20, email = 'new@example.com' WHERE id = 1;⚠️ 最重要的一条安全规则:UPDATE 和 DELETE 一定要带 WHERE!
UPDATE users SET age = 19; -- 危险!没有 WHERE,会把所有用户的年龄都改成194. 删除数据:DELETE
-- 删除 id=2 的用户DELETE FROM users WHERE id = 2;
-- 清空整张表(保留表结构)TRUNCATE TABLE users;七、排序、分页、去重
-- 按年龄从小到大排序SELECT * FROM users ORDER BY age ASC;
-- 按年龄从大到小SELECT * FROM users ORDER BY age DESC;
-- 只取前 2 条(分页常用)SELECT * FROM users ORDER BY id LIMIT 2;
-- 跳过前 2 条,再取 2 条(第二页)SELECT * FROM users ORDER BY id LIMIT 2 OFFSET 2;
-- 去重:查看所有不同的年龄SELECT DISTINCT age FROM users;分页公式: LIMIT 每页数量 OFFSET (页码-1) * 每页数量。第 2 页 = LIMIT 2 OFFSET 2,第 3 页 = LIMIT 2 OFFSET 4。
八、聚合函数:统计数据的利器
-- 一共有多少个用户SELECT COUNT(*) FROM users;
-- 平均年龄SELECT AVG(age) FROM users;
-- 最大/最小年龄SELECT MAX(age), MIN(age) FROM users;
-- 年龄总和SELECT SUM(age) FROM users;GROUP BY:按组统计
-- 按年龄分组,看每个年龄有多少人SELECT age, COUNT(*) FROM users GROUP BY age;九、约束:给数据”定规矩”
约束是数据库自动执行的”检查规则”,防止脏数据入库:
| 约束 | 作用 | 例子 |
|---|---|---|
| NOT NULL | 不能为空 | name VARCHAR(50) NOT NULL |
| UNIQUE | 不能重复 | email VARCHAR(100) UNIQUE |
| CHECK | 必须满足条件 | age INT CHECK (age >= 0) |
| PRIMARY KEY | 主键(唯一+非空) | id SERIAL PRIMARY KEY |
| FOREIGN KEY | 外键,关联别的表 | user_id INT REFERENCES users(id) |
外键(FOREIGN KEY)是数据库最重要的概念之一。它让两张表”关联”起来。
十、多表关联:JOIN(重点!)
真实项目从来不止一张表。比如:用户表 + 订单表。
-- 订单表:每个订单属于哪个用户CREATE TABLE orders ( id SERIAL PRIMARY KEY, user_id INT NOT NULL REFERENCES users(id), -- 外键:指向 users 表的 id product VARCHAR(100), amount NUMERIC(10, 2));
INSERT INTO orders (user_id, product, amount) VALUES (1, '键盘', 199.00), (1, '鼠标', 89.00), (3, '显示器', 1299.00);现在问:每个订单是谁买的? 需要把 orders 和 users 两张表”连起来”查——这就是 JOIN:
SELECT orders.id, users.name, orders.product, orders.amountFROM ordersJOIN users ON orders.user_id = users.id;输出:
id | name | product | amount----+------+---------+-------- 1 | 小明 | 键盘 | 199.00 2 | 小明 | 鼠标 | 89.00 3 | 小刚 | 显示器 | 1299.00JOIN 的原理(用比喻讲): 想象两张表是两本册子,JOIN ON 就是”用订书机把 user_id 等于 users.id 的页订在一起”。ON 后面写”两张表靠什么对上”。
简化写法(表别名):
SELECT o.id, u.name, o.product, o.amountFROM orders oJOIN users u ON o.user_id = u.id;JOIN 的三种常用类型:
-- INNER JOIN:只取两边都有的(默认 JOIN 就是它)SELECT ... FROM orders JOIN users ON ...;
-- LEFT JOIN:左表全保留,右表没有的显示 NULLSELECT ... FROM users LEFT JOIN orders ON ...;
-- RIGHT JOIN:右表全保留SELECT ... FROM users RIGHT JOIN orders ON ...;实战场景:查每个用户买了多少钱
SELECT u.name, SUM(o.amount) AS total_spentFROM users uLEFT JOIN orders o ON o.user_id = u.idGROUP BY u.nameORDER BY total_spent DESC NULLS LAST;LEFT JOIN 很重要: 用 LEFT JOIN 才能把”没买过东西的用户”也显示出来(金额是 NULL)。
十一、索引:给查询提速
数据一多(几百万行),全表扫描就慢了。索引就像书的目录——不用翻完整本书,直接定位。
-- 给 email 列建索引(以后按 email 查询会很快)CREATE INDEX idx_users_email ON users(email);什么时候建索引:
- 经常在 WHERE 里用的列(如 email、user_id)
- 经常 JOIN 的列(外键)
注意: 索引不是越多越好,它会让写入(INSERT/UPDATE)变慢,占用空间。小表完全不用建。
验证查询是否走了索引:
EXPLAIN SELECT * FROM users WHERE email = 'xiaoming@example.com';看到 Index Scan 说明用上了索引。
十二、事务:要么全成功,要么全失败
事务(Transaction)保证一组操作”要么全部成功,要么全部回滚”。最经典的例子是转账:
-- 小明转账 100 元给小红BEGIN; -- 1. 开始事务
UPDATE accounts SET balance = balance - 100 WHERE name = '小明';UPDATE accounts SET balance = balance + 100 WHERE name = '小红'; -- 2. 执行操作
COMMIT; -- 3. 提交:确认生效如果中间任何一步出错,可以用 ROLLBACK 撤销:
BEGIN;UPDATE accounts SET balance = balance - 100 WHERE name = '小明';-- 这里出错了!ROLLBACK; -- 撤销所有操作,钱没少事务的四大特性(ACID)记住四个词就行:
- 原子性:要么全做,要么全不做
- 一致性:数据始终合理(不会出现钱凭空消失)
- 隔离性:事务之间互不干扰
- 持久性:提交后数据不会丢
十三、备份与恢复:数据是命根子
# 备份整个数据库到文件pg_dump -U postgres myapp > myapp_backup.sql
# 恢复psql -U postgres -d myapp < myapp_backup.sql建议: 定时备份(比如每天一次),这是运维的底线。丢失数据是比丢代码严重得多的事故。
十四、实战:Go 连接 PostgreSQL(和上一篇衔接)
现在把 Go 和 PG 连起来,做一个真正读写数据库的后端接口。
1. 安装驱动
go mod init myappgo get github.com/lib/pq2. 写代码:查询用户列表
package main
import ( "database/sql" "encoding/json" "fmt" "log" "net/http"
_ "github.com/lib/pq")
type User struct { ID int `json:"id"` Name string `json:"name"` Age int `json:"age"`}
func main() { // 连接数据库 // 格式:postgres://用户名:密码@主机:端口/数据库名 connStr := "postgres://postgres:你的密码@localhost:5432/myapp?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("数据库连接成功!")
// 接口:返回所有用户 http.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) { // 查询数据库 rows, err := db.Query("SELECT id, name, age FROM users") if err != nil { http.Error(w, err.Error(), 500) return } defer rows.Close()
// 遍历结果 var users []User for rows.Next() { var u User if err := rows.Scan(&u.ID, &u.Name, &u.Age); err != nil { http.Error(w, err.Error(), 500) return } users = append(users, u) }
// 返回 JSON w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(users) })
fmt.Println("服务器启动:http://localhost:8080") http.ListenAndServe(":8080", nil)}运行:
go run main.go浏览器打开 http://localhost:8080/api/users,就能看到数据库里的用户数据变成了 JSON:
[{"id":1,"name":"小明","age":18},{"id":3,"name":"小刚","age":20}]这一刻,你已经打通了完整的全栈链路:
前端 (JS/fetch) → 后端 (Go HTTP接口) → 数据库 (PostgreSQL)用户改数据 → 存进数据库 → 刷新页面数据还在。这才是真实网站的工作方式。
十五、总结
这一篇我们从零学会了 PostgreSQL:
- 数据库基础:仓库 → 货架(表)→ 商品(行)
- 安装与连接:psql 命令行
- 建表:CREATE TABLE,主键、类型
- CRUD:INSERT / SELECT / UPDATE / DELETE
- 排序分页去重:ORDER BY、LIMIT、OFFSET、DISTINCT
- 聚合函数:COUNT、AVG、MAX、GROUP BY
- 约束:NOT NULL、UNIQUE、CHECK、外键
- JOIN 多表查询:把两张表关联起来(重点)
- 索引:查询提速
- 事务:ACID,转账例子
- 备份恢复:pg_dump
- Go + PG 实战:写一个真正的全栈接口
你的后端学习主线已经完成:
Go 基础 → PostgreSQL → Go 连接数据库 → 全栈打通
下一步就是自己动手建个表、写个增删改查接口,把数据跑起来。数据库是后端的地基,多写多练,很快就熟了。