PostgreSQL 从入门到精通:一场与开源数据库的深度对话
文章目录
- 1. 认识 PostgreSQL:一个有故事的数据库
- 1.1 从伯克利走来的 "数据库老兵"
- 1.2 为什么选择 PostgreSQL?
- 1.3 核心特性深度解读
- 2. 安装与配置:让 PostgreSQL 在你的电脑安家
- 2.1 不同系统的安装指南(附避坑指南)
- Ubuntu/Debian 系统:像安装普通软件一样简单
- CentOS/RHEL 系统:需要先加仓库
- Windows 系统:图形化向导更友好
- 2.2 初始化配置:给你的数据库 "做个初始化体检"
- 第一步:登录并修改超级用户密码
- 第二步:创建数据库和用户
- 第三步:认识核心配置文件
- 3. 数据库基础操作:和 PostgreSQL"对话" 的开始
- 3.1 连接数据库:建立你的 "通信渠道"
- 3.2 数据库与模式:给数据 "安家落户"
- 数据库操作
- 模式操作
- 3.3 表操作:搭建数据的 "骨架"
- 创建表:定义清晰的 "数据规则"
- 修改表结构:给 "骨架" 做 "微调"
- 删除表:"推倒重来" 的操作
- 4. 数据类型详解:给数据 "贴标签"
- 4.1 基本数据类型:最常用的 "基础款"
- 数值类型:处理数字的 "工具箱"
- 字符类型:存储文本的 "容器"
- 日期时间类型:时间的 "记录者"
- 4.2 特殊数据类型:应对复杂场景的 "特种兵"
- 布尔类型:非此即彼的 "判断器"
- 枚举类型:限定范围的 "选项框"
- 数组类型:批量存储的 "集合袋"
- JSON 类型:灵活存储的 "万能袋"
- 范围类型:表示区间的 "尺子"
- 5. SQL 查询与数据操作:和数据 "互动" 的核心
- 5.1 数据插入:给表 "添砖加瓦"
- 基本插入:单条数据
- 批量插入:一次多条数据
- 从查询结果插入:复制数据
- 返回插入的数据:知道插了啥
- 5.2 数据查询:从表中 "提取信息"
- 基本查询:查看数据
- 条件过滤:找到你要的数据
- 排序:让结果更有序
- 分页:大结果集 "分段看"
- 去重:消除重复数据
- 聚合函数:统计数据
- 条件判断:用 CASE 表达式
- 5.3 数据更新与删除:修改或清理数据
- 更新数据:修改已有记录
- 删除数据:移除不需要的记录
- 6. 高级查询技术:挖掘数据的 "深层价值"
- 6.1 连接查询:多表数据 "强强联合"
- 内连接(INNER JOIN):取两表的交集
- 左外连接(LEFT JOIN):保留左表所有记录
- 右外连接(RIGHT JOIN):保留右表所有记录
- 全外连接(FULL JOIN):保留两表所有记录
- 自连接:表自己和自己连接
- 6.2 子查询:查询中的 "嵌套查询"
- 标量子查询:返回单个值
- 行子查询:返回一行多列
- 表子查询:返回多行多列
- 相关子查询:依赖外部查询的子查询
- EXISTS 子查询:判断是否存在记录
- 6.3 公共表表达式 (CTE):复杂查询的 "模块化工具"
- 基本 CTE:将查询拆分成步骤
- 递归 CTE:处理层次结构数据
- 6.4 窗口函数:分组统计的 "高级玩法"
- 排名函数:给数据 "排座次"
- 聚合窗口函数:每组的统计值
- 窗口帧:定义统计的 "范围"
- 7. 索引与性能优化:让查询 "跑" 得更快
- 7.1 索引类型与创建:选择合适的 "目录"
- B-tree 索引:最常用的 "万能钥匙"
- 唯一索引:确保数据 "不重复"
- 部分索引:只索引 "有用的数据"
- 表达式索引:对计算结果创建索引
- 哈希索引:等值查询的 "专项选手"
- GiST 和 GIN 索引:特殊数据类型的 "专属工具"
- BRIN 索引:超大表的 "轻量级索引"
- 7.2 查询性能分析:找到 "慢" 的原因
- 使用 EXPLAIN 分析查询计划
- 使用 EXPLAIN ANALYZE 获取实际执行统计
- 查看索引使用情况
- 7.3 查询优化技巧:让查询 "提速" 的实用方法
- 1. 避免使用 SELECT *
- 2. 合理使用 LIMIT 限制结果
- 3. 避免在 WHERE 子句中对列使用函数
- 4. 批量操作代替循环
- 5. 用连接代替相关子查询
- 8. 事务与并发控制:保证数据的 "准确性"
- 8.1 事务管理:把操作 "打包"
- 基本事务
- 事务回滚:出错时 "撤销" 操作
- 保存点:事务中的 "checkpoint"
- 事务隔离级别:控制并发时的可见性
- 8.2 锁机制:并发时的 "交通规则"
- 行级锁:只锁需要修改的行
- 表级锁:锁定整个表
- 查看锁信息:诊断锁冲突
- 9. 高级特性:PostgreSQL 的 "隐藏大招"
- 9.1 存储过程与函数:数据库中的 "小程序"
- 创建和调用函数
- 带副作用的函数(修改数据)
- 返回表的函数
- 9.2 触发器:自动执行的 "钩子"
- 创建触发器示例:记录员工数据变更日志
- 9.3 全文搜索:让数据库 "读懂" 文本
- 基本使用步骤
- 10. 安全与权限管理:守护数据的 "安全门"
- 10.1 用户与角色管理:给用户 "分配权限"
- 创建角色和用户
- 授予权限
- 角色成员关系
- 查看角色和权限
- 10.2 行级安全策略:数据访问的 "精细化控制"
- 启用行级安全并创建策略
- 使用行级安全
- 11. 备份与恢复:给数据 "买保险"
- 11.1 逻辑备份与恢复:灵活的 "数据快照"
- 使用 pg_dump 备份
- 恢复数据库
- 11.2 物理备份与时间点恢复 (PITR):应对 "重大事故"
- 配置归档模式(前提)
- 创建基础备份
- 时间点恢复(PITR)
- 12. 高可用与复制:让数据库 "不宕机"
- 12.1 流复制设置:主从架构
- 主库配置
- 从库配置
- 12.2 监控复制状态
- 在主库上查看复制状态
- 在从库上查看复制状态
- 13. 扩展与插件:给 PostgreSQL"装插件"
- 13.1 常用扩展介绍
- 查看可用扩展
- 安装和使用扩展
- 实用扩展示例
- 14. 监控与维护:让数据库 "健康运行"
- 14.1 数据库监控:了解数据库 "状态"
- 查看数据库和表大小
- 查看连接和活动查询
- 查看锁冲突
- 14.2 维护任务:保持数据库 "活力"
- VACUUM:清理无用数据
- 重建索引:修复索引碎片
- 更新统计信息:帮助优化器生成更好的计划
- 15. 最佳实践与总结:PostgreSQL 的 "使用说明书"
- 15.1 设计规范:打好 "地基"
- 15.2 性能优化:让数据库 "跑" 得快
- 15.3 安全实践:守护数据 "安全"
- 15.4 高可用性:保证业务 "不中断"
- 参考文献
嘿,朋友!如果你正在寻找一款可靠、强大又灵活的数据库管理系统,那今天咱们要聊的 PostgreSQL,绝对值得你泡上一杯咖啡,静下心来好好认识一下。它可不是那种冷冰冰的技术工具 —— 更像是一个经历了 30 多年打磨的 “老伙计”,既保留着开源世界的纯粹,又藏着应对复杂业务的智慧。接下来,咱们就从初识到深交,一点点揭开它的面纱。
1. 认识 PostgreSQL:一个有故事的数据库
1.1 从伯克利走来的 “数据库老兵”
故事要从 1986 年说起。当时加州大学伯克利分校的 Michael Stonebraker 教授带着团队启动了 POSTGRES 项目,目标很简单:打造一个超越当时主流数据库的系统。你可能会问,为什么叫 “POSTGRES”?其实是 “Post-Ingres” 的缩写 ——Ingres 是 Stonebraker 教授之前开发的数据库,而 Postgres 则是站在巨人肩膀上的升级之作。
30 多年过去,这个项目从学术研究走向了产业实践:1996 年首次发布开源版本,2000 年更名为 PostgreSQL(加入 SQL 支持的标志),如今已更新到 16 版本。它就像一位不断学习的老兵,既坚守着 “ACID 兼容” 的底线,又持续吸收着新的技术理念 —— 比如对 JSON 的原生支持、地理空间数据处理、全文搜索等,都是它与时俱进的证明。
1.2 为什么选择 PostgreSQL?
咱们聊聊实际的:为什么那么多企业和开发者钟情于它?
- 不挑食的 “多面手”:无论是结构化数据(表)、半结构化数据(JSON)、非结构化文本,还是地理空间信息,它都能妥善处理。
- 并发控制的 “智慧家”:采用多版本并发控制(MVCC),简单说就是 “读不阻塞写,写不阻塞读”。比如你在查询数据时,别人修改数据不会打断你,因为你看到的是数据的一个 “快照”。
- 扩展能力的 “变形金刚”:支持自定义函数、存储过程、数据类型,甚至能通过扩展插件获得新功能(比如 PostGIS 处理地图数据)。
- 开源世界的 “清流”:采用 PostgreSQL 许可证,允许商业使用、修改和分发,没有隐藏的专利陷阱。
1.3 核心特性深度解读
咱们来拆解开它的 “核心技能包”,看看这些特性到底意味着什么:
- 完全 ACID 兼容:这是数据库的 “基本功”。原子性(Atomicity)确保事务要么全成,要么全败(比如转账时不会出现 “钱扣了没到账”);一致性(Consistency)保证数据从一个合法状态到另一个合法状态(比如余额不能为负);隔离性(Isolation)避免多事务相互干扰;持久性(Durability)确保提交的数据不会因崩溃丢失。
- 丰富的索引类型:别以为索引只有 B-tree 一种!PostgreSQL 提供了 “全家桶”:
- B-tree:最常用,适合等值查询(=)、范围查询(>、<、BETWEEN)
- Hash:只适合等值查询,性能有时比 B-tree 好,但不支持范围查询
- GiST:擅长处理 “层次化” 数据,比如地理坐标(判断两点距离)、全文搜索
- GIN:对数组、JSONB 等类型特别友好,查询速度快但索引体积大
- BRIN:适合超大表(比如亿级行),当数据有物理顺序时(如时间戳),用它准没错
- 多版本并发控制(MVCC):这是 PostgreSQL 处理并发的 “独门秘籍”。传统数据库用锁机制,一个事务修改数据时会锁住记录,其他事务只能等待;而 MVCC 会为每个事务创建数据的 “快照”,读事务看到的是快照数据,写事务修改新版本,两者互不干扰。这就是为什么高并发场景下,PostgreSQL 依然能保持流畅。
2. 安装与配置:让 PostgreSQL 在你的电脑安家
2.1 不同系统的安装指南(附避坑指南)
Ubuntu/Debian 系统:像安装普通软件一样简单
# 更新包列表(这一步很重要,避免安装旧版本)
sudo apt update# 安装PostgreSQL和扩展包(contrib包含很多实用工具)
sudo apt install postgresql postgresql-contrib# 检查服务是否启动(看到active (running)就说明成功了)
sudo systemctl status postgresql
避坑点:Ubuntu 默认会创建一个名为postgres
的系统用户和数据库超级用户,初始没有密码,但只能通过系统用户postgres
登录。如果启动失败,可能是端口 5432 被占用,用netstat -tulpn | grep 5432
查看占用进程。
CentOS/RHEL 系统:需要先加仓库
# 添加PostgreSQL官方仓库(以12版本为例,可替换为13/14等)
sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装服务器端软件
sudo yum install -y postgresql12-server# 初始化数据库(这一步会创建数据目录和基础表结构)
sudo /usr/pgsql-12/bin/postgresql-12-setup initdb# 启动服务并设置开机自启
sudo systemctl start postgresql-12
sudo systemctl enable postgresql-12
避坑点:CentOS 默认的 SELinux 可能会阻止 PostgreSQL 访问文件,若启动失败,可临时关闭 SELinux:sudo setenforce 0
,或永久修改/etc/selinux/config
文件。
Windows 系统:图形化向导更友好
- 从官网下载对应版本:https://www.postgresql.org/download/windows/(建议选最新稳定版)
- 运行安装程序,注意这几个关键点:
- 安装目录:避免有空格或中文(比如
C:\PostgreSQL
就比C:\Program Files\PostgreSQL
好) - 端口:默认 5432,若被占用可修改(记住你改的端口,后续连接要用)
- 超级用户密码:一定要记牢!这是
postgres
用户的密码
- 安装目录:避免有空格或中文(比如
- 安装完成后,会自动启动服务,还会安装 pgAdmin(图形化管理工具,新手友好)
2.2 初始化配置:给你的数据库 “做个初始化体检”
安装完成后,咱们需要做些基础配置,让数据库更易用。
第一步:登录并修改超级用户密码
# 切换到postgres系统用户(Linux/Mac)
sudo -i -u postgres# 启动psql命令行工具(这是PostgreSQL的"终端")
psql# 修改postgres用户密码(把'mypassword'换成你的密码)
\password postgres
# 执行后会提示输入新密码,输入时不会显示,输完回车即可
第二步:创建数据库和用户
-- 创建一个名为mydb的数据库
CREATE DATABASE mydb;-- 创建一个名为myuser的用户,设置加密密码
CREATE USER myuser WITH ENCRYPTED PASSWORD 'mypassword';-- 把mydb的所有权限授予myuser(实际生产中建议按需求授权,不要给全部)
GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;
第三步:认识核心配置文件
PostgreSQL 的 “脾气” 都在配置文件里,主要有三个:
-
postgresql.conf:主配置文件,比如端口、内存分配、日志设置等。
- 位置:Linux 通常在
/var/lib/pgsql/12/data/
(12 是版本号),Windows 在安装目录\data\
- 关键参数:
port = 5432
:端口号,修改后需重启服务max_connections = 100
:最大连接数,根据服务器性能调整shared_buffers = 128MB
:共享内存缓冲区,建议设为服务器内存的 1/4
- 位置:Linux 通常在
-
pg_hba.conf:控制谁能连接数据库,以及用什么方式认证。
-
格式:
TYPE DATABASE USER ADDRESS METHOD
-
示例:允许 192.168.1.0 网段的用户用密码连接所有数据库
host all all 192.168.1.0/24 md5
-
-
pg_ident.conf:用于系统用户和数据库用户的映射(较少用到)。
小贴士:修改配置文件后,需要重启服务生效(sudo systemctl restart postgresql
)。
3. 数据库基础操作:和 PostgreSQL"对话" 的开始
3.1 连接数据库:建立你的 “通信渠道”
要操作数据库,首先得建立连接。psql 命令行工具是最直接的方式,语法如下:
psql -h 主机名 -p 端口号 -U 用户名 -d 数据库名
举几个例子:
# 连接本地默认数据库(主机默认localhost,端口默认5432)
psql -U postgres -d postgres# 连接远程数据库(比如连接192.168.1.100的mydb数据库)
psql -h 192.168.1.100 -p 5432 -U myuser -d mydb
进入 psql 后,这些快捷键能帮你提高效率:
\l
:查看所有数据库(类似 “list”)\c 数据库名
:切换数据库(类似 “connect”)\dt
:查看当前数据库的所有表(类似 “describe tables”)\d 表名
:查看表结构\q
:退出 psql
如果需要执行 SQL 文件(比如初始化脚本),用\i
命令:
# 在psql中执行当前目录下的init.sql
\i ./init.sql
3.2 数据库与模式:给数据 “安家落户”
数据库(Database)是数据的 “大仓库”,而模式(Schema)是仓库里的 “货架”—— 同一个数据库可以有多个模式,用来区分不同用途的表(比如hr
模式放人事表,sales
模式放销售表)。
数据库操作
-- 创建数据库时指定所有者、编码等参数
CREATE DATABASE company WITH OWNER = postgres -- 所有者ENCODING = 'UTF8' -- 字符编码(建议用UTF8支持中文)LC_COLLATE = 'zh_CN.UTF-8' -- 排序规则(影响中文排序)LC_CTYPE = 'zh_CN.UTF-8' -- 字符分类(影响大小写转换等)CONNECTION LIMIT = -1; -- 连接限制(-1表示无限制)-- 删除数据库(危险操作!确认后再执行)
DROP DATABASE IF EXISTS company;
模式操作
-- 创建模式,指定由hr_manager用户管理
CREATE SCHEMA hr AUTHORIZATION hr_manager;-- 在hr模式下创建表(两种方式)
-- 方式1:创建时指定模式
CREATE TABLE hr.employees (id INT);-- 方式2:先切换模式,再创建表
SET search_path TO hr; -- 设置当前搜索路径为hr模式
CREATE TABLE departments (id INT);-- 查看所有模式
SELECT schema_name FROM information_schema.schemata;
为什么用模式? 比如一个电商系统,订单表、用户表、商品表可以分属不同模式,既避免表名冲突,又方便权限管理(比如只给运营看 sales 模式的表)。
3.3 表操作:搭建数据的 “骨架”
表是存储数据的基本单位,就像 Excel 的工作表。创建表时,需要定义字段名、数据类型和约束。
创建表:定义清晰的 “数据规则”
CREATE TABLE employees (id SERIAL PRIMARY KEY, -- 自增主键(SERIAL会自动创建序列)first_name VARCHAR(50) NOT NULL, -- 名,非空last_name VARCHAR(50) NOT NULL, -- 姓,非空email VARCHAR(100) UNIQUE, -- 邮箱,唯一(不允许重复)hire_date DATE DEFAULT CURRENT_DATE, -- 入职日期,默认当前日期salary NUMERIC(10,2) CHECK (salary > 0), -- 工资,必须大于0department_id INTEGER,-- 外键约束:关联departments表的idCONSTRAINT fk_departmentFOREIGN KEY(department_id) REFERENCES departments(id)ON DELETE SET NULL -- 当关联的部门被删除时,此字段设为NULL
);
字段解读:
SERIAL
:自动增长的整数,适合做主键(PostgreSQL 10 + 推荐用IDENTITY
,更标准)VARCHAR(50)
:可变长度字符串,最长 50 字符(超出会报错)CHECK (salary > 0)
:确保工资为正数,避免不合理数据- 外键:保证数据一致性(比如不能给员工分配不存在的部门 id)
修改表结构:给 “骨架” 做 “微调”
-- 新增字段(电话)
ALTER TABLE employees ADD COLUMN phone VARCHAR(15);-- 修改字段约束(电话设为非空)
-- 注意:如果表中已有数据,需先确保所有行的phone不为NULL,否则会失败
ALTER TABLE employees ALTER COLUMN phone SET NOT NULL;-- 重名字段(phone改为phone_number)
ALTER TABLE employees RENAME COLUMN phone TO phone_number;-- 删除字段(谨慎操作!数据会丢失)
ALTER TABLE employees DROP COLUMN phone_number;
删除表:“推倒重来” 的操作
-- 删除表(如果存在),并级联删除依赖它的对象(如外键引用)
DROP TABLE IF EXISTS employees CASCADE;
警告:DROP TABLE
会永久删除表和数据,执行前一定要备份!如果只是想清空数据保留表结构,用TRUNCATE TABLE employees;
。
4. 数据类型详解:给数据 “贴标签”
PostgreSQL 支持的数据类型非常丰富,就像给不同类型的物品准备了不同的收纳盒。选对数据类型,不仅能节省空间,还能提高查询效率。
4.1 基本数据类型:最常用的 “基础款”
数值类型:处理数字的 “工具箱”
CREATE TABLE numeric_types (small_int SMALLINT, -- 2字节整数,范围-32768到32767(适合小范围数字,如年龄)integer_col INTEGER, -- 4字节整数,-2147483648到2147483647(最常用,如ID)big_int BIGINT, -- 8字节整数,适合大数字(如订单号、手机号)decimal_col DECIMAL(10,2), -- 精确数值,10位数字,2位小数(适合金额)real_col REAL, -- 4字节浮点数,约6位小数精度(适合非精确计算,如温度)double_col DOUBLE PRECISION, -- 8字节浮点数,约15位精度(比REAL更精确)serial_col SERIAL -- 自增整数(实际是INTEGER+序列的组合)
);
选择建议:
- 整数优先用
INTEGER
(平衡空间和范围),超大数用BIGINT
- 金额、数量等需要精确计算的用
DECIMAL
,避免浮点数误差(比如 0.1+0.2 在浮点数中可能不等于 0.3)
字符类型:存储文本的 “容器”
CREATE TABLE character_types (char_col CHAR(10), -- 固定长度,不足用空格填充(适合如身份证号等固定长度字符串)varchar_col VARCHAR(100), -- 可变长度,有最大限制(适合如姓名、地址)text_col TEXT -- 可变长度,无最大限制(适合长文本,如文章内容)
);
常见误区:有人认为VARCHAR(n)
比TEXT
性能好,其实在 PostgreSQL 中两者存储方式相同,n
只是约束,建议:
- 长度固定用
CHAR(n)
- 长度可变且有明确上限用
VARCHAR(n)
- 长文本或长度不确定用
TEXT
日期时间类型:时间的 “记录者”
CREATE TABLE datetime_types (date_col DATE, -- 日期(如'2023-10-01'),只存年月日time_col TIME, -- 时间(如'14:30:00'),只存时分秒timestamp_col TIMESTAMP, -- 日期+时间(如'2023-10-01 14:30:00')timestamptz_col TIMESTAMPTZ, -- 带时区的日期时间(推荐用,避免时区混乱)interval_col INTERVAL -- 时间间隔(如'3 days 2 hours',适合计算时间差)
);
最佳实践:
- 存储带时区的时间(如国际业务)用
TIMESTAMPTZ
,PostgreSQL 会自动转换为 UTC 存储 - 计算两个时间的差用
INTERVAL
,比如SELECT '2023-10-05'::DATE - '2023-10-01'::DATE;
会返回4 days
4.2 特殊数据类型:应对复杂场景的 “特种兵”
布尔类型:非此即彼的 “判断器”
CREATE TABLE boolean_demo (is_active BOOLEAN DEFAULT TRUE, -- 默认为TRUEhas_completed BOOLEAN -- 可取值TRUE、FALSE、NULL(NULL表示未知)
);-- 插入数据
INSERT INTO boolean_demo VALUES (TRUE, FALSE), (DEFAULT, NULL);
枚举类型:限定范围的 “选项框”
当字段值只能是几个固定选项时,用枚举类型(ENUM)比VARCHAR
更合适。
-- 定义枚举类型(心情:悲伤、一般、开心)
CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');-- 使用枚举类型
CREATE TABLE person (name TEXT,current_mood mood -- 只能取'sad'、'ok'、'happy'中的一个
);-- 插入合法值
INSERT INTO person VALUES ('张三', 'happy');-- 插入非法值会报错
INSERT INTO person VALUES ('李四', 'angry'); -- 错误:angry不在枚举列表中
数组类型:批量存储的 “集合袋”
PostgreSQL 支持一维和多维数组,适合存储同类的多个值(如标签、权限列表)。
CREATE TABLE array_demo (id SERIAL PRIMARY KEY,names TEXT[], -- 一维数组(如['张三', '李四'])matrix INTEGER[][], -- 二维数组(如[[1,2],[3,4]])schedule TEXT[][] -- 可用于存储周计划(如每周每天的安排)
);-- 插入数组数据
INSERT INTO array_demo VALUES (DEFAULT,ARRAY['Alice', 'Bob'], -- 方法1:用ARRAY关键字'{{1,2},{3,4}}', -- 方法2:用花括号'{"{"Mon","Tue"}", "{"Wed","Thu"}"}' -- 字符串形式(注意转义)
);-- 查询数组元素(取第一个名字)
SELECT names[1] FROM array_demo; -- PostgreSQL数组下标从1开始!-- 数组包含查询(查找包含'Alice'的记录)
SELECT * FROM array_demo WHERE 'Alice' = ANY(names);
JSON 类型:灵活存储的 “万能袋”
处理半结构化数据(如 API 返回、日志)时,JSON 类型非常方便。PostgreSQL 提供JSON
和JSONB
两种:
JSON
:存储原始字符串,查询时解析,适合写入多、查询少的场景JSONB
:存储二进制格式,支持索引,查询更快,适合查询多的场景(推荐用)
CREATE TABLE json_demo (id SERIAL PRIMARY KEY,data JSON, -- 文本JSONdata_b JSONB -- 二进制JSON
);-- 插入JSON数据
INSERT INTO json_demo VALUES (DEFAULT,'{"name": "张三", "age": 30, "hobbies": ["读书", "跑步"]}','{"name": "李四", "age": 25, "hobbies": ["游泳", "旅行"]}'
);-- 查询JSONB字段(->>返回文本,->返回JSON对象)
SELECT data_b->>'name' AS name, -- 取name字段的值(文本)(data_b->'age')::INT AS age -- 取age字段并转为整数
FROM json_demo;-- JSONB条件查询(查找年龄大于28的)
SELECT * FROM json_demo WHERE (data_b->>'age')::INT > 28;-- 给JSONB字段创建GIN索引(加速查询)
CREATE INDEX idx_json_data_b ON json_demo USING GIN (data_b);
范围类型:表示区间的 “尺子”
适合存储连续的区间(如时间范围、价格范围),还能方便地判断区间关系(重叠、包含等)。
CREATE TABLE range_demo (id SERIAL PRIMARY KEY,period DATERANGE, -- 日期范围(如[2023-01-01, 2023-12-31])salary_range NUMRANGE -- 数值范围(如(5000, 10000],圆括号表示不包含)
);-- 插入范围数据
INSERT INTO range_demo VALUES (DEFAULT,'[2023-01-01, 2023-12-31]'::DATERANGE, -- 全年'(5000, 10000]'::NUMRANGE -- 5000到10000,不包含5000,包含10000
);-- 范围重叠查询(查找与2023年第二季度重叠的period)
SELECT * FROM range_demo
WHERE period && '[2023-04-01, 2023-06-30]'::DATERANGE;-- 范围包含查询(查找包含6000的salary_range)
SELECT * FROM range_demo
WHERE salary_range @> 6000;
5. SQL 查询与数据操作:和数据 “互动” 的核心
学会了创建表和数据类型,接下来就是最核心的操作:增删改查(CRUD)。这部分是你和 PostgreSQL"对话" 的主要内容,咱们一步步来。
5.1 数据插入:给表 “添砖加瓦”
基本插入:单条数据
-- 插入指定字段(推荐,即使表结构变化也不容易出错)
INSERT INTO employees (first_name, last_name, email, salary, department_id)
VALUES ('John', 'Doe', 'john.doe@example.com', 50000.00, 1);
批量插入:一次多条数据
INSERT INTO employees (first_name, last_name, email, salary, department_id)
VALUES ('Jane', 'Smith', 'jane.smith@example.com', 60000.00, 2),('Bob', 'Johnson', 'bob.johnson@example.com', 55000.00, 1),('Alice', 'Williams', 'alice.williams@example.com', 70000.00, 3);
从查询结果插入:复制数据
-- 把工资高于65000的员工复制到managers表
INSERT INTO managers (first_name, last_name, email, salary)
SELECT first_name, last_name, email, salary
FROM employees
WHERE salary > 65000;
返回插入的数据:知道插了啥
用RETURNING
子句可以返回刚插入的记录(比如获取自增 ID):
INSERT INTO employees (first_name, last_name, email, salary, department_id)
VALUES ('Charlie', 'Brown', 'charlie.brown@example.com', 48000.00, 2)
RETURNING id, first_name, last_name; -- 返回插入的id和姓名
5.2 数据查询:从表中 “提取信息”
查询是 SQL 中最复杂也最强大的部分,咱们从简单到复杂逐步展开。
基本查询:查看数据
-- 查询所有字段(*表示所有,实际开发中尽量指定字段,避免不必要的开销)
SELECT * FROM employees;-- 查询指定字段
SELECT first_name, last_name, salary FROM employees;
条件过滤:找到你要的数据
用WHERE
子句设置条件:
-- 单条件:工资大于50000
SELECT first_name, last_name, salary
FROM employees
WHERE salary > 50000;-- 多条件:工资大于50000且部门ID为1(AND表示同时满足)
SELECT first_name, last_name, salary
FROM employees
WHERE salary > 50000 AND department_id = 1;-- 或条件:部门ID为1或2(OR表示满足其一)
SELECT first_name, last_name, department_id
FROM employees
WHERE department_id = 1 OR department_id = 2;-- 范围查询:工资在50000到70000之间(BETWEEN包含边界)
SELECT first_name, last_name, salary
FROM employees
WHERE salary BETWEEN 50000 AND 70000;-- 列表查询:部门ID在1、2、3中的(IN表示在列表内)
SELECT first_name, last_name, department_id
FROM employees
WHERE department_id IN (1, 2, 3);
排序:让结果更有序
用ORDER BY
排序,ASC
升序(默认),DESC
降序:
-- 按工资降序(从高到低),工资相同则按姓氏升序
SELECT first_name, last_name, salary
FROM employees
ORDER BY salary DESC, last_name ASC;
分页:大结果集 “分段看”
用LIMIT
限制返回行数,OFFSET
指定跳过行数(适合分页查询):
-- 取工资最高的10个人(跳过前0行,取10行)
SELECT first_name, last_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 10 OFFSET 0; -- 第一页-- 第二页(跳过前10行,取10行)
SELECT first_name, last_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 10 OFFSET 10;
去重:消除重复数据
用DISTINCT
去除重复记录:
-- 查看有员工的所有部门ID(去重)
SELECT DISTINCT department_id FROM employees;
聚合函数:统计数据
聚合函数用于对一组数据进行计算,常见的有COUNT
(计数)、AVG
(平均值)、MAX
(最大值)等:
-- 按部门统计:员工数量、平均工资、最高工资、最低工资
SELECT department_id,COUNT(*) AS employee_count, -- 总人数(*表示包括NULL)AVG(salary) AS average_salary, -- 平均工资MAX(salary) AS max_salary, -- 最高工资MIN(salary) AS min_salary -- 最低工资
FROM employees
GROUP BY department_id -- 按部门分组
HAVING AVG(salary) > 50000; -- 筛选平均工资大于50000的部门(HAVING用于分组后过滤)
注意:WHERE
用于分组前过滤,HAVING
用于分组后过滤。
条件判断:用 CASE 表达式
类似编程语言的if-else
,在查询中做条件判断:
SELECT first_name,last_name,salary,CASE WHEN salary < 50000 THEN 'Low' -- 工资<50000:低WHEN salary BETWEEN 50000 AND 80000 THEN 'Medium' -- 50000-80000:中ELSE 'High' -- 其他:高END AS salary_level -- 新增列:工资等级
FROM employees;
5.3 数据更新与删除:修改或清理数据
更新数据:修改已有记录
-- 基本更新:给2号部门员工涨5%工资
UPDATE employees
SET salary = salary * 1.05
WHERE department_id = 2;-- 基于其他表的更新:给工程部门员工涨10%工资
UPDATE employees
SET salary = employees.salary * 1.10
FROM departments -- 关联departments表
WHERE employees.department_id = departments.id
AND departments.name = 'Engineering'; -- 条件:部门名为'Engineering'
删除数据:移除不需要的记录
-- 基本删除:删除工资低于30000的员工
DELETE FROM employees
WHERE salary < 30000;-- 基于其他表的删除:删除属于"废弃部门"的员工
DELETE FROM employees
USING departments -- 关联departments表
WHERE employees.department_id = departments.id
AND departments.name = 'Obsolete Department'; -- 条件:部门名为'Obsolete Department'-- 清空表:删除所有行(比DELETE快,因为不写日志)
TRUNCATE TABLE audit_log; -- 无法回滚,谨慎使用!
重要提示:UPDATE
和DELETE
如果不带WHERE
子句,会修改 / 删除表中所有记录,一定要小心!建议先执行SELECT
确认条件正确后再执行。
6. 高级查询技术:挖掘数据的 “深层价值”
当数据分散在多个表中,或者需要复杂统计时,基础查询就不够用了。这部分咱们学习连接查询、子查询等高级技巧,让你能从数据中挖出更有价值的信息。
6.1 连接查询:多表数据 “强强联合”
现实中,数据通常分散在多个表中(比如员工表和部门表),连接查询(JOIN)能把这些表的数据 “拼” 在一起。
内连接(INNER JOIN):取两表的交集
只返回两个表中匹配条件的记录:
-- 查看员工及其所属部门名称(只显示有对应部门的员工)
SELECT e.first_name, e.last_name, d.name AS department_name
FROM employees e -- 别名e代表employees表
INNER JOIN departments d ON e.department_id = d.id; -- 连接条件:员工的department_id等于部门的id
左外连接(LEFT JOIN):保留左表所有记录
返回左表所有记录,以及右表中匹配的记录(右表无匹配则为 NULL):
-- 查看所有员工及其部门(包括没有部门的员工)
SELECT e.first_name, e.last_name, d.name AS department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
右外连接(RIGHT JOIN):保留右表所有记录
和左连接相反,返回右表所有记录,以及左表中匹配的记录:
-- 查看所有部门及其员工(包括没有员工的部门)
SELECT e.first_name, e.last_name, d.name AS department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.id;
全外连接(FULL JOIN):保留两表所有记录
返回左表和右表的所有记录,两边无匹配则为 NULL(PostgreSQL 支持,某些数据库如 MySQL 不支持):
-- 查看所有员工和所有部门,包括没有部门的员工和没有员工的部门
SELECT e.first_name, e.last_name, d.name AS department_name
FROM employees e
FULL JOIN departments d ON e.department_id = d.id;
自连接:表自己和自己连接
用于查询表中具有层级或关联关系的记录(如员工和其经理):
-- 查找同一部门的同事(排除自己)
SELECT e1.first_name AS emp_name, e2.first_name AS colleague_name,d.name AS department_name
FROM employees e1
INNER JOIN employees e2 ON e1.department_id = e2.department_id -- 同一部门
INNER JOIN departments d ON e1.department_id = d.id
WHERE e1.id <> e2.id; -- 排除自己
6.2 子查询:查询中的 “嵌套查询”
子查询就是嵌套在其他查询中的查询,相当于 “先查一个结果,再用这个结果查另一个”。
标量子查询:返回单个值
子查询返回一个值(一行一列),可用于SELECT
、WHERE
等子句:
-- 查看每个员工的工资和公司平均工资的对比
SELECT first_name, last_name, salary,(SELECT AVG(salary) FROM employees) AS average_salary -- 子查询:计算平均工资
FROM employees;
行子查询:返回一行多列
返回一行多个值,通常用于IN
或=
条件:
-- 查找每个部门工资最高的员工
SELECT first_name, last_name, department_id, salary
FROM employees
WHERE (department_id, salary) IN (SELECT department_id, MAX(salary) -- 子查询:每个部门的最高工资FROM employeesGROUP BY department_id
);
表子查询:返回多行多列
返回的结果像一个临时表,通常用于FROM
子句(需取别名):
-- 查看每个部门的名称和员工数量
SELECT dept.name, emp_count.count
FROM departments dept
JOIN (SELECT department_id, COUNT(*) AS count -- 子查询:每个部门的员工数FROM employeesGROUP BY department_id
) emp_count ON dept.id = emp_count.department_id; -- 关联部门表
相关子查询:依赖外部查询的子查询
子查询中用到了外部查询的字段,每一行都会执行一次子查询:
-- 查找工资高于本部门平均工资的员工
SELECT first_name, last_name, salary, department_id
FROM employees e1
WHERE salary > (SELECT AVG(salary) -- 子查询:当前员工所在部门的平均工资(依赖e1.department_id)FROM employees e2WHERE e2.department_id = e1.department_id
);
EXISTS 子查询:判断是否存在记录
用于判断子查询是否返回结果,返回TRUE
或FALSE
,效率通常比IN
高:
-- 查看有工资高于80000员工的部门
SELECT name
FROM departments d
WHERE EXISTS (SELECT 1 -- 这里1可以换成任意值, EXISTS只关心是否有记录FROM employees eWHERE e.department_id = d.id AND e.salary > 80000
);
6.3 公共表表达式 (CTE):复杂查询的 “模块化工具”
CTE 用WITH
子句定义一个临时结果集,让复杂查询更清晰(类似编程语言的函数)。
基本 CTE:将查询拆分成步骤
-- 步骤1:计算各地区销售额
-- 步骤2:找出销售额前10%的地区
-- 步骤3:查询这些地区的产品销售情况
WITH regional_sales AS (SELECT region, SUM(amount) AS total_salesFROM ordersGROUP BY region
), top_regions AS (SELECT regionFROM regional_salesWHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales) -- 前10%
)
SELECT region, product, SUM(quantity) AS product_units, SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;
递归 CTE:处理层次结构数据
递归 CTE 包含两部分:锚点成员(初始查询)和递归成员(引用自身的查询),适合处理树形结构(如组织架构、评论回复)。
-- 查询员工层级关系(顶级经理→部门经理→普通员工)
WITH RECURSIVE employee_hierarchy AS (-- 锚点成员:查找没有经理的顶级经理(manager_id为NULL)SELECT id, first_name, last_name, manager_id, 1 AS levelFROM employeesWHERE manager_id IS NULLUNION ALL -- 连接锚点和递归结果-- 递归成员:查找下属(员工的manager_id等于上级的id)SELECT e.id, e.first_name, e.last_name, e.manager_id, eh.level + 1FROM employees eINNER JOIN employee_hierarchy eh ON e.manager_id = eh.id
)
SELECT * FROM employee_hierarchy ORDER BY level, id; -- 按层级和ID排序
执行过程:
- 先执行锚点成员,得到顶级经理(level=1)
- 执行递归成员,用顶级经理 ID 找到其下属(level=2)
- 重复步骤 2,直到没有新的下属(递归终止)
- 合并所有结果
6.4 窗口函数:分组统计的 “高级玩法”
窗口函数能在不分组的情况下,对一组数据进行统计(比如 “每个员工的工资在部门内排第几”),结果会保留所有行。
排名函数:给数据 “排座次”
SELECT first_name, last_name, salary,department_id,-- 按部门分组,按工资降序排名(有相同值时排名相同,后续排名会跳号)RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dept_salary_rank,-- 密集排名(有相同值时排名相同,后续排名不跳号)DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dept_salary_dense_rank,-- 行号(即使值相同,行号也不同)ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dept_row_number
FROM employees;
举例:如果部门内有两个员工工资都是最高(10000),则:
RANK()
会给他们都排 1,下一个排 3DENSE_RANK()
会给他们都排 1,下一个排 2ROW_NUMBER()
会按顺序排 1 和 2(取决于 ORDER BY 的其他字段)
聚合窗口函数:每组的统计值
SELECT first_name, last_name, salary,department_id,-- 部门平均工资(每个员工都能看到自己部门的平均工资)AVG(salary) OVER (PARTITION BY department_id) AS dept_avg_salary,-- 部门总工资SUM(salary) OVER (PARTITION BY department_id) AS dept_total_salary,-- 与部门平均工资的差值salary - AVG(salary) OVER (PARTITION BY department_id) AS diff_from_avg
FROM employees;
窗口帧:定义统计的 “范围”
默认情况下,窗口函数统计整个分组,用ROWS
或RANGE
可以限定统计范围(如 “最近 3 条记录的平均值”)。
-- 计算按入职日期排序的移动平均工资(当前行和前2行的平均)
SELECT first_name, last_name, salary,hire_date,AVG(salary) OVER (ORDER BY hire_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW -- 范围:前2行到当前行) AS moving_avg
FROM employees;
常用窗口帧:
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
:从分组开始到当前行ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
:从当前行到分组结束ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
:前 1 行、当前行、后 1 行
7. 索引与性能优化:让查询 “跑” 得更快
如果把数据库比作图书馆,索引就像图书目录 —— 没有索引,找一本书需要翻遍整个图书馆;有了索引,能直接定位到书的位置。这部分咱们学习如何创建和使用索引,以及优化查询性能。
7.1 索引类型与创建:选择合适的 “目录”
PostgreSQL 提供多种索引类型,每种适合不同场景,选对索引能让查询速度提升几十甚至上百倍。
B-tree 索引:最常用的 “万能钥匙”
B-tree 是默认索引类型,适合大多数场景,尤其是:
- 等值查询(=、<>)
- 范围查询(>、<、>=、<=、BETWEEN)
- 排序(ORDER BY)
-- 给last_name创建B-tree索引(适合按姓氏查询或排序)
CREATE INDEX idx_employees_last_name ON employees (last_name);-- 复合索引(多列索引):适合同时按department_id和salary查询
-- 顺序很重要:选择性高的列放前面(选择性=不同值的数量/总记录数)
CREATE INDEX idx_employees_department_salary ON employees (department_id, salary DESC);
复合索引注意:查询条件中用到索引的前缀列时才能生效。比如(a,b,c)
索引,能优化a
、a AND b
、a AND b AND c
的查询,但不能优化b
、b AND c
的查询。
唯一索引:确保数据 “不重复”
唯一索引不仅能加速查询,还能保证字段值唯一(类似UNIQUE
约束,实际上UNIQUE
约束就是通过唯一索引实现的)。
-- 给email创建唯一索引(确保邮箱不重复)
CREATE UNIQUE INDEX idx_employees_email ON employees (email);
部分索引:只索引 “有用的数据”
只对表中满足条件的行创建索引,适合表中大部分数据不需要查询的场景(比如只查询 “高工资员工”)。
-- 只给工资>100000的员工创建索引(节省空间,提高查询效率)
CREATE INDEX idx_high_salary_employees ON employees (salary) WHERE salary > 100000;
表达式索引:对计算结果创建索引
当查询条件是字段的函数或表达式时,普通索引无效,需要用表达式索引。
-- 对first_name的小写形式创建索引(优化按小写姓名查询的场景)
CREATE INDEX idx_employees_lower_name ON employees (LOWER(first_name));-- 此时以下查询能用到索引
SELECT * FROM employees WHERE LOWER(first_name) = 'john';
哈希索引:等值查询的 “专项选手”
哈希索引只适合等值查询(=),不支持范围查询和排序,但等值查询性能可能比 B-tree 好。
CREATE INDEX idx_employees_id_hash ON employees USING HASH (id);
GiST 和 GIN 索引:特殊数据类型的 “专属工具”
- GiST:适合层次化数据,如地理坐标(PostGIS)、全文搜索、范围类型。
- GIN:适合多值类型,如数组、JSONB,查询速度快但索引体积大。
-- GiST索引:优化地理坐标查询(如两点距离)
CREATE INDEX idx_employees_location ON employees USING GIST (location);-- GIN索引:优化JSONB查询
CREATE INDEX idx_employees_data ON employees USING GIN (data_jsonb);-- GIN索引:优化数组包含查询
CREATE INDEX idx_employees_tags ON employees USING GIN (tags); -- tags是TEXT[]类型
BRIN 索引:超大表的 “轻量级索引”
BRIN(Block Range Index)适合超大表(如亿级行),且数据有物理顺序(如时间戳、自增 ID)。它不存储每行的索引值,而是存储每个数据块的范围,体积很小。
-- 给hire_date创建BRIN索引(适合按入职日期范围查询的大表)
CREATE INDEX idx_employees_hire_date_brin ON employees USING BRIN (hire_date);
7.2 查询性能分析:找到 “慢” 的原因
创建索引后,需要确认查询是否用到了索引,以及哪里耗时。EXPLAIN
命令是分析查询计划的利器。
使用 EXPLAIN 分析查询计划
-- 分析查询计划(不实际执行查询)
EXPLAIN SELECT * FROM employees WHERE last_name = 'Smith';
输出结果中,Index Scan using idx_employees_last_name on employees
表示用到了索引;如果是Seq Scan on employees
,表示全表扫描(没用到索引)。
使用 EXPLAIN ANALYZE 获取实际执行统计
-- 执行查询并显示实际耗时(会真正执行查询,注意大表!)
EXPLAIN ANALYZE
SELECT e.first_name, e.last_name, d.name
FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE e.salary > 70000;
关注Execution time
(总执行时间)和每个步骤的耗时,找出瓶颈。
查看索引使用情况
-- 查看索引的使用次数(idx_scan越大,索引越有用)
SELECT schemaname,relname AS table_name,indexrelname AS index_name,idx_scan AS index_scans, -- 索引被使用的次数idx_tup_read AS tuples_read, -- 通过索引读取的行数idx_tup_fetch AS tuples_fetched -- 通过索引获取的行数
FROM pg_stat_user_indexes
ORDER BY idx_scan DESC;
如果某个索引的idx_scan
为 0,说明它没用过,可以考虑删除(节省空间和写入开销)。
7.3 查询优化技巧:让查询 “提速” 的实用方法
1. 避免使用 SELECT *
SELECT *
会返回所有字段,包括不需要的,增加 IO 和网络开销:
-- 不好:返回所有字段
SELECT * FROM employees WHERE department_id = 5;-- 好:只返回需要的字段
SELECT id, first_name, last_name, email
FROM employees
WHERE department_id = 5;
2. 合理使用 LIMIT 限制结果
不需要全部结果时,用LIMIT
减少返回数据量:
-- 只取最近入职的10个员工
SELECT first_name, last_name
FROM employees
ORDER BY hire_date DESC
LIMIT 10;
3. 避免在 WHERE 子句中对列使用函数
对列使用函数会导致索引失效(除非用表达式索引):
-- 不好:对hire_date用了EXTRACT函数,无法使用索引
SELECT * FROM employees WHERE EXTRACT(YEAR FROM hire_date) = 2020;-- 好:直接比较日期,能使用索引
SELECT * FROM employees WHERE hire_date >= '2020-01-01' AND hire_date < '2021-01-01';
4. 批量操作代替循环
多次单条操作(如循环插入)比一次批量操作慢得多:
-- 不好:循环执行多次INSERT
-- FOR i IN 1..1000 LOOP
-- INSERT INTO logs VALUES (i, 'message');
-- END LOOP;-- 好:一次批量插入
INSERT INTO logs (id, message)
SELECT generate_series(1, 1000), 'message'; -- generate_series是PostgreSQL的生成序列函数
5. 用连接代替相关子查询
相关子查询每行执行一次,效率低;连接通常更高效:
-- 好:用连接
SELECT e.first_name, e.last_name, d.name
FROM employees e
JOIN departments d ON e.department_id = d.id;-- 不好:相关子查询(每行都会执行一次子查询)
SELECT first_name, last_name,(SELECT name FROM departments WHERE id = employees.department_id) AS dept_name
FROM employees;
8. 事务与并发控制:保证数据的 “准确性”
在多用户同时操作数据库时,如何保证数据正确(比如避免 “超卖”)?这就需要事务和并发控制机制。
8.1 事务管理:把操作 “打包”
事务是一组操作的集合,要么全部成功,要么全部失败(比如转账:扣钱和加钱必须同时成功或失败)。
基本事务
-- 开始事务
BEGIN;
-- 步骤1:从账户1扣100元
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 步骤2:给账户2加100元
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 提交事务(所有操作生效)
COMMIT;
事务回滚:出错时 “撤销” 操作
BEGIN;
-- 删除所有状态为"已取消"的订单
DELETE FROM orders WHERE status = 'cancelled';
-- 发现误删(比如应该删'shipped'),回滚事务
ROLLBACK; -- 所有操作被撤销,数据回到事务开始前的状态
保存点:事务中的 “checkpoint”
当事务很长时,可以设置保存点,回滚到保存点而不是整个事务:
BEGIN;
-- 步骤1:扣钱
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 设置保存点
SAVEPOINT point1;
-- 步骤2:尝试给账户2加钱(假设这步可能失败)
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 如果步骤2失败,回滚到保存点
ROLLBACK TO SAVEPOINT point1;
-- 换一个账户加钱
UPDATE accounts SET balance = balance + 100 WHERE id = 3;
-- 提交事务
COMMIT;
事务隔离级别:控制并发时的可见性
不同隔离级别决定了一个事务能看到其他事务的哪些修改,PostgreSQL 支持 4 种:
- 读未提交(READ UNCOMMITTED):能看到未提交的修改(可能出现 “脏读”)
- 读已提交(READ COMMITTED):只能看到已提交的修改(默认级别,避免脏读)
- 可重复读(REPEATABLE READ):事务中多次查询结果一致(避免 “不可重复读”)
- 可序列化(SERIALIZABLE):最高级别,完全隔离(避免 “幻读”,但性能开销大)
-- 启动一个可重复读隔离级别的事务
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 事务中的查询结果始终一致,不受其他事务影响
SELECT * FROM accounts WHERE id = 1;
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
COMMIT;
选择建议:大部分场景用默认的READ COMMITTED
;需要严格一致性时用REPEATABLE READ
;极少数极端场景用SERIALIZABLE
。
8.2 锁机制:并发时的 “交通规则”
锁用于防止多个事务同时修改同一数据导致的冲突,PostgreSQL 有多种锁类型。
行级锁:只锁需要修改的行
行级锁粒度细,并发高,常用的有:
FOR UPDATE
:排他锁,其他事务不能修改或加排他锁FOR SHARE
:共享锁,其他事务可以加共享锁但不能加排他锁
-- 开启事务,给id=1的账户加排他锁
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- 修改数据(此时其他事务想改这条行会等待)
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT; -- 提交后释放锁
表级锁:锁定整个表
表级锁粒度粗,会影响并发,通常用于表结构修改:
-- 给accounts表加ACCESS EXCLUSIVE锁(最高级表锁,阻止所有操作)
LOCK TABLE accounts IN ACCESS EXCLUSIVE MODE;
-- 此时可以安全地修改表结构
ALTER TABLE accounts ADD COLUMN status VARCHAR(10);
查看锁信息:诊断锁冲突
-- 查看表的锁信息(以accounts表为例)
SELECT locktype, -- 锁类型(行、表等)relation::regclass, -- 表名mode, -- 锁模式granted -- 是否已授予(false表示等待)
FROM pg_locks
WHERE relation = 'accounts'::regclass;
如果有granted = false
的记录,说明有事务在等待锁,可能存在锁竞争。
避免死锁:死锁是两个事务互相等待对方释放锁导致的,解决方法:
- 所有事务按相同顺序访问表(比如先操作账户 A,再操作账户 B)
- 尽量缩短事务时间,减少锁持有时间
- 用
FOR UPDATE SKIP LOCKED
跳过被锁的行(PostgreSQL 9.5 + 支持)
9. 高级特性:PostgreSQL 的 “隐藏大招”
PostgreSQL 不仅能做基础的增删改查,还有很多高级特性,让你能实现更复杂的业务逻辑。
9.1 存储过程与函数:数据库中的 “小程序”
函数(Function)和存储过程(Procedure)能把一组 SQL 语句封装起来,像调用函数一样执行,提高代码复用率。
创建和调用函数
PostgreSQL 支持多种语言编写函数(如 PL/pgSQL、Python、JavaScript),最常用的是 PL/pgSQL。
-- 创建函数:计算部门员工数量
CREATE OR REPLACE FUNCTION get_employee_count(dept_id INTEGER)
RETURNS INTEGER AS $$ -- 返回整数
DECLAREemployee_count INTEGER; -- 声明变量
BEGIN-- 查询部门员工数,存入变量SELECT COUNT(*) INTO employee_countFROM employeesWHERE department_id = dept_id;RETURN employee_count; -- 返回结果
END;
$$ LANGUAGE plpgsql; -- 使用PL/pgSQL语言-- 调用函数
SELECT get_employee_count(1); -- 查看1号部门的员工数
带副作用的函数(修改数据)
-- 创建函数:给员工涨工资
CREATE OR REPLACE FUNCTION update_salary(emp_id INTEGER, percent_increase NUMERIC)
RETURNS VOID AS $$ -- 无返回值
BEGINUPDATE employeesSET salary = salary * (1 + percent_increase / 100)WHERE id = emp_id;-- 如果没找到员工,抛出异常IF NOT FOUND THENRAISE EXCEPTION 'Employee with id % not found', emp_id;END IF;
END;
$$ LANGUAGE plpgsql;-- 调用函数(需要用SELECT或PERFORM)
SELECT update_salary(1, 5); -- 给1号员工涨5%工资
返回表的函数
函数可以返回多行多列,像表一样使用:
-- 创建函数:查询部门员工信息
CREATE OR REPLACE FUNCTION get_employees_by_dept(dept_id INTEGER)
RETURNS TABLE ( -- 定义返回的表结构emp_id INTEGER,first_name VARCHAR,last_name VARCHAR,salary NUMERIC
) AS $$
BEGIN-- 返回查询结果RETURN QUERYSELECT id, first_name, last_name, salaryFROM employeesWHERE department_id = dept_id;
END;
$$ LANGUAGE plpgsql;-- 调用表函数(像查询表一样)
SELECT * FROM get_employees_by_dept(1);
9.2 触发器:自动执行的 “钩子”
触发器是绑定在表上的 “自动程序”,当表发生 INSERT/UPDATE/DELETE 时,会自动执行指定的函数(比如记录日志、数据校验)。
创建触发器示例:记录员工数据变更日志
-- 1. 创建审计日志表(存储变更记录)
CREATE TABLE employee_audit (id SERIAL PRIMARY KEY,employee_id INTEGER NOT NULL, -- 被修改的员工IDchanged_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 变更时间old_data JSONB, -- 旧数据(JSON格式)new_data JSONB, -- 新数据(JSON格式)operation VARCHAR(10) NOT NULL -- 操作类型:INSERT/UPDATE/DELETE
);-- 2. 创建触发器函数(定义触发时执行的逻辑)
CREATE OR REPLACE FUNCTION log_employee_changes()
RETURNS TRIGGER AS $$
BEGINIF TG_OP = 'INSERT' THEN -- 插入操作INSERT INTO employee_audit (employee_id, new_data, operation)VALUES (NEW.id, row_to_json(NEW), 'INSERT'); -- NEW表示新记录RETURN NEW;ELSIF TG_OP = 'UPDATE' THEN -- 更新操作INSERT INTO employee_audit (employee_id, old_data, new_data, operation)VALUES (NEW.id, row_to_json(OLD), row_to_json(NEW), 'UPDATE'); -- OLD表示旧记录RETURN NEW;ELSIF TG_OP = 'DELETE' THEN -- 删除操作INSERT INTO employee_audit (employee_id, old_data, operation)VALUES (OLD.id, row_to_json(OLD), 'DELETE');RETURN OLD;END IF;RETURN NULL;
END;
$$ LANGUAGE plpgsql;-- 3. 创建触发器(绑定表和函数)
CREATE TRIGGER trg_employee_auditAFTER INSERT OR UPDATE OR DELETE ON employees -- 触发时机和事件FOR EACH ROW -- 每行触发一次EXECUTE FUNCTION log_employee_changes(); -- 执行的函数
效果:当对employees
表执行插入、更新、删除时,employee_audit
会自动记录变更,方便追溯数据历史。
9.3 全文搜索:让数据库 “读懂” 文本
PostgreSQL 内置全文搜索功能,不需要额外依赖 Elasticsearch 就能实现关键词搜索(适合中小规模文本搜索)。
基本使用步骤
- 创建支持全文搜索的表:
CREATE TABLE documents (id SERIAL PRIMARY KEY,title TEXT NOT NULL, -- 标题content TEXT NOT NULL, -- 内容tsv TSVECTOR -- 全文搜索向量(存储预处理后的文本)
);
- 创建触发器自动更新 TSVECTOR:
-- 函数:更新tsv字段(将标题和内容转换为TSVECTOR)
CREATE OR REPLACE FUNCTION documents_tsvector_trigger()
RETURNS TRIGGER AS $$
BEGIN-- to_tsvector:将文本转换为搜索向量('english'是词典)NEW.tsv = to_tsvector('english', COALESCE(NEW.title, '') || ' ' || COALESCE(NEW.content, ''));RETURN NEW;
END;
$$ LANGUAGE plpgsql;-- 触发器:插入或更新时自动更新tsv
CREATE TRIGGER trg_documents_tsvectorBEFORE INSERT OR UPDATE ON documentsFOR EACH ROW EXECUTE FUNCTION documents_tsvector_trigger();
- 创建 GIN 索引加速搜索:
CREATE INDEX idx_documents_tsv ON documents USING GIN (tsv);
- 执行全文搜索:
-- 插入示例数据
INSERT INTO documents (title, content) VALUES
('PostgreSQL Guide', 'PostgreSQL is a powerful open source relational database system.'),
('SQL Basics', 'SQL is a standard language for storing, manipulating and retrieving data in databases.');-- 搜索包含'powerful'和'open'的文档(&&表示且)
SELECT title, content
FROM documents
WHERE tsv @@ to_tsquery('english', 'powerful & open');-- 搜索包含'database'的文档,并按相关性排序
SELECT title, content, ts_rank(tsv, to_tsquery('english', 'database')) AS rank
FROM documents
WHERE tsv @@ to_tsquery('english', 'database')
ORDER BY rank DESC;
全文搜索关键词:
&
:且(同时包含)|
:或(包含任意一个)!
:非(不包含):*
:前缀匹配(如power:*
匹配 powerful、powered 等)
10. 安全与权限管理:守护数据的 “安全门”
数据库存储着重要数据,必须做好安全防护,控制谁能访问、能做什么操作。
10.1 用户与角色管理:给用户 “分配权限”
PostgreSQL 中,用户(User)和角色(Role)几乎是一样的,角色可以被赋予权限,也可以包含其他角色(类似 “用户组”)。
创建角色和用户
-- 创建一个只读角色(不能登录,只用于分配权限)
CREATE ROLE read_only;-- 创建一个可以登录的用户(WITH LOGIN)
CREATE ROLE app_user WITH LOGIN PASSWORD 'securepassword';
授予权限
权限可以细化到数据库、模式、表、列等级别,遵循 “最小权限原则”(只给必要的权限)。
-- 允许read_only角色连接mydb数据库
GRANT CONNECT ON DATABASE mydb TO read_only;-- 允许read_only角色使用public模式
GRANT USAGE ON SCHEMA public TO read_only;-- 允许read_only角色查询public模式下的所有表
GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only;-- 给新表自动赋予权限(否则新增的表read_only无法查询)
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO read_only;
角色成员关系
可以把角色赋给用户,让用户继承角色的权限:
-- 让app_user继承read_only角色的权限
GRANT read_only TO app_user;
查看角色和权限
-- 查看所有角色
SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin
FROM pg_roles;-- 查看表的权限
SELECT * FROM information_schema.table_privileges
WHERE grantee = 'read_only';
10.2 行级安全策略:数据访问的 “精细化控制”
行级安全(RLS)允许控制用户只能访问表中特定行的数据(比如员工只能看自己部门的数据)。
启用行级安全并创建策略
-- 1. 对employees表启用行级安全
ALTER TABLE employees ENABLE ROW LEVEL SECURITY;-- 2. 创建策略:用户只能查看自己部门的员工
CREATE POLICY emp_department_policy ON employeesFOR SELECT -- 对查询操作生效USING (department_id = current_setting('app.current_department_id')::integer);
-- 说明:current_setting('app.current_department_id')获取会话参数,需提前设置-- 3. 创建策略:经理可以管理自己部门的员工(增删改查)
CREATE POLICY emp_manager_policy ON employeesUSING (department_id IN (SELECT department_id FROM managers WHERE user_id = current_user)); -- 只允许经理操作自己部门的员工
使用行级安全
-- 设置当前会话的部门ID(比如设置为3)
SET app.current_department_id = '3';-- 查询时,只会返回department_id=3的员工
SELECT * FROM employees;
行级安全适合多租户系统(每个租户只能看自己的数据)、分部门的数据隔离等场景。
11. 备份与恢复:给数据 “买保险”
数据是企业的命脉,必须定期备份,以防意外(如误删、硬件故障)。PostgreSQL 提供多种备份方式,适合不同场景。
11.1 逻辑备份与恢复:灵活的 “数据快照”
逻辑备份是导出 SQL 语句或数据,优点是灵活(可恢复单个表),缺点是速度慢(适合中小数据库)。
使用 pg_dump 备份
# 备份单个数据库(包含表结构和数据)
pg_dump -U username -d dbname -f backup.sql# 备份所有数据库(包括角色、表空间等)
pg_dumpall -U postgres -f alldb.sql# 仅备份数据(不包含表结构)
pg_dump -U username -d dbname -a -f data_only.sql# 仅备份表结构(不包含数据)
pg_dump -U username -d dbname -s -f schema_only.sql# 自定义格式备份(压缩,支持并行恢复,推荐)
pg_dump -U username -d dbname -F c -f backup.custom
恢复数据库
# 恢复SQL格式备份
psql -U username -d dbname -f backup.sql# 恢复自定义格式备份
pg_restore -U username -d dbname backup.custom# 并行恢复(用4个进程,速度更快)
pg_restore -U username -d dbname -j 4 backup.custom
11.2 物理备份与时间点恢复 (PITR):应对 “重大事故”
物理备份是复制数据库的物理文件,速度快(适合大数据库),结合 WAL 日志可以恢复到任意时间点。
配置归档模式(前提)
# 修改postgresql.conf
wal_level = replica # 至少为replica
archive_mode = on
# 归档命令:将WAL日志复制到归档目录(确保目录存在)
archive_command = 'test ! -f /var/lib/pgsql/archive/%f && cp %p /var/lib/pgsql/archive/%f'# 重启PostgreSQL使配置生效
sudo systemctl restart postgresql
创建基础备份
# 创建基础备份(-D指定备份目录,-U指定用户,-P显示进度,-F p表示纯文本格式,-R包含复制信息)
pg_basebackup -D /var/lib/pgsql/backup -U replicator -P -F p -R
时间点恢复(PITR)
假设数据库在 2023-01-01 12:00:00 出了问题,需要恢复到这个时间点前:
# 1. 停止PostgreSQL服务
sudo systemctl stop postgresql# 2. 清空数据目录(确保备份有效!)
rm -rf /var/lib/pgsql/data/*# 3. 复制基础备份到数据目录
cp -r /var/lib/pgsql/backup/* /var/lib/pgsql/data/# 4. 创建恢复配置文件(recovery.conf)
echo "restore_command = 'cp /var/lib/pgsql/archive/%f %p'" > /var/lib/pgsql/data/recovery.conf
# 指定恢复到2023-01-01 12:00:00
echo "recovery_target_time = '2023-01-01 12:00:00'" >> /var/lib/pgsql/data/recovery.conf# 5. 启动PostgreSQL,开始恢复
sudo systemctl start postgresql
恢复完成后,recovery.conf
会自动重命名为recovery.done
,表示恢复结束。
12. 高可用与复制:让数据库 “不宕机”
业务不能容忍数据库长时间宕机,通过复制和高可用方案,可以实现故障时快速切换,减少停机时间。
12.1 流复制设置:主从架构
流复制(Streaming Replication)是 PostgreSQL 最常用的复制方式,主库(Primary)写入数据,从库(Standby)实时同步,主库故障时从库可以接管。
主库配置
- 修改
postgresql.conf
:
wal_level = replica # 生成足够的WAL日志供复制
max_wal_senders = 10 # 最大WAL发送进程数(从库连接数)
wal_keep_size = 1024 # 保留的WAL日志大小(MB),防止从库落后太多
- 修改
pg_hba.conf
,允许从库连接:
# 允许192.168.1.100(从库IP)的replicator用户通过密码连接复制
host replication replicator 192.168.1.100/32 md5
- 创建复制用户:
CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'replicationpass';
- 重启主库生效:
sudo systemctl restart postgresql
从库配置
- 清空从库的数据目录(首次配置时):
sudo systemctl stop postgresql
rm -rf /var/lib/pgsql/data/*
- 从主库获取基础备份(自动配置复制):
pg_basebackup -h primary_host -U replicator -D /var/lib/pgsql/data -P -R
# -h:主库IP,-U:复制用户,-D:从库数据目录,-P:显示进度,-R:自动生成复制配置
- 启动从库:
sudo systemctl start postgresql
从库会自动连接主库,同步数据。默认情况下,从库是只读的(hot_standby = on
),可以分担查询压力。
12.2 监控复制状态
在主库上查看复制状态
SELECT application_name, -- 从库名称client_addr, -- 从库IPstate, -- 状态(streaming表示正常同步)sent_lsn, -- 已发送的WAL位置write_lsn, -- 从库已写入的WAL位置flush_lsn, -- 从库已刷新到磁盘的WAL位置replay_lsn, -- 从库已应用的WAL位置write_lag, -- 写入延迟flush_lag, -- 刷新延迟replay_lag -- 应用延迟
FROM pg_stat_replication;
在从库上查看复制状态
SELECT pg_is_in_recovery() AS is_standby, -- 是否为从库(true表示是)pg_last_wal_receive_lsn() AS received_lsn, -- 已接收的WAL位置pg_last_wal_replay_lsn() AS replayed_lsn, -- 已应用的WAL位置pg_last_xact_replay_timestamp() AS replay_timestamp; -- 最后应用事务的时间
如果received_lsn
和replayed_lsn
差距大,说明从库同步延迟,可能需要优化网络或从库性能。
13. 扩展与插件:给 PostgreSQL"装插件"
PostgreSQL 的扩展性极强,通过扩展可以添加各种功能(如加密、地理信息、性能监控等)。
13.1 常用扩展介绍
查看可用扩展
-- 查看所有可用扩展
SELECT * FROM pg_available_extensions;
安装和使用扩展
-- 安装PostGIS(地理信息扩展,处理地图数据)
CREATE EXTENSION IF NOT EXISTS postgis;-- 安装pgcrypto(加密扩展,支持数据加密)
CREATE EXTENSION IF NOT EXISTS pgcrypto;-- 安装hstore(键值对扩展,存储灵活的键值数据)
CREATE EXTENSION IF NOT EXISTS hstore;-- 查看已安装的扩展
SELECT * FROM pg_extension;
实用扩展示例
- pgcrypto:加密数据
-- 加密密码(bf算法,8轮迭代)
SELECT crypt('mypassword', gen_salt('bf', 8));
-- 结果类似:$2a$08$VhVQrB5lLlkD1eX5QK5i5O5QK5i5O5QK5i5O5QK5i5O5QK5i5O-- 验证密码(比较加密后的结果)
SELECT crypt('mypassword', '$2a$08$VhVQrB5lLlkD1eX5QK5i5O') = '$2a$08$VhVQrB5lLlkD1eX5QK5i5O5QK5i5O5QK5i5O5QK5i5O5QK5i5O';
-- 返回true表示密码正确
- hstore:存储键值对
-- 创建带hstore字段的表
CREATE TABLE products (id SERIAL PRIMARY KEY,name TEXT NOT NULL,attributes HSTORE -- 存储产品属性(如颜色、内存等)
);-- 插入数据
INSERT INTO products (name, attributes) VALUES ('Laptop','color=>red, memory=>16GB, storage=>512GB'::hstore
);-- 查询属性(->操作符取单个值)
SELECT name, attributes->'color' AS color
FROM products;-- 条件查询(存在某个键)
SELECT * FROM products WHERE attributes ? 'memory'; -- ?表示是否包含键-- 更新属性
UPDATE products
SET attributes = attributes || 'price=>5999'::hstore -- ||合并hstore
WHERE id = 1;
14. 监控与维护:让数据库 “健康运行”
数据库需要定期监控和维护,才能保持良好性能,避免故障。
14.1 数据库监控:了解数据库 “状态”
查看数据库和表大小
-- 查看所有数据库大小
SELECT datname AS database,pg_size_pretty(pg_database_size(datname)) AS size -- pg_size_pretty:格式化大小
FROM pg_database
ORDER BY pg_database_size(datname) DESC;-- 查看表大小(包括索引等)
SELECT schemaname,relname AS table_name,pg_size_pretty(pg_total_relation_size(relid)) AS total_size, -- 总大小(数据+索引)pg_size_pretty(pg_relation_size(relid)) AS data_size, -- 数据大小pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) AS external_size -- 索引等其他大小
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;
查看连接和活动查询
-- 查看所有活动连接
SELECT datname AS database,usename AS user,application_name, -- 应用名称client_addr AS client_ip, -- 客户端IPstate, -- 状态(active表示正在执行查询)query_start AS start_time, -- 查询开始时间query -- 正在执行的SQL
FROM pg_stat_activity
WHERE state = 'active'; -- 只看活跃的
如果有长时间运行的查询(query_start
很早),可能需要终止:
-- 终止进程(pid是查询结果中的pid字段)
SELECT pg_terminate_backend(pid);
查看锁冲突
-- 查看阻塞和被阻塞的查询
SELECT blocked_locks.pid AS blocked_pid,blocked_activity.usename AS blocked_user,blocking_locks.pid AS blocking_pid,blocking_activity.usename AS blocking_user,blocked_activity.query AS blocked_statement, -- 被阻塞的SQLblocking_activity.query AS blocking_statement -- 造成阻塞的SQL
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktypeAND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.databaseAND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted; -- 未授予的锁(等待中)
14.2 维护任务:保持数据库 “活力”
VACUUM:清理无用数据
PostgreSQL 的 MVCC 机制会产生 “死元组”(被删除或更新的旧版本数据),需要定期清理:
-- 手动执行VACUUM(普通清理,不阻塞查询)
VACUUM employees;-- VACUUM ANALYZE(清理+更新统计信息,推荐)
VACUUM ANALYZE employees;-- 查看需要清理的表(死元组比例高的表)
SELECT schemaname,relname AS table_name,n_live_tup AS live_tuples, -- 活元组数量n_dead_tup AS dead_tuples, -- 死元组数量round(n_dead_tup::numeric / (n_live_tup + n_dead_tup), 2) AS dead_ratio -- 死元组比例
FROM pg_stat_user_tables
WHERE n_dead_tup > 0
ORDER BY dead_ratio DESC;
PostgreSQL 默认启用autovacuum
进程,会自动清理死元组,但对于频繁更新的表,可能需要手动执行。
重建索引:修复索引碎片
索引使用久了会产生碎片,影响性能,需要重建:
-- 重建单个索引
REINDEX INDEX idx_employees_last_name;-- 重建整个表的索引
REINDEX TABLE employees;-- 并发重建(不阻塞查询,PostgreSQL 12+支持)
REINDEX CONCURRENTLY INDEX idx_employees_last_name;
更新统计信息:帮助优化器生成更好的计划
查询优化器依赖表的统计信息生成执行计划,统计信息过时会导致计划不佳:
-- 更新单个表的统计信息
ANALYZE employees;-- 更新所有表的统计信息
ANALYZE;
VACUUM ANALYZE
会同时清理死元组和更新统计信息,建议定期执行。
15. 最佳实践与总结:PostgreSQL 的 “使用说明书”
经过前面的学习,你已经掌握了 PostgreSQL 的核心知识,最后咱们总结一些最佳实践,帮助你更好地使用它。
15.1 设计规范:打好 “地基”
- 数据类型选择:按实际需求选最合适的类型(如金额用
DECIMAL
,手机号用BIGINT
或TEXT
),避免过度使用VARCHAR(255)
。 - 约束设计:合理使用主键、外键、唯一约束、CHECK 约束,保证数据完整性(比如
CHECK (salary > 0)
防止工资为负)。 - 规范化与反规范化:优先规范化(减少冗余),但对查询频繁的表可适度反规范化(如冗余部门名称,避免关联查询)。
- 命名约定:表名、字段名使用小写字母 + 下划线(如
employees
、first_name
),避免关键字(如user
、order
)。
15.2 性能优化:让数据库 “跑” 得快
- 索引策略:
- 给 WHERE、JOIN、ORDER BY 涉及的字段创建索引
- 避免过度索引(写入时会变慢)
- 复合索引注意字段顺序(选择性高的放前面)
- 查询优化:
- 避免
SELECT *
,只查需要的字段 - 大表查询用 LIMIT 分页
- 复杂查询用 EXPLAIN 分析,避免全表扫描
- 避免
- 连接管理:使用连接池(如 pgBouncer),避免频繁创建和关闭连接(PostgreSQL 连接开销大)。
- 内存配置:根据服务器内存调整
shared_buffers
(建议物理内存的 1/4)、work_mem
(每个排序 / 哈希操作的内存)。
15.3 安全实践:守护数据 “安全”
- 最小权限:用户只授予必要的权限(如应用用户只给 DML 权限,不给 DDL 权限)。
- 密码策略:使用强密码,定期更换,避免明文存储(用 pgcrypto 加密)。
- 网络安全:限制数据库端口(5432)只对信任的 IP 开放,使用 SSL 加密连接(修改
ssl = on
)。 - 定期备份:结合逻辑备份和物理备份,定期测试恢复流程(备份没用上等于没备份)。
15.4 高可用性:保证业务 “不中断”
- 复制方案:主从流复制,主库故障时从库接管(可配合 Patroni、Repmgr 等工具实现自动故障转移)。
- 监控告警:监控数据库状态(连接数、锁、空间、复制延迟),设置告警阈值(如连接数超过 80% 告警)。
- 灾难恢复:制定 RPO(恢复点目标)和 RTO(恢复时间目标),确保数据丢失控制在可接受范围,恢复时间足够快。
参考文献
- PostgreSQL 官方文档. (2023). PostgreSQL 15.2 Documentation. https://www.postgresql.org/docs/15/index.html
- Krosing, H., & Riggs, S. (2021). PostgreSQL 14 High Performance. Packt Publishing.
- Ramadan, E. (2020). Mastering PostgreSQL 13. Packt Publishing.
- 唐成. (2019). PostgreSQL 实战。机械工业出版社.
- 彭智勇,彭煜玮. (2018). PostgreSQL 数据库内核分析。电子工业出版社.
希望这篇万字长文能带你全面认识 PostgreSQL 这位 “老伙计”。它的功能远不止于此,更多的特性和技巧需要在实际使用中探索。记住,最好的学习方式是动手实践 —— 安装它,创建表,写查询,遇到问题查文档,你会逐渐发现它的强大和灵活。祝你在 PostgreSQL 的世界里玩得开心!