当前位置: 首页 > news >正文

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 系统:图形化向导更友好
  1. 从官网下载对应版本:https://www.postgresql.org/download/windows/(建议选最新稳定版)
  2. 运行安装程序,注意这几个关键点:
    • 安装目录:避免有空格或中文(比如C:\PostgreSQL就比C:\Program Files\PostgreSQL好)
    • 端口:默认 5432,若被占用可修改(记住你改的端口,后续连接要用)
    • 超级用户密码:一定要记牢!这是postgres用户的密码
  3. 安装完成后,会自动启动服务,还会安装 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
  • 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 提供JSONJSONB两种:

  • 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;  -- 无法回滚,谨慎使用!

重要提示UPDATEDELETE如果不带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 子查询:查询中的 “嵌套查询”

子查询就是嵌套在其他查询中的查询,相当于 “先查一个结果,再用这个结果查另一个”。

标量子查询:返回单个值

子查询返回一个值(一行一列),可用于SELECTWHERE等子句:

-- 查看每个员工的工资和公司平均工资的对比
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 子查询:判断是否存在记录

用于判断子查询是否返回结果,返回TRUEFALSE,效率通常比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排序

执行过程

  1. 先执行锚点成员,得到顶级经理(level=1)
  2. 执行递归成员,用顶级经理 ID 找到其下属(level=2)
  3. 重复步骤 2,直到没有新的下属(递归终止)
  4. 合并所有结果

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,下一个排 3
  • DENSE_RANK()会给他们都排 1,下一个排 2
  • ROW_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;
窗口帧:定义统计的 “范围”

默认情况下,窗口函数统计整个分组,用ROWSRANGE可以限定统计范围(如 “最近 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)索引,能优化aa AND ba AND b AND c的查询,但不能优化bb 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的记录,说明有事务在等待锁,可能存在锁竞争。

避免死锁:死锁是两个事务互相等待对方释放锁导致的,解决方法:

  1. 所有事务按相同顺序访问表(比如先操作账户 A,再操作账户 B)
  2. 尽量缩短事务时间,减少锁持有时间
  3. 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 就能实现关键词搜索(适合中小规模文本搜索)。

基本使用步骤
  1. 创建支持全文搜索的表
CREATE TABLE documents (id SERIAL PRIMARY KEY,title TEXT NOT NULL,  -- 标题content TEXT NOT NULL,  -- 内容tsv TSVECTOR  -- 全文搜索向量(存储预处理后的文本)
);
  1. 创建触发器自动更新 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();
  1. 创建 GIN 索引加速搜索
CREATE INDEX idx_documents_tsv ON documents USING GIN (tsv);
  1. 执行全文搜索
-- 插入示例数据
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)实时同步,主库故障时从库可以接管。

主库配置
  1. 修改postgresql.conf
wal_level = replica  # 生成足够的WAL日志供复制
max_wal_senders = 10  # 最大WAL发送进程数(从库连接数)
wal_keep_size = 1024  # 保留的WAL日志大小(MB),防止从库落后太多
  1. 修改pg_hba.conf,允许从库连接:
# 允许192.168.1.100(从库IP)的replicator用户通过密码连接复制
host    replication     replicator      192.168.1.100/32       md5
  1. 创建复制用户:
CREATE USER replicator WITH REPLICATION ENCRYPTED PASSWORD 'replicationpass';
  1. 重启主库生效:
sudo systemctl restart postgresql
从库配置
  1. 清空从库的数据目录(首次配置时):
sudo systemctl stop postgresql
rm -rf /var/lib/pgsql/data/*
  1. 从主库获取基础备份(自动配置复制):
pg_basebackup -h primary_host -U replicator -D /var/lib/pgsql/data -P -R
# -h:主库IP,-U:复制用户,-D:从库数据目录,-P:显示进度,-R:自动生成复制配置
  1. 启动从库:
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_lsnreplayed_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;
实用扩展示例
  1. pgcrypto:加密数据
-- 加密密码(bf算法,8轮迭代)
SELECT crypt('mypassword', gen_salt('bf', 8));
-- 结果类似:$2a$08$VhVQrB5lLlkD1eX5QK5i5O5QK5i5O5QK5i5O5QK5i5O5QK5i5O-- 验证密码(比较加密后的结果)
SELECT crypt('mypassword', '$2a$08$VhVQrB5lLlkD1eX5QK5i5O') = '$2a$08$VhVQrB5lLlkD1eX5QK5i5O5QK5i5O5QK5i5O5QK5i5O5QK5i5O';
-- 返回true表示密码正确
  1. 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,手机号用BIGINTTEXT),避免过度使用VARCHAR(255)
  • 约束设计:合理使用主键、外键、唯一约束、CHECK 约束,保证数据完整性(比如CHECK (salary > 0)防止工资为负)。
  • 规范化与反规范化:优先规范化(减少冗余),但对查询频繁的表可适度反规范化(如冗余部门名称,避免关联查询)。
  • 命名约定:表名、字段名使用小写字母 + 下划线(如employeesfirst_name),避免关键字(如userorder)。

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(恢复时间目标),确保数据丢失控制在可接受范围,恢复时间足够快。

参考文献

  1. PostgreSQL 官方文档. (2023). PostgreSQL 15.2 Documentation. https://www.postgresql.org/docs/15/index.html
  2. Krosing, H., & Riggs, S. (2021). PostgreSQL 14 High Performance. Packt Publishing.
  3. Ramadan, E. (2020). Mastering PostgreSQL 13. Packt Publishing.
  4. 唐成. (2019). PostgreSQL 实战。机械工业出版社.
  5. 彭智勇,彭煜玮. (2018). PostgreSQL 数据库内核分析。电子工业出版社.

希望这篇万字长文能带你全面认识 PostgreSQL 这位 “老伙计”。它的功能远不止于此,更多的特性和技巧需要在实际使用中探索。记住,最好的学习方式是动手实践 —— 安装它,创建表,写查询,遇到问题查文档,你会逐渐发现它的强大和灵活。祝你在 PostgreSQL 的世界里玩得开心!

http://www.dtcms.com/a/362165.html

相关文章:

  • 时序数据库国产的有哪些?
  • 利用棒棒糖图探索Office (US)的IMDB评分
  • 毕业项目推荐:64-基于yolov8/yolov5/yolo11的蝴蝶种类检测识别系统(Python+卷积神经网络)
  • 如何修复 Vercel 函数超时并大幅降低云函数成本
  • 计组(2)CPU与指令
  • 我的学习经历,个人能力说明书,求职书
  • 伺服器模拟输入控制电机转速
  • 华为云CCE
  • 【计算岗位解析:从代码到产品,这些角色如何“造”出数字世界?】
  • SpringBoot的基础介绍,用法和配置
  • 线上API接口响应慢?一套高效排查与定位问题的心法
  • PyTorch 面试题及详细答案120题(96-105)-- 性能优化与调试
  • Java类的初始化顺序
  • 问题解决方法:qt的设计师页面怎么开启scroll area组件的滚轮功能
  • 【ElasticSearch实用篇-04】Boost权重底层原理和基本使用
  • 机器学习入门,非线性模型的预测方法之多项式
  • 后端笔试题-多线程JUC相关
  • M13 噬菌体展示技术:载体与结构深度解析
  • Git软件版本控制
  • 贵州在假期及夏天结束后保持旅游活力的策略分析
  • elasticsearch中文分词器analysis-ik使用
  • 《山东棒球》板球比赛规则·棒球1号位
  • c语言2:关于变量
  • Robomaster电机控制和serialplot串口绘图(通用)
  • 定时器设计之->分级时间轮
  • Kubernetes 中根据 Pod IP 查找 Pod 及关联服务的方法
  • 蜂窝物联网模组:电动两轮车新国标实施下的关乎安全与智能化支撑
  • 车辆轨迹数据实时同步方案:从 “定时轮询” 到 “消息驱动” 的升级实践
  • Qt Widgets 之 QAbstractButton
  • UCIE Specification详解(十一)