PostgreSQL -- 开源对象-关系型数据库

第一章 PostgreSQL 概述

一、PostgreSQL 简介

1.1 PostgreSQL 介绍

PostgreSQL(通常简称为 Postgres)是一个功能强大的开源对象-关系型数据库系统,以其稳定性、标准兼容性、可扩展性和丰富的功能而广受开发者和企业的青睐。

核心特性:

  • 开源免费:遵循 PostgreSQL 许可证(类似 MIT),可用于商业项目。
  • ACID 兼容:支持事务的原子性、一致性、隔离性和持久性。
  • SQL 标准兼容:高度兼容 ANSI/ISO SQL 标准,支持复杂查询、子查询、CTE、窗口函数等。
  • 扩展性强:支持自定义数据类型、函数、操作符、索引方法(如 GiST、GIN、BRIN),可通过扩展(如 postgis、pg_trgm、uuid-ossp)增强功能。
  • 多版本并发控制(MVCC):读写不阻塞,提高并发性能。
    JSON/JSONB 支持:原生支持半结构化数据存储与查询,适合现代应用。
  • 复制与高可用:支持流复制(Streaming Replication)、逻辑复制、WAL 归档、自动故障转移(配合 Patroni、repmgr 等工具)。
  • 安全性:支持 SSL 连接、行级安全(RLS)、角色权限管理、审计日志等。

1.2 PostgreSQL 安装

1.2.1 下载

进入 PostgreSQL 官网:https://www.postgresql.org/,如下:

点击 Download 进入下载界面:

选择对应的平台,点击进入下载版本的选择:

下载可以使用 EDB(EnterpriseDB) 下载安装包,也可以下载压缩包免安装使用。使用 EDB 如下:

EDB 下载界面选择对应的版本和终端,点击下载即可

1.2.2 Windows 安装

点击已经下载完成的 Windows 平台安装包,进入安装界面:

点击 Next,进入修改安装路径:

选择需要安装的组件:

设置数据库的数据存储路径:

设置超级用户的密码:

设置端口:

选择时区:

安装信息汇总预览:

准备安装确认:

进入实际安装过程:

安装完成:

安装成功之后,在应用程序内有如下信息:

  • Application Stack Builder:一个图形化安装工具,用于安装 PostgreSQL 的附加组件(如扩展、驱动、客户端工具等)。它不是数据库本身,而是用来"构建"应用栈,比如通过它安装 pgAdmin、PostGIS、ODBC 驱动 等,类似于一个"软件包管理器"界面。
  • Installation notes:查看 PostgreSQL 安装的详细信息。
  • pgAdmin 4:PostgreSQL 的官方图形化管理工具(GUI)。
  • pgAdmin documentation:pgAdmin 4 的官方帮助文档。
  • PostgreSQL documentation:PostgreSQL 官方手册
  • PostgreSQL release notes:查看 PostgreSQL 当前版本的新特性、修复内容和已知问题。
  • Reload Configuration:重新加载 PostgreSQL 的配置文件(postgresql.confpg_hba.conf)。
  • SQL Shell (psql):PostgreSQL 的命令行客户端工具(CLI)。

系统命令行连接数据库:

语法:

sql 复制代码
psql -h [ip] -p 5432 -U [用户名] [数据库名]

示例:

1.2.3 pgAdmin 工具

打开 pgAdmin 4,进入官方图形化管理工具,如下:

创建 Postgre SQL 连接:点击左侧的 Servers > Postgre SQL 10,输入超级用户密码

连接成功,界面如下:

界面功能说明:

  • Servers (1):表示你当前连接了 1 个服务器实例,每个 Server 对应一个运行中的 PostgreSQL 服务进程(通常在本地或远程主机上)。

  • PostgreSQL 18:安装的 PostgreSQL 版本(这里是 18)。它是一个"服务器"节点,代表整个数据库系统,右键可执行操作:重启、停止、重新加载配置等。

  • Databases (1):当前服务器上的数据库个数,这里是一个。默认创建的是名为 postgres 的数据库(这是系统默认数据库),可以右键新建其他数据库。
    postgres:这是 PostgreSQL 自动创建的默认数据库。

    • Casts:类型转换规则,定义如何在不同数据类型之间自动转换(如 text → int)。通常不需要手动管理。
    • Catalogs:系统表集合,包含所有系统表(如 pg_class, pg_attribute),记录数据库的元数据。普通用户很少直接操作。
    • Event Triggers:事件触发器,在特定系统事件发生时执行函数(如 CREATE DATABASE、DROP TABLE)。高级功能,常用于审计。
    • Extensions:扩展插件,已安装的扩展(如 postgis, uuid-ossp, hstore)。可通过右键添加新扩展。
    • Foreign Data Wrappers (FDW):外部数据包装器,允许访问外部数据源(如 MySQL、CSV 文件、其他数据库)。适合跨系统集成。
    • Languages (1):存储过程语言,支持的编程语言(如 PL/pgSQL, PL/Python, PL/Perl)。用于编写自定义函数。
    • Publications:发布(复制源,逻辑复制中的"发布"端,指定哪些表要被复制出去。配合 Subscriptions 使用。
    • Schemas:架构(命名空间,数据库内的逻辑分组,类似文件夹。默认有 public schema。可用于隔离不同模块的数据。
    • Subscriptions:订阅(复制目标),逻辑复制中的"订阅"端,从其他数据库拉取数据。常用于主从同步或数据迁移。
  • Login/Group Roles (17):用户与角色,包括数据库用户(登录角色)、组角色及权限设置,初始会有 postgres 用户和其他系统角色。

  • Tablespaces (2):表空间,控制数据文件的物理存储位置。可用于将大表放在高速磁盘上。


二、PostgreSQL 数据类型

2.1 数值类型

PostgreSQL 中的数值类型如下:

名称 存储长度 描述 范围 别名 是否自增
smallint 2 字节 小范围整数 -32,768 到 +32,767 int2
integer 4 字节 常用整数(最通用) -2,147,483,648 到 +2,147,483,647 int, int4
bigint 8 字节 大范围整数 -9,223,372,036,854,775,808 到 +9,223,372,036,854,775,807 int8
decimal 可变长 用户指定精度,精确(金融推荐) 最多 131,072 位数字(整数部分),小数最多 16,383 位 numeric
numeric 可变长 decimal,完全等价 同上 decimal
real 4 字节 单精度浮点,不精确 约 ±1.2×10⁻³⁸ 到 ±3.4×10³⁸ float4
double precision 8 字节 双精度浮点,不精确 约 ±2.2×10⁻³⁰⁸ 到 ±1.8×10³⁰⁸ float8
smallserial 2 字节 自增小整数(自动创建序列) 1 到 32,767 ---
serial 4 字节 自增整数(最常用主键) 1 到 2,147,483,647 ---
bigserial 8 字节 自增大整数(大数据量主键) 1 到 9,223,372,036,854,775,807 ---

说明:

  • 性能对比:整数运算最快 → INT > BIGINT > NUMERIC > FLOAT

  • NUMERIC 的精度:NUMERIC(precision, scale)

    • precision:总位数(整数+小数)
    • scale:小数位数
    • NUMERIC(5,2) 表示最大 999.99,最小 -999.99,NUMERIC 表示无限制(最大 131072 位)
  • 类型选择:

    • 金额、价格、税率推荐用 NUMERIC(p,s)
    • 科学计算、传感器数据推荐用 REAL / DOUBLE PRECISION
    • 计数、ID、索引推荐用整数类型(INTBIGINT
    • 普通 ID 主键用 SERIAL(够用且节省空间),超大系统(如社交平台用 BIGSERIAL,状态码、枚举值等小范围用 SMALLINT 节省空间

2.2 货币类型

money 类型是 PostgreSQL 提供的一个专为存储货币金额而设计数据类型,但是不推荐使用。

numericintbigint 类型的值可以转换为 money

名字 存储容量 描述 范围
money 8 字节 货币金额 -92233720368547758.08 到 +92233720368547758.07

money 类型在显示时会根据当前 lc_monetary(区域货币设置)自动格式化,lc_monetary 的设置如下:

sql 复制代码
-- 设置区域(可选)
SET lc_monetary = 'en_US.UTF-8';  -- 美元格式,显示如$1,234.56
-- 或
SET lc_monetary = 'zh_CN.UTF-8';  -- 人民币格式(部分系统支持)

money 为什么不推荐使用:

  • 依赖操作系统 locale:显示和解析行为由 lc_monetary 决定,在不同服务器(开发/测试/生产)上可能表现不一致,若 locale 不支持,可能报错或显示乱码。

  • 精度固定,无法适应所有货币:强制 2 位小数,无法表示日元(无小数)、科威特第纳尔(3 位小数)等,无法动态适配多币种系统。

  • 应用层兼容性差:应用程序(如 PythonNode.jsJava)读取 money 字段时,常返回带格式的字符串(如 $1,234.56),而非数值,需额外解析,容易出错。

2.3 字符类型

PostgreSQL 支持的字符类型如下:

类型 全称 / 别名 长度限制 存储方式 是否补空格 推荐使用场景
CHAR(n) CHARACTER(n) 固定长度 n(1 ≤ n ≤ 10,737,41823) 不足补空格至 n 字节 极少使用(如固定编码)
VARCHAR(n) CHARACTER VARYING(n) 最多 n 个字符 按实际内容存储 需要明确长度限制时
TEXT --- 无限制(最大约 1 GB) 按实际内容存储 绝大多数场景首选

说明:

  • CHAR(n):固定长度字符串,如果输入字符串长度 < n,自动在右侧填充空格。查询时,尾部空格会被忽略。

  • VARCHAR(n):可变长度带上限,最多存储 n 个字符(不是字节!UTF-8 下一个汉字 = 1 字符),超长会报错。

  • TEXT:无长度限制的字符串

    • PostgreSQL 中 TEXTVARCHAR 在内部使用完全相同的存储结构,性能与 VARCHAR 几乎无差别。
    • PostgreSQL 对 TEXT 做了高度优化:小字符串直接内联存储,大字符串自动压缩并移出主行(TOAST 技术)。
    • VARCHAR 不加长度时(即 VARCHAR)等价于 TEXT

扩展:特殊字符类型

类型 说明
NAME 系统内部使用(最长 64 字节),不建议用户使用
CITEXT 大小写不敏感的 TEXT(需安装 citext 扩展,例:'Alice' = 'alice'true

2.4 日期/时间类型

PostgreSQL 支持的日期/时间类型如下:

类型 全称 存储大小 精度 是否含时区 范围
DATE --- 4 字节 4713 BC ~ 5874897 AD
TIME [WITHOUT TIME ZONE] --- 8 字节 1 微秒 00:00:00 ~ 24:00:00
TIME WITH TIME ZONE TIMETZ 12 字节 1 微秒 同上
TIMESTAMP [WITHOUT TIME ZONE] --- 8 字节 1 微秒 4713 BC ~ 294276 AD
TIMESTAMP WITH TIME ZONE TIMESTAMPTZ 8 字节 1 微秒 同上
INTERVAL --- 16 字节 1 微秒 - ±178,000,000 年

说明:

  • PostgreSQL 除 DATE 类型外的其他日期/时间类型均支持微秒(1 μs = 0.000001 秒)精度。

  • DATE:仅包含日期,格式为 YYYY-MM-DD,且不包含时区信息。

  • TIME [WITHOUT TIME ZONE]:不带时区的时间,格式为 HH:MI:SS[.US]

  • TIME WITH TIME ZONE(TIMETZ:带时区的时间,格式为 HH:MI:SS±TZ

  • TIMESTAMP [WITHOUT TIME ZONE]:不带时区的日期+时间,格式为 YYYY-MM-DD HH:MI:SS[.US]。如果应用部署在不同时区,同一时刻会存成不同值!

  • TIMESTAMP WITH TIME ZONE:带时区的日期+时间,格式为 YYYY-MM-DD HH:MI:SS±TZ,存储时全部转为 UTC 时间戳,查询时自动按当前会话时区转换显示。

  • INTERVAL:时间间隔,表示一段时间长度

常用函数与表达式

功能 表达式
当前时间(带时区) NOW()CURRENT_TIMESTAMP
当前日期 CURRENT_DATE
当前时间(无日期) CURRENT_TIME
转换时区 occurred_at AT TIME ZONE 'UTC'
提取部分 EXTRACT(HOUR FROM ts), DATE(ts)
日期加减 ts + INTERVAL '1 day'

2.5 布尔类型

PostgreSQL 的 布尔类型(BOOLEAN) 用于表示逻辑真(True)/假值(False)。

名称 存储格式 描述
boolean 1 字节 true/false

PostgreSQL 接受多种字符串或数值作为布尔值的输入:

表示 TRUE 的输入 表示 FALSE 的输入
'true', 't' 'false', 'f'
'yes', 'y' 'no', 'n'
'on' 'off'
1 0

所有输入不区分大小写(如 'True', 'YES', 'F' 均有效)。

2.6 枚举类型

PostgreSQL 的枚举类型(Enumerated Types,简称 ENUM) 是一种用户自定义的数据类型,用于定义一个固定、有序的字符串值集合。

枚举类型内部以整数形式高效存储(节省空间),但对外表现为字符串。

枚举类型支持排序(按定义顺序,不是字母顺序)。

与其他类型不同的是枚举类型需要使用 CREATE TYPE 命令创建,语法如下:

sql 复制代码
-- 创建一个订单状态枚举
CREATE TYPE [enum_name] AS ENUM ([val1], [val2],[val3]);

示例:

sql 复制代码
CREATE TYPE week AS ENUM ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun');

在数据表中使用枚举:

sql 复制代码
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    week_day week NOT NULL DEFAULT 'Mon'
);

枚举值的约束与安全:

  • 只能插入定义过的值,插入其他的值会报错 ERROR: invalid input value for enum [enum_name]: [value]
  • 类型安全:不同枚举类型即使值相同也不能互换。
  • PostgreSQL 不允许删除或修改已有枚举值,但可以从 v9.1+ 开始添加新值。

2.7 几何类型

2.7.1 几何类型介绍

PostgreSQL 的几何类型(Geometric Types) 是一组用于表示二维平面空间对象的内置数据类型。几何类型适用于简单的图形、坐标或区域表示,但不适用于专业的地理信息系统(GIS)应用------这类需求应使用 PostGIS 扩展。

PostgreSQL 支持的几何类型如下:

类型 描述 示例
POINT 平面上的一个点 (1.5, 2.0)
LINE 无限长的直线(较少用) {(0,0),(1,1)}
LSEG 有限线段(Line Segment) [(1,2),(3,4)]
BOX 矩形(由两个对角点定义) (1,2),(3,4)
PATH 开放或闭合的路径(点序列) [(0,0),(1,1),(2,2)]
POLYGON 封闭多边形 ((0,0),(1,0),(1,1),(0,1))
CIRCLE 圆(圆心 + 半径) <(1,2),3>

说明:

  • POINT 表示点。

  • LSEG 表示线段。

  • BOX 表示矩形,由左下角和右上角(或任意两个对角点)定义。

  • PATH 表示路径,分为开放路径和闭合路径:

    • 开放路径:首尾不相连,用 [...] 表示
    • 闭合路径:首尾相连,用 (...) 表示
  • POLYGON 表示多边形,必须是闭合的(首尾点可相同或不同,系统自动闭合)。

  • CIRCLE 表示圆,由圆心和半径构成,格式为 <(x,y),r>

2.7.2 常用操作符
操作 说明 示例
<<
&<
&& 相交(重叠) box1 && box2
&>
>>
@> 包含 circle @> point
<@ 被包含 point <@ circle
~=
+ / - 平移 point + point
` `
area() 面积 area(box '(0,0),(2,2)') → 4
center() 中心点 center(circle '<(1,1),2>') → (1,1)
2.7.3 索引支持

可对几何列创建 GiST 索引,加速空间查询

2.7.5 注意事项
  1. 仅限二维平面

    • 不支持 3D(如高程)、球面(地球曲率)计算。
    • 坐标只是数字,无单位、无投影、无地理意义。
  2. 精度有限

    • 使用 double precision 存储坐标,适合工程/图形,不适合高精度测绘。
  3. 功能远弱于 PostGIS:

    • 简单图形、游戏地图、CAD 草图可用内置几何类型
    • 地图、LBS、导航、地理分析必须用 PostGIS
功能 几何类型 PostGIS
地理坐标(WGS84) 不支持 支持
距离(米/千米) 不支持(返回欧氏距离) 支持(大圆距离)
投影变换 不支持 支持
复杂空间关系(相接、包含等) 基础支持 完整支持(DE-9IM)
GeoJSON 导入导出 不支持 支持

2.8 网络地址类型

2.8.1 网络地址类型介绍

网络地址类型专门用于存储和操作 IP 地址、子网和 MAC 地址,这些类型不仅节省空间,还支持丰富的操作符和函数,非常适合网络管理、安全审计、日志分析等场景。

PostgreSQL 支持的网络地址类型如下:

类型 描述 存储大小 示例
inet IPv4 或 IPv6 主机地址(可带 CIDR 掩码) 7--19 字节 '192.168.1.5', '2001:db8::1/64'
cidr IPv4 或 IPv6 网络块(子网) 7--19 字节 '192.168.1.0/24', '2001:db8::/32'
macaddr MAC 地址(IEEE 802 标准,6 字节) 6 字节 '08:00:2b:01:02:03'
macaddr8 扩展 MAC 地址(8 字节,IEEE 802.2014+) 8 字节 '08:00:2b:01:02:03:04:05'

说明:

  • inet:IP 地址(主机或带掩码)

    • 可表示单个主机(不带掩码)或带前缀的地址(常用于 ACL)
    • 允许主机位非零(即使有掩码)
  • cidr:网络块(子网)

    • 严格表示网络地址:主机位必须全为 0
    • 用于定义子网范围
    • 常用于:IP 段分配表、VPC 网络配置、权限组按网段授权
  • macaddr:传统 MAC 地址(6 字节)

    • 支持多种输入格式,如 '08:00:2b:01:02:03''08-00-2b-01-02-03''08002b:010203''08002b010203'
    • 输出统一为 xx:xx:xx:xx:xx:xx 格式
  • macaddr8:扩展 MAC 地址(8 字节)

    • 用于 IEEE 802.2014+ 标准(如 InfiniBand、部分虚拟化环境)
    • 格式:xx:xx:xx:xx:xx:xx:xx:xx
2.8.2 常用操作符和函数

IP 地址操作:

操作 说明 示例
<< 是否在子网内 '192.168.1.5' << '192.168.1.0/24'true
>> 子网是否包含该地址 '192.168.1.0/24' >> '192.168.1.10'true
&& 两个网络是否重叠 '192.168.1.0/24' && '192.168.0.0/16'true
family() 返回 IP 版本 family('::1')6
masklen() 获取掩码长度 masklen('192.168.1.0/24')24
broadcast() 广播地址 broadcast('192.168.1.0/24')192.168.1.255
host() 提取纯 IP(去掩码) host('192.168.1.5/24')'192.168.1.5'

MAC 地址操作:

函数 说明
trunc(macaddr) 清除最后 24 位(获取 OUI 厂商前缀)
macaddr_not, macaddr_and, macaddr_or 位运算
2.8.3 索引与性能

可创建普通 B-tree 索引(支持 =, <, > 等)

更推荐 GiSTSP-GiST 索引 用于子网包含查询

2.9 位串类型

2.9.1 位串类型介绍

PostgreSQL 的位串类型(Bit String Types) 用于存储和操作二进制位序列(0 和 1),适用于需要直接处理位数据的场景,如权限掩码、硬件通信、加密标志、压缩状态等。

PostgreSQL 支持的位串类型如下:

类型 全称 描述 长度限制
bit(n) bit varying 固定长度位串,长度为 n 必须指定 n(1 ≤ n ≤ 8388608)
bit varying(n) varbit(n)(别名) 可变长度位串,最大 n 可选 n;若省略则无上限(最大约 1 GB)

说明:

  • bit(n):固定位数。

    • 插入时必须恰好 n 位,否则报错或截断(取决于设置)常用于已知长度的位掩码
    • 没有长度的 bit 等效于 bit(1)
  • bit varying:可变长度。

    • 适合不确定位数的场景,更灵活,但略耗空间(需存储长度信息)
    • 没有长度的 bit varying 意思是没有长度限制

示例:

sql 复制代码
-- 创建表:用 8 位表示 8 个布尔权限
CREATE TABLE user_roles (
    id SERIAL,
    perms BIT(8),  -- 例如:00101101 表示启用第1、3、4、6项权限
	data VARBIT(1024)  -- 最多 1024 位
);

-- 插入(必须 8 位)
INSERT INTO user_roles (perms, data) VALUES (B'00101101', B'1010');
-- 或
INSERT INTO user_roles (perms, data) VALUES ('11000000', B'11110000111100001111');

-- 错误:长度不匹配
INSERT INTO user_roles (perms) VALUES ('101');  -- ERROR: bit string length 3 does not match type bit(8)

-- 无长度限制(慎用)
CREATE TABLE raw_bits (
	payload VARBIT
);
2.9.2 常用操作符与函数

操作:

操作 说明 示例
& 按位 AND B'1100' & B'1010'B'1000'
` ` 按位 OR
# 按位 XOR B'1100' # B'1010'B'0110'
~ 按位 NOT ~B'1010'B'0101'(注意:结果长度不变)
<< / >> 左/右移位 B'1010' << 2B'1000'

函数:

函数 说明 示例
length(bit) 返回位数 length(B'1010')4
substring(bit from m for n) 截取子串 substring(B'11001011' from 3 for 4)B'0010'
get_bit(bit, pos) 获取某位(从 0 开始) get_bit(B'1010', 1)0
set_bit(bit, pos, newval) 设置某位 set_bit(B'1010', 1, 1)B'1110'
2.9.3 存储与效率

存储方式:每 8 位对齐为 1 字节,bit(5) 占 1 字节,bit(9) 占 2 字节

空间效率高:比用 BOOLEAN[] 或多个 BOOLEAN 字段更紧凑

索引支持:可创建 B-tree 索引(适用于等值查询)

位运算无法使用普通索引加速(如 WHERE perms & B'1000' != B'0000'),需考虑表达式索引或应用层优化。

2.10 文本搜索类型

2.10.1 文本搜索类型介绍

PostgreSQL 的文本搜索类型(Text Search Types) 是其全文检索(Full-Text Search, FTS) 功能的核心组成部分,专为高效、智能地搜索自然语言文本而设计。

文本搜索类型远比简单的 LIKE '%keyword%' 更强大,支持:

  • 词干提取(如 "running" → "run")
  • 忽略停用词(如 "the", "and")
  • 多语言分词
  • 相关性排序(ranking)
  • 短语与邻近搜索

PostgreSQL 支持的文本搜索类型如下:

类型 描述 用途
tsvector 文档的标准化向量表示(包含词位 + 位置信息) 存储被索引的文本内容
tsquery 查询条件的结构化表示(包含关键词 + 逻辑操作符) 表达搜索请求

tsvector:文档的内部表示。将原始文本解析、归一化后,生成一个有序的词位(lexeme)列表,每个词位附带其在文档中的出现位置(可选) 和权重(A/B/C/D)。

示例:

sql 复制代码
SELECT to_tsvector('english', 'The quick brown fox jumps over the lazy dog.');

-- 结果:
-- 'brown':3 'dog':9 'fox':4 'jump':5 'lazi':8 'quick':2
  • 停用词 theover 被移除,jumps → 词干 jumplazylazi(Porter 词干算法)
  • 数字表示词在原文中的位置(从1开始)

tsquery:查询的内部表示

支持的操作符:

操作符 含义 示例
& AND 'cat & dog'
` ` OR
! NOT 'cat & !dog'
<-> 邻近(相邻) 'cat <-> dog'
<N> N 个词以内 'cat <3> dog'

这两个类型通常不直接由用户输入,而是通过函数(如 to_tsvector, to_tsquery)从普通文本转换而来。

2.10.2 基本使用流程

将文档转为 tsvector

sql 复制代码
CREATE TABLE articles (
    id SERIAL,
    title TEXT,
    body TEXT,
    -- 可选:预计算 tsvector 列(推荐用于大表)
    doc_tsvector TSVECTOR
);

-- 插入时计算
INSERT INTO articles (title, body, doc_tsvector)
VALUES (
    'How to use PostgreSQL',
    'PostgreSQL is a powerful open-source database...',
    to_tsvector('english', 'How to use PostgreSQL. PostgreSQL is a powerful open-source database...')
);

执行搜索:

sql 复制代码
-- 搜索包含 "postgres" 或 "database" 的文章
SELECT id, title
FROM articles
WHERE doc_tsvector @@ to_tsquery('english', 'postgres & database');
  • @@ 是 匹配操作符:tsvector @@ tsquery 返回 BOOLEAN
2.10.3 相关性排序

使用 ts_rank()ts_rank_cd() 计算匹配度:

sql 复制代码
SELECT 
    id, 
    title,
    ts_rank(doc_tsvector, query) AS rank
FROM articles,
     to_tsquery('english', 'postgres & database') AS query
WHERE doc_tsvector @@ query
ORDER BY rank DESC;
2.10.4 索引

tsvector 列创建 GIN 索引(最常用)或 GiST 索引:

sql 复制代码
-- GIN:适合静态数据,查询快,更新慢
CREATE INDEX idx_articles_fts ON articles USING GIN (doc_tsvector);

-- GiST:适合频繁更新,查询稍慢
CREATE INDEX idx_articles_fts_gist ON articles USING GiST (doc_tsvector);
2.10.5 高级功能

多字段搜索:

sql 复制代码
-- 合并标题和正文,给标题更高权重
UPDATE articles SET doc_tsvector =
    setweight(to_tsvector('english', title), 'A') ||
    setweight(to_tsvector('english', body), 'B');

权重:A > B > C > D(默认 D),查询时可按权重调整排名

高亮匹配结果:

sql 复制代码
SELECT 
    ts_headline('english', body, to_tsquery('english', 'postgres')) 
FROM articles 
WHERE doc_tsvector @@ to_tsquery('english', 'postgres');

返回:"...powerful open-source PostgreSQL..."

多语言支持:内置 20+ 语言的分词器(english, chinese 需扩展),中文需配合 zhparserjieba 扩展

PostgreSQL 原生不支持中文分词!需安装扩展:

  • 方案 1:zhparser + SCWS
  • 方案 2:应用层分词 + simple 配置

2.11 UUID 类型

2.11.1 UUID 类型介绍

PostgreSQL 的 UUID 类型 是一种专门用于存储通用唯一标识符(Universally Unique Identifier) 的数据类型。它在分布式系统、微服务架构、高并发场景中被广泛使用,因其全局唯一性、无序性和安全性,常作为主键替代自增整数(SERIAL/BIGINT)。

UUID 存储大小为 16 字节(128 位),标准格式为 32 位十六进制 + 4 个连字符:xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx,例如:a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11

PostgreSQL 本身不提供 UUID 生成函数,但可通过内置扩展实现:

sql 复制代码
-- 启用 uuid-ossp 扩展,首次使用需创建(需超级用户权限)
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
2.11.2 常用函数
函数 说明 示例
uuid_generate_v4() 随机 UUID(最常用) a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11
uuid_generate_v1() 基于 MAC 地址 + 时间戳 可排序,但泄露主机信息
gen_random_uuid() 来自 pgcrypto 扩展(更安全) 推荐用于生产环境

使用示例:

sql 复制代码
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email TEXT NOT NULL UNIQUE,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 插入(无需指定 id)
INSERT INTO users (email) VALUES ('alice@example.com');

-- 查询
SELECT * FROM users;
-- id: "f47ac10b-58cc-4372-a567-0e02b2c3d479"

2.12 XML 类型

PostgreSQL 的 XML 类型 用于存储和操作 XML(可扩展标记语言)文档或片段。它支持验证、解析、XPath 查询、XSLT 转换等高级功能,适用于需要处理结构化 XML 数据的场景(如与旧系统集成、医疗/金融行业标准数据交换等)。

XML 类型内部以二进制格式高效存储(非纯文本),可存储完整 XML 文档或 XML 片段。

示例:

sql 复制代码
CREATE TABLE documents (
    id SERIAL,
    content XML
);

XML 通过类型修饰符可以对内容进行验证控制:

用法 说明
XML 默认:仅检查是否良好格式(well-formed)
XML DOCUMENT 必须是完整 XML 文档(有单个根元素)
XML CONTENT 允许文档或片段(默认行为)
XML STRIP WHITESPACE 自动去除无意义空白(实验性)

函数:

函数 说明
xmlelement(name, content) 创建元素
xmlforest(col1, col2) 将多列转为子元素
xmlconcat(...) 拼接 XML 片段
table_to_xml(tbl) 整表转 XML

索引:

2.13 JSON 类型

PostgreSQL 提供了两种强大的 JSON 数据类型:JSONJSONB,用于高效存储和查询半结构化数据。

特性 JSON JSONB
存储格式 原始文本(保留空格、键顺序、重复键) 二进制格式(解析后存储)
存储空间 较大 更小(无冗余)
写入性能 快(直接存字符串) 稍慢(需解析)
读取/查询性能 慢(每次需解析) 极快(已解析)
索引支持 不支持 GIN 索引 支持 GIN 索引
重复键处理 保留所有 仅保留最后一个
键顺序 保留 不保留(按哈希排序)
适用场景 需要精确原始格式(如审计日志) 绝大多数场景

操作符与函数:

操作符 说明 示例 结果类型
-> 返回 JSON 对象 attributes -> 'brand' jsonb
->> 返回文本 attributes ->> 'brand' text
#> 按路径数组取值 attributes #> '{specs, screen}' jsonb
#>> 按路径数组取文本 attributes #>> '{specs, screen}' text

索引:

  • GIN 索引:支持:?, ?|, ?&, @>, <@ 等操作符,适合动态 schema、未知查询模式
  • 表达式索引
  • JSONB 路径索引

2.14 数组类型

PostgreSQL 的数组类型(Array Types) 是其最具特色的功能之一,允许在单个字段中存储多个同类型值,如整数列表、标签集合、坐标序列等。

所有内置类型和用户自定义类型都支持数组,语法为 type[](如 INT[], TEXT[], UUID[])。

数组类型支持多维数组(如 INT[][]),数组的长度是可变可,不需要预定义大小,数组的索引从 1 开始(不是 0!)。

示例:

sql 复制代码
-- 创建表
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    title TEXT,
    tags TEXT[],          -- 字符串数组
    ratings INT[],        -- 整数数组
    matrix FLOAT[][]      -- 二维浮点数组
);

-- 插入数据
-- 方式1:使用花括号(标准 SQL)
INSERT INTO posts (tags, ratings) 
VALUES ('{postgresql,sql,database}', '{5,4,5,3}');

-- 方式2:使用 ARRAY 构造器(更灵活)
INSERT INTO posts (tags, ratings)
VALUES (ARRAY['json','nosql'], ARRAY[4,5]);

-- 二维数组
INSERT INTO posts (matrix)
VALUES ('{{1.0,2.0},{3.0,4.0}}');

2.15 复合类型

PostgreSQL 的复合类型(Composite Types) 是一种用户自定义的数据结构,类似于 C 语言的 struct、Python 的 namedtuple 或 Java 的轻量级 POJO

复合类型将多个字段组合成一个单一值,用于表示复杂对象(如地址、坐标、范围等),并在表、函数、变量中使用。

创建复合类型使用 CREATE TYPE ... AS 语句,示例:

sql 复制代码
-- 定义一个"地址"复合类型
CREATE TYPE address AS (
    street TEXT,
    city TEXT,
    postal_code TEXT,
    country TEXT
);

-- 定义一个"价格范围"
CREATE TYPE price_range AS (
    min_price NUMERIC(10,2),
    max_price NUMERIC(10,2)
);

-- 使用符合类型字段
CREATE TABLE companies (
    id SERIAL PRIMARY KEY,
    name TEXT,
    headquarters address,      -- 使用复合类型
    budget price_range
);

-- 插入数据
INSERT INTO companies (name, headquarters, budget) VALUES
('Acme Corp', 
 ('123 Main St', 'New York', '10001', 'USA')::address,
 (50000.00, 200000.00)::price_range
);
  • 复合类型中的字段可为任意 PostgreSQL 类型。
  • 值必须用 (...) 包裹,并显式转换为类型(如 ::address),否则会被当作行字面量。

2.16 范围类型

PostgreSQL 的范围类型(Range Types) 是一种强大而优雅的数据类型,用于表示连续区间,如时间范围、数值区间、IP 地址段等。它原生支持区间包含、重叠、相邻等操作,并可配合 GiST 索引 实现高效查询,非常适合日程安排、价格策略、资源分配等场景。

PostgreSQL 支持的范围类型如下:

范围类型 对应元素类型 示例
int4range INTEGER [10, 20)
int8range BIGINT [100, 200]
numrange NUMERIC (1.5, 5.0]
tsrange TIMESTAMP ["2025-01-01 09:00", "2025-01-01 17:00")
tstzrange TIMESTAMPTZ ["2025-01-01 09:00+08", "2025-01-01 17:00+08")
daterange DATE ["2025-01-01", "2025-01-31"]
  • 所有范围类型都支持 开区间 ( / 闭区间 [ 的任意组合。

    • [a, b]:闭区间:包含 a 和 b
    • (a, b):开区间:不包含 a 和 b
    • [a, b):左闭右开:包含 a,不包含 b
    • (a, b]:左开右闭:不包含 a,包含 b
    • [a,):从 a 到正无穷
    • (,b]:从负无穷到 b
    • empty:空范围(长度为 0)

示例:

sql 复制代码
CREATE TABLE room_bookings (
    id SERIAL PRIMARY KEY,
    room_name TEXT,
    during tstzrange  -- 使用带时区的时间范围
);

-- 插入一个预订
INSERT INTO room_bookings (room_name, during) VALUES (
    'Meeting Room A',
    tstzrange('2025-12-05 14:00+08', '2025-12-05 15:30+08')
);

操作符与函数:

区间关系判断:

操作符 含义 示例
&& 重叠(有交集) r1 && r2
@> 包含(左包含右) during @> '2025-12-05 14:30+08'::timestamptz
<@ 被包含 '2025-12-05'::date <@ during
<< 严格在左(不接触) r1 << r2
>> 严格在右 r1 >> r2
&< 不延伸到右边(左 ≤ 右.end) 用于"结束前"查询
&> 不延伸到左边 用于"开始后"查询
`- -` 相邻(如 [1,2] 和 [2,3])

函数:

函数 说明
lower(range) 下界
upper(range) 上界
isempty(range) 是否为空
range_merge(r1, r2) 合并两个重叠/相邻范围

索引:对范围列创建 GiST 索引 以加速重叠、包含等查询

自定义范围类型

2.17 对象标识符类型

PostgreSQL 的对象标识符类型(Object Identifier Types) 是一组用于内部系统管理的特殊数据类型,主要用于存储数据库系统目录(system catalogs)中的 OID(Object Identifier)值。

对象标识符类型在普通应用开发中极少直接使用,但在系统监控、扩展开发或深入理解 PostgreSQL 内部机制时非常重要。

OID(Object Identifier)在 PostgreSQL 早期用于唯一标识系统对象(如表、函数、类型等)的 32 位无符号整数,从 PostgreSQL 8.1 起,默认创建的用户表不再自动生成 OID 列(default_with_oids = off),因为:

  • OID 空间有限(约 40 亿,可能溢出)
  • 不适合分布式环境
  • 主键应显式定义(如 SERIAL 或 UUID)

PostgreSQL 支持的对象标识符类型如下:

类型 描述 典型用途
oid 通用对象 ID 系统表主键(如 pg_class.oid
regproc 注册的函数名 pg_proc.oid 的别名,显示为函数名
regprocedure 带参数的函数签名 'substr(text,integer)'
regoper 操作符名 '+'
regoperator 带类型的完整操作符 '+(integer,integer)'
regclass 表/索引/序列名 最常用!可安全转换为表名
regtype 数据类型名 'int4', 'text'
regconfig 文本搜索配置名 'english'
regdictionary 文本搜索字典名 'simple'

2.18 伪类型

PostgreSQL 类型系统包含一系列特殊用途的条目, 它们按照类别来说叫做伪类型。伪类型不能作为字段的数据类型, 但是它可以用于声明一个函数的参数或者结果类型。 伪类型在一个函数不只是简单地接受并返回某种SQL 数据类型的情况下很有用。

PostgreSQL 支持的伪类型如下:

名字 描述
any 表示一个函数接受任何输入数据类型。
anyelement 表示一个函数接受任何数据类型。
anyarray 表示一个函数接受任意数组数据类型。
anynonarray 表示一个函数接受任意非数组数据类型。
anyenum 表示一个函数接受任意枚举数据类型。
anyrange 表示一个函数接受任意范围数据类型。
cstring 表示一个函数接受或者返回一个空结尾的 C 字符串。
internal 表示一个函数接受或者返回一种服务器内部的数据类型。
language_handler 一个过程语言调用处理器声明为返回 language_handler
fdw_handler 一个外部数据封装器声明为返回 fdw_handler
record 标识一个函数返回一个未声明的行类型。
trigger 一个触发器函数声明为返回 trigger
void 表示一个函数不返回数值。
opaque 一个已经过时的类型,以前用于所有上面这些用途。

三、PostgreSQL 运算符

3.1 算术运算符

PostgreSQL 支持如下算术运算符:假设变量 a 为 2,变量 b 为 3

运算符 描述 实例
+ a + b 结果为 5
- a - b 结果为 -1
* a * b 结果为 6
/ b / a 结果为 1
% 模(取余) b % a 结果为 1
^ 指数 a ^ b 结果为 8
|/ 平方根 |/ 25.0 结果为 5
||/ 立方根 ||/ 27.0 结果为 3
! 阶乘 5 ! 结果为 120
!! 阶乘(前缀操作符) !! 5 结果为 120

3.2 比较运算符

PostgreSQL 支持如下比较运算符:假设变量 a 为 10,变量 b 为 20

运算符 描述 实例
= 等于 (a = b) 为 false。
!= 不等于 (a != b) 为 true。
<> 不等于 (a <> b) 为 true。
> 大于 (a > b) 为 false。
< 小于 (a < b) 为 true。
>= 大于等于 (a >= b) 为 false。
<= 小于等于 (a <= b) 为 true。

3.3 逻辑运算符

PostgreSQL 支持如下逻辑运算符:

  • AND:逻辑与运算符。如果两个操作数都非零,则条件为真。
  • NOT:逻辑非运算符。用来逆转操作数的逻辑状态,如果条件为真则逻辑非运算符将使其为假。
  • OR:逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。

ANDOR 示例:

a b a AND b a OR b
TRUE TRUE TRUE TRUE
TRUE FALSE FALSE TRUE
TRUE NULL NULL TRUE
FALSE FALSE FALSE FALSE
FALSE NULL FALSE NULL
NULL NULL NULL NULL

NOT 示例:

a NOT a
TRUE FALSE
FALSE TRUE
NULL NULL

3.4 按位运算符

位运算符作用于位,并逐位执行操作。PostgreSQL 支持如下按位运算符:假设变量 A 的值为 60,变量 B 的值为 13

运算符 描述 运算规则 实例
& 按位与操作,按二进制位进行"与"运算。 0&0=0; 0&1=0; 1&0=0; 1&1=1; A & B = 12,即为 0000 1100
` ` 按位或运算符,按二进制位进行"或"运算。 `0
# 异或运算符,按二进制位进行"异或"运算。 0#0=0; 0#1=1; 1#0=1; 1#1=0; A # B = 49,即为 0011 0001
~ 取反运算符,按二进制位进行"取反"运算。 ~1=0; ~0=1; ~A = -61,即为 1100 0011,一个有符号二进制数的补码形式。
<< 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 A << 2 = 240,即为 1111 0000
>> 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 A >> 2 = 15,即为 0000 1111

第二章 PostgreSQL 语法

一、PostgreSQL 数据库表操作

1.1 数据库操作

1.1.1 创建数据库

PostgreSQL 创建数据库可以用以下三种方式:

  1. 使用 CREATE DATABASE 语句来创建。
  2. 使用 createdb 命令来创建。
  3. 使用 pgAdmin 工具等可视化工具。

CREATE DATABASE 命令需要在 PostgreSQL 命令窗口来执行,其语法如下:

sql 复制代码
CREATE DATABASE dbname;

示例:

sql 复制代码
CREATE DATABASE mysql_test_db1;

createdb 是一个 SQL 命令 CREATE DATABASE 的封装,其语法如下:

sql 复制代码
createdb [option...] [dbname [description]]

参数说明:

  • dbname:要创建的数据库名。

  • description:关于新创建的数据库相关的说明。

  • options:参数可选项,可以是以下值:

    • -D tablespace:指定数据库默认表空间。
    • -e:将 createdb 生成的命令发送到服务端。
    • -E encoding:指定数据库的编码。
    • -l locale:指定数据库的语言环境。
    • -T template:指定创建此数据库的模板。
    • --help:显示 createdb 命令的帮助信息。
    • -h host:指定服务器的主机名。
    • -p port:指定服务器监听的端口,或者 socket 文件。
    • -U username:连接数据库的用户名。
    • -w:忽略输入密码。
    • -W:连接时强制要求输入密码。

示例:

sql 复制代码
createdb -h localhost -p 5432 -U postgres mysql_test_db2
1.1.2 选择数据库

在 PostgreSQL 命令窗口中,通过命令 \n 可以查询已经存在的数据库,如下:

如果要使用具体的数据库,可以使用命令 \c [dbName],如下:

1.1.3 删除数据库

PostgreSQL 提供三种删除数据库的方式:

  1. 使用 DROP DATABASE 语句来删除。
  2. 使用 dropdb 命令来删除。
  3. 使用 pgAdmin 等可视化工具。

DROP DATABASE 语句:

  • DROP DATABASE 语句会删除数据库的系统目录项并且删除包含数据的文件目录,只能由超级管理员或数据库拥有者执行。

  • DROP DATABASE 语句命令需要在 PostgreSQL 命令窗口来执行。

DROP DATABASE 语法如下:

sql 复制代码
DROP DATABASE [ IF EXISTS ] name

参数说明:

  • IF EXISTS:如果数据库不存在则发出提示信息,而不是错误信息吗,不添加这个选项的时候,如果删除的数据库不存在则会报错。

  • name:要删除的数据库的名称。

示例:

dropdb 命令:

  • dropdbDROP DATABASE 的包装器, 用于删除 PostgreSQL 数据库。
  • dropdb 命令一般只能由超级管理员或数据库拥有者执行。

dropdb 语法如下:

sql 复制代码
dropdb [connection-option...] [option...] dbname

参数说明:

  • dbname:要删除的数据库名。

  • options:参数可选项,可以是以下值:

    • -e:显示 dropdb 生成的命令并发送到数据库服务器。
    • -i:在做删除的工作之前发出一个验证提示。
    • -V:打印 dropdb 版本并退出。
    • --if-exists:如果数据库不存在则发出提示信息,而不是错误信息。
    • --help:显示有关 dropdb 命令的帮助信息。
    • -h host:指定运行服务器的主机名。
    • -p port:指定服务器监听的端口,或者 socket 文件。
    • -U username:连接数据库的用户名。
    • -w:连接时忽略输入密码。
    • -W:连接时强制要求输入密码。
    • --maintenance-db=dbname:删除数据库时指定连接的数据库,默认为 postgres,如果它不存在则使用 template1

示例:

1.2 数据表操作

1.2.1 创建表

PostgreSQL 使用 CREATE TABLE 语句来创建数据库表,语法如下:

sql 复制代码
CREATE TABLE [table_name] (
   column1 datatype,
   column2 datatype,
   column3 datatype,
   .....
   columnN datatype,
   PRIMARY KEY( 一个或多个列 )
);

CREATE TABLE 在当前数据库创建一个新的空白表,该表将由发出此命令的用户所拥有。

示例:

sql 复制代码
CREATE TABLE USERS (
   ID INT PRIMARY KEY     NOT NULL,
   NAME           TEXT    NOT NULL,
   AGE            INT     NOT NULL,
   ADDRESS        CHAR(50),
   SALARY         REAL
);

创建结果:

1.2.2 查看表

在 PostgreSQL 命令窗口中,可以通过命令 \d 查询已有的表,通过命令 \d tablename 查看具体的表格信息:

1.2.3 删除表

PostgreSQL 使用 DROP TABLE 语句来删除表格,包含表格数据、规则、触发器等。语法如下:

sql 复制代码
DROP TABLE table_name;

示例:

1.3 SCHEMA

在 PostgreSQL 中,模式(Schema) 是数据库对象(如表、视图、函数、索引等)的命名空间容器。它提供了一种逻辑分组机制,用于组织和管理数据库对象,同时支持多用户环境下的权限控制和命名隔离。

一个数据库可以包含多个模式,每个模式可以包含多个数据库对象(如表、序列、函数等)。默认情况下,每个新数据库都包含一个名为 public 的模式,用户在未指定模式名时,默认使用 search_path 中的第一个可用模式(通常是 public)。

1.3.1 创建 SCHEMA

PostgreSQL 使用 CREATE SCHEMA 语句创建模式,语法如下:

sql 复制代码
CREATE SCHEMA myschema;
-- 或者指定拥有者
CREATE SCHEMA schema_name AUTHORIZATION user_name;
1.3.2 查看 SCHEMA

在 PostgreSQL 命令窗口通过 \dn 命令可以查看当前的所有 SCHEMA,也可以通过 SQL 语句查看,语法如下

sql 复制代码
\dn  -- 在 psql 命令行中

-- 或

SELECT schema_name
FROM information_schema.schemata;

示例:

1.3.3 删除 SCHEMA

PostgreSQL 使用 DROP SCHEMA 语句删除模式,语法如下:

sql 复制代码
-- 删除空模式
DROP SCHEMA schema_name;

-- 删除模式及其所有对象(级联删除)
DROP SCHEMA schema_name CASCADE;

示例:

1.4 ALTER 命令

在 PostgreSQL 中,ALTER TABLE 命令用于添加,修改,删除一张已经存在表的列,也可以用来添加和删除约束。

ALTER TABLE 命令语法:

sql 复制代码
ALTER TABLE [IF EXISTS] table_name
    action [, ...];

语法详解:

  • table_name:要修改的表名(可带 schema,如 public.users
  • action:一个或多个修改操作,多个操作时用逗号分隔
  • IF EXISTS:防止表不存在时报错
1.4.1 ALTER 表字段

添加新列:ADD COLUMN

sql 复制代码
ALTER TABLE users
ADD COLUMN phone TEXT;

-- 带默认值和非空约束
ALTER TABLE users
ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
  • 给已有表加 NOT NULL 列时,必须提供 DEFAULT 值,否则会失败。

删除列:DROP COLUMN

sql 复制代码
ALTER TABLE users
DROP COLUMN phone;

-- 如果列不存在也不报错
ALTER TABLE users
DROP COLUMN IF EXISTS old_field;
  • 不能删除被视图、索引、外键引用的列(需先删依赖)
  • 不能删除分区键列

修改列的数据类型:ALTER COLUMN ... TYPE

sql 复制代码
-- 将 age 从 INT 改为 SMALLINT
ALTER TABLE users
ALTER COLUMN age TYPE SMALLINT;

-- 需要转换格式(例如字符串转日期)
ALTER TABLE logs
ALTER COLUMN created_at TYPE TIMESTAMPTZ
USING created_at::TIMESTAMPTZ;
  • USING 子句:当 PostgreSQL 无法自动转换时,手动指定转换逻辑。

设置/删除默认值:SET/DROP DEFAULT

sql 复制代码
-- 设置默认值
ALTER TABLE users
ALTER COLUMN created_at SET DEFAULT NOW();

-- 删除默认值
ALTER TABLE users
ALTER COLUMN created_at DROP DEFAULT;

设置/移除非空约束:SET/DROP NOT NULL

sql 复制代码
-- 禁止为空
ALTER TABLE users
ALTER COLUMN email SET NOT NULL;

-- 允许为空
ALTER TABLE user
ALTER COLUMN email DROP NOT NULL;
  • SET NOT NULL 会扫描全表!确保现有数据都非空,否则失败。

重命名列:RENAME COLUMN

sql 复制代码
ALTER TABLE users
RENAME COLUMN username TO nickname;
  • 视图、函数中引用的列名不会自动更新,需手动调整。
1.4.2 ALTER 表

重命名表:RENAME TO

sql 复制代码
ALTER TABLE users
RENAME TO user_accounts;
  • 所有索引、约束、外键会自动跟随新表名。

更改表的 Owner:

sql 复制代码
ALTER TABLE users
OWNER TO new_user;

设置表级选项:SET ..

sql 复制代码
-- 启用/禁用行级安全
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- 设置填充因子(影响 UPDATE 性能)
ALTER TABLE users SET (fillfactor = 80);

-- 设置表空间
ALTER TABLE users SET TABLESPACE fast_ssd;
1.4.3 ALTER 表约束

添加约束:ADD CONSTRAINT

sql 复制代码
-- 添加主键约束
ALTER TABLE users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);

-- 添加唯一约束
ALTER TABLE users
ADD CONSTRAINT uk_users_email UNIQUE (email);

-- 添加检查约束
ALTER TABLE users
ADD CONSTRAINT chk_age CHECK (age >= 0 AND age <= 150);

-- 添加外键约束
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE;

删除约束:DROP CONSTRAINT

sql 复制代码
ALTER TABLE users
DROP CONSTRAINT uk_users_email;

-- 如果约束不存在也不报错
ALTER TABLE users
DROP CONSTRAINT IF EXISTS non_existent_constraint;

通过查系统表可以查看所有约束:SELECT conname FROM pg_constraint WHERE conrelid = 'users'::regclass;

1.4.4 ALTER 其他操作

批量 ALTER

sql 复制代码
ALTER TABLE users
    ADD COLUMN bio TEXT,
    ALTER COLUMN email SET NOT NULL,
    RENAME COLUMN name TO full_name;

结合 IF EXISTS / IF NOT EXISTS 条件性修改:

sql 复制代码
ALTER TABLE users
ADD COLUMN IF NOT EXISTS last_login TIMESTAMPTZ;

处理大表时的性能优化:添加带 DEFAULT 的列,新版本(PostgreSQL ≥11)不会重写全表,但如果是旧版本(<11),会锁表并重写

sql 复制代码
-- v11+:瞬间完成(即使表有 10 亿行)
ALTER TABLE huge_table ADD COLUMN flag BOOLEAN DEFAULT false;

二、PostgreSQL 数据行操作

2.1 插入数据

在 PostgreSQL 中,插入数据使用 INSERT INTO 语句,语法如下:

sql 复制代码
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);

-- 插入多行数据
INSERT INTO table_name (column1, column2, ...)
VALUES 
(value1, value2, ...),
(value1, value2, ...),
(value1, value2, ...);

示例:

sql 复制代码
-- 插入单行数据
INSERT INTO users(id, name, age, address, salary)
VALUES (1, '张三', 18, '北京市', 3000);

-- 插入多行数据
INSERT INTO users(id, name, age, address, salary)
VALUES 
	(2, '李四', 100, '成都市', 18000),
	(3, '王二', 50, '西安市', 50000),
	(4, '麻子', 10, '重庆市', 10000);

数据表结果:

PostgreSQL 支持直接从一个表中查询数据来新增到另一个表中,使用 INSERT INTO ... SELECT 语句,语法如下:

sql 复制代码
INSERT INTO table_name (column1, column2, ...)
SELECT column1, column2, ...
FROM table_name1;

示例:

sql 复制代码
INSERT INTO users_backup (id, name, age, address, salary)
SELECT id, name, age, address, salary
FROM users;

数据表结果:

PostgreSQL 支持在插入数据后返回插入的行,常用于获取自增主键。使用 RETURNING 语句,语法如下

sql 复制代码
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);
RETURNING column1, column2;
-- 常用于获取自增主键

示例:

sql 复制代码
INSERT INTO users(id, name, age, address, salary)
VALUES 
	(10, 'AAA', 100, '成都市', 18000),
	(11, 'BBB', 50, '西安市', 50000),
	(12, 'CCC', 10, '重庆市', 10000)
RETURNING id, name;

结果:

2.2 删除数据

PostgreSQL 删除数据语法如下:

sql 复制代码
DELETE FROM [table_name] WHERE [condition];
  • condition 是由 WHERE 开头的筛选条件,如果省略 WHERE 会删除整张表的所有行!

示例:

sql 复制代码
DELETE FROM users WHERE id = 10;

结果:

PostgreSQL 支持删除后返回被删除的行,使用 RETURNNING 语句,语法如下:

sql 复制代码
DELETE FROM [table_name] WHERE [condition]
RETURNNING *;

示例:

sql 复制代码
DELETE FROM users WHERE id = 11
RETURNING *;

结果:

PostgreSQL 支持基于其他表进行条件删除,使用 USING 语句,语法如下:

sql 复制代码
DELETE FROM [table_name1]
USING [table_name2]
WHERE [table_name1和table_name2的关联条件];

示例,有如下两个表的数据:

users 表中删除 id 等于 users_backup 表的 id 的数据:

sql 复制代码
DELETE FROM users
USING users_backup
WHERE users.id = users_backup.id;

结果:

2.3 编辑数据

PostgreSQL 编辑数据语法如下:

sql 复制代码
UPDATE [table_name]
SET [colunm_name]= [value]
WHERE [condition];

示例:

sql 复制代码
UPDATE users SET address = '宇宙' WHERE id = 1;

结果:

PostgreSQL 支持编辑后返回被编辑的行,使用 RETURNNING 语句,语法如下:

sql 复制代码
UPDATE [table_name]
SET [colunm_name]= [value]
WHERE [condition]
RETURNING *;

示例:

sql 复制代码
UPDATE users SET address = '宇宙' WHERE id = 2
RETURNING *;

结果:

PostgreSQL 支持基于其他表进行条件编辑,使用 USING 语句,语法如下:

sql 复制代码
UPDATE [table_name1]
SET [colunm_name]= [value]
FROM [table_name2]
WHERE [table_name1和table_name2的关联条件];

示例,有如下两个表的数据:

users 表中修改 id 等于 users_backup 表的 id 的数据,将 salary 修改为100,如下:

sql 复制代码
UPDATE users
SET salary = 100
FROM users_backup
WHERE users.id = users_backup.id;

结果:

2.4 查询数据

2.4.1 基础查询

PostgreSQL 中,基础查询语法如下:

sql 复制代码
SELECT [columns] FROM [db_name] 

示例:

sql 复制代码
SELECT * FROM users;

查询结果:

PostgreSQL 的查询支持很多语句,如 WHERE 条件查询、GROUP BY 分组、ORDER BY 排序等。完整的查询语句语法如下:

sql 复制代码
[ WITH [ RECURSIVE ] with_query [, ...] ]
SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ]
    * | expression [ [ AS ] output_name ] [, ...]
FROM from_item [, ...]
[ WHERE condition ]
[ GROUP BY grouping_element [, ...] ]
[ HAVING group_condition ]
[ WINDOW window_name AS ( window_definition ) [, ...] ]
[ ORDER BY sort_expression [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] ]
[ LIMIT { count | ALL } ]
[ OFFSET start [ ROW | ROWS ] ]
[ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY ]
[ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF table_name [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ]

虽然书写顺序是 SELECT ... FROM ... WHERE ...,但 SQL 引擎的逻辑执行顺序 是:

sql 复制代码
FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET
2.4.2 WHERE 子句

在 PostgreSQL 中,WHERE 子句用于过滤行,只返回满足指定条件的记录,除了用于 SELECT,也常用于 UPDATEDELETEDML 语句。

WHERE 子句语法:

sql 复制代码
-- 查询
SELECT [columns] FROM [table_name] WHERE [condition];

-- 编辑
UPDATE [table_name] SET ... WHERE [condition];

-- 删除
DELETE FROM [table_name] WHERE [condition];

WHERE 后的条件可以是任意返回布尔值(true/false/unknown)的表达式,表达式可以由各种运算符或SQL语句组成。

users 表有如下数据:

查询示例:

sql 复制代码
select * from users where age > 50;
select * from users where salary = 3000;

AND / OR / NOT

  • AND:所有条件都为真

  • OR:任一条件为真

  • NOT:取反

  • NOT > AND > OR

sql 复制代码
-- AND
select * from users where name = '牛牛' and salary = 3000;

-- OR
select * from users where name = '牛牛' or salary = 3000;

-- NOT 
select * from users where not (name = '牛牛' or salary = 3000);

IS NULL / IS NOT NULL

  • IS NULL:字段的值为 null

  • IS NOT NULL:字段的值不为 null

sql 复制代码
select * from users where address is null;

select * from users where address is not null;

IN / NOT IN

  • IN:字段的值在指定的 value
  • NOT IN:字段的不值在指定的 value
sql 复制代码
select * from users where id in (1,3,5,7,9);

select * from users where id not in (1,3,5,7,9);

BETWEEN AND

  • 查询字段的值在指定范围内的数据,返回包含左右边界
sql 复制代码
select * from users where salary between 3000 and 15000;

LIKE / ILIKE

  • 模糊查询,LIKE 区分大小写,ILIKE 不区分大小写

  • 模糊查询通过通配符实现:

    • %:匹配任意多个字符,
    • _:匹配单个字符
  • 如果添加通配符,则语句等价于 = 语句,如 a like 'b' 等价于 a = 'b'

sql 复制代码
select * from users where address like '%重庆%';	-- 匹配 重庆市                                               
select * from users where address like '重庆%';	-- 匹配 重庆市                                               
select * from users where address like '%重庆';	-- 匹配无数据

-- LIKE / ILIKE 对比
select * from users where name like '%A%';	-- 匹配 AAA
select * from users where name like '%a%';	-- 匹配 无数据
select * from users where name ilike '%A%';	-- 匹配 AAA
select * from users where name ilike '%a%';	-- 匹配 AAA

-- 通配符对比
select * from users where name like '%A%';	-- 匹配 AAA
select * from users where name like '_A_';	-- 匹配 无数据

select * from users where name like 'A%';	-- 匹配 AAA
select * from users where name like 'A_';	-- 匹配 无数据

select * from users where name like '%A';	-- 匹配 AAA
select * from users where name like '_A';	-- 匹配 无数据

ANY / ALL

  • 批量匹配,通常配合数组或子查询
  • ANY:只要匹配指定数据中的任意一个,就算满足条件
  • ALL:必须匹配指定数据中的每一个才算满足条件
sql 复制代码
-- age>40 且 age>70
select * from users where age > all(ARRAY[40,70]);

-- age>40 或 age>70
select * from users where age > ANY(ARRAY[40,70]);
2.4.3 ORDER BY / LIMIT

在 PostgreSQL 中,ORDER BY 用于对一列或者多列数据进行排序:

  • ASC:升序,不指定排序方式时默认是 ASC
  • DESC:降序

语法:

sql 复制代码
SELECT column-list
FROM [table_name]
WHERE [condition]
ORDER BY [column1, column2, .. columnN] [ASC | DESC];

示例:

sql 复制代码
select * from users order by age asc;	-- age从小到大

select * from users order by age;		-- age从小到大

select * from users order by age desc;	-- age从大到小

ORDER BY 通常搭配 LIMIT 使用,PostgreSQL 中 LIMIT 子句用于限制查询的数据的数量。

LIMIT 语法:

sql 复制代码
SELECT [columns]
FROM [table_name]
LIMIT [no of rows] OFFSET [row num]
  • LIMIT:限制查询多少条数据
  • OFFSET:偏移量,从第几条开始查询,初始值为0

示例:

sql 复制代码
-- 100,99,84,78,62,50,37,29,19,10
select * from users order by age desc

select * from users order by age desc limit 4;	-- 100,99,84,78

select * from users order by age desc limit 4 offset 0;	-- 78,62,50,37
2.4.4 GROUP BY / HAVING

(1)GROUP BY

在 PostgreSQL 中,GROUP BY 语句用将结果集按一个或多个列(或表达式)相同的数据进行分组,通常与聚合函数(如 COUNT()SUM()AVG()MAX()MIN() 等)一起使用,对每个分组进行统计计算。

GROUP BY 子句必须放在 WHERE 子句中的条件之后,必须放在 ORDER BY 子句之前。

sql 复制代码
-- 分组并取每个组的salary总和
select address, sum(salary) from users group by address;
-- 分组并取每个组的salary最大值
select address, max(salary) from users group by address;
-- 分组并取每个组的salary最小值
select address, min(salary) from users group by address;
-- 分组并取每个组的salary平均值
select address, avg(salary) from users group by address;

(2)HAVING

GROUP BY 常与 HAVING 配合使用,HAVING 也是用于过滤数据,其与 WHERE 的区别如下:

  • WHERE:在分组前执行,用于过滤原始行
  • HAVING:在分组后执行,用于过滤分组的结果
sql 复制代码
select address, avg(salary) from users group by address having avg(salary) > 5000;

常与 GROUP BY 结合的聚合函数:

聚合函数 说明 聚合函数 说明
COUNT(*) 行数(含 NULL) MAX(created_at) 最大值
COUNT(col) 非 NULL 值数量 MIN(id) 最小值
SUM(price) 求和 STRING_AGG(name, ', ') 字符串拼接
AVG(score) 平均值(忽略 NULL) ARRAY_AGG(email) 聚合成数组

(3)高级分组功能

在 PostgreSQL 中,GROUPING SETSROLLUPCUBE 是 高级分组功能,用于在单个查询中生成 多个维度的聚合汇总结果(类似"多级小计"和"总计"),常用于报表和数据分析场景。

  • GROUPING SETS:精确控制分组组合,显式指定想要的所有分组维度组合。
  • ROLLUP:层级式小计(从细到粗),自动生成层级递进的小计,适用于有天然层次结构的数据(如:年 → 季度 → 月)。
  • CUBE:所有维度组合的交叉汇总,生成所有可能的分组组合(笛卡尔积式的汇总),适合多维分析(OLAP)。

GROUPING SETS 示例:

sql 复制代码
-- 假设 sales 表有如下数据
/*
	product	region	amount
	A		North	100
	A		South	150
	B		North	200
*/

-- 查询sql
SELECT 
  product,
  region,
  SUM(amount) AS total
FROM sales
GROUP BY GROUPING SETS (
  (product, region),   -- 组合1:按产品+区域分组
  (product),           -- 组合2:只按产品分组
  (region),            -- 组合3:只按区域分组
  ()                   -- 组合4:全局总计(无分组)
)
ORDER BY product, region;

-- 结果

查询结果:

product region total 对应的 GROUPING SETS 分组 说明
A North 100 (product, region) A 在 North 的销售额
A South 150 (product, region) A 在 South 的销售额
A NULL 250 (product) A 产品的总销售额(North + South)
B North 200 (product, region) B 在 North 的销售额
B NULL 200 (product) B 产品的总销售额
NULL North 300 (region) North 区域总销售额(A+ B)
NULL South 150 (region) South 区域总销售额(A)
NULL NULL 450 () 全局总销售额

ROLLUP 语法:

sql 复制代码
SELECT col1, col2, ..., aggregate_function(...)
FROM table
GROUP BY ROLLUP(col1, col2, ..., colN);

-- 等价于
SELECT col1, col2, ..., aggregate_function(...)
FROM table
GROUP BY GROUPING SETS (
  (col1, col2, ..., colN),
  (col1, col2, ..., colN-1),
  ...,
  (col1),
  ()
)

ROLLUP 示例:

sql 复制代码
-- 假设表 orders 有如下数据
/*
	order_id	order_date		amount
		1		2024-01-10		100
		2		2024-01-15		200
		3		2024-02-05		150
		4		2025-01-20		300
*/

-- 查询SQL
SELECT
  EXTRACT(YEAR FROM order_date) AS year,
  EXTRACT(MONTH FROM order_date) AS month,
  SUM(amount) AS total
FROM orders
GROUP BY ROLLUP(year, month)
ORDER BY year, month;

PostgreSQL 允许在 GROUP BY 中直接使用 SELECT 列别名(如 year, month),这是它的扩展特性。

查询结果分析:

year month total 对应的分组层级 含义
2024 1 300 (year, month) 2024 年 1 月销售额(100+200)
2024 2 150 (year, month) 2024 年 2 月销售额
2024 NULL 450 (year) 2024 年小计(1月+2月)
2025 1 300 (year, month) 2025 年 1 月销售额
2025 NULL 300 (year) 2025 年小计
NULL NULL 750 () 总计(所有年月)

CUBE 语法:

sql 复制代码
SELECT col1, col2, ..., aggregate_function(...)
FROM table
GROUP BY CUBE(col1, col2, ..., colN);

-- 等价于
SELECT col1, col2, ..., aggregate_function(...)
FROM table
GROUP BY GROUPING SETS (
  (col1, col2, ..., colN),
  (col1, col2, ..., colN-1),
  ...,
  (col1, col3),
  (col2, col3),
  ...,
  (col1),
  (col2),
  ...,
  ()
)
-- 所有可能的子集组合(共 2^N 个)

CUBE 示例:

sql 复制代码
-- 假设表 sales 数据如下:
/*
	product	region	salesperson	amount
		A	North	Alice		100
		A	South	Bob			150
		B	North	Alice		200
*/

-- 查询SQL
SELECT
  product,
  region,
  salesperson,
  SUM(amount) AS total
FROM sales
GROUP BY CUBE(product, region, salesperson)
ORDER BY product, region, salesperson;

查询结果分析:共 2³ = 8 种分组,实际结果行数可能少于 18 行,因为某些组合在数据中不存在,就不会出现在分组结果中。

product region salesperson total 对应分组 含义
A North Alice 100 (A,R,S) 明细:A 在 North 由 Alice 卖出
A South Bob 150 (A,R,S) 明细
B North Alice 200 (A,R,S) 明细
A North NULL 100 (A,R) A 在 North 的总销售额(不管谁卖)
A South NULL 150 (A,R) A 在 South 的总销售额
B North NULL 200 (B,R) B 在 North 的总销售额
A NULL Alice 100 (A,S) A 由 Alice 卖出的总额(不管地区)
A NULL Bob 150 (A,S) A 由 Bob 卖出的总额
B NULL Alice 200 (B,S) B 由 Alice 卖出的总额
NULL North Alice 300 (R,S) North 由 Alice 卖出的总额(A+B)
NULL South Bob 150 (R,S) South 由 Bob 卖出的总额
A NULL NULL 250 (A) 产品 A 的总销售额
B NULL NULL 200 (B) 产品 B 的总销售额
NULL North NULL 300 ® North 区域总销售额
NULL South NULL 150 ® South 区域总销售额
NULL NULL Alice 300 (S) Alice 的总销售额
NULL NULL Bob 150 (S) Bob 的总销售额
NULL NULL NULL 450 () 全局总计

如果要区分结果集中的 NULL 是原始值还是汇总占位符,可以使用GROUPING() 函数,示例:

sql 复制代码
SELECT
  product,
  region,
  salesperson,
  SUM(amount) AS total,
  GROUPING(product) AS g_product,
  GROUPING(region) AS g_region,
  GROUPING(salesperson) AS g_salesperson
FROM sales
GROUP BY CUBE(product, region, salesperson);
  • g_product = 1表示 product 列是被聚合掉的(显示的 NULL 是汇总标志)
  • g_product = 0 表示 product 是真实值(即使它本身是 NULL
2.4.5 WITH 子句

(1)WITH 子句

在 PostgreSQL 中,WITH 子句(也称为 公共表表达式,Common Table Expression,简称 CTE)是一种非常强大且可读性高的 SQL 结构,用于定义临时命名结果集,这些结果可以在主查询中被引用一次或多次。

WITH 子句是在多次执行子查询时特别有用,定义之后,就可以在后续的查询中通过它的名称一次或多次引用。

WITH 特点:

  • 临时性:CTE 只在当前查询中存在,不会持久化
  • 可读性高:将复杂逻辑拆分为命名步骤,类似"变量"
  • 可递归:使用 WITH RECURSIVE 实现树形/图遍历
  • 非物化(默认):PostgreSQL 默认将 CTE 作为优化器提示(可能内联),但可通过 MATERIALIZED 强制物化(PG 12+)

WITH 语法:

sql 复制代码
-- 定义一个 WITH
WITH cte_name AS (
    SELECT ...
)
SELECT ... FROM cte_name;

-- 定义多个 WITH
WITH
  cte1 AS (SELECT ...),
  cte2 AS (SELECT ... FROM cte1),
  cte3 AS (SELECT ...)
SELECT * FROM cte2 JOIN cte3 ...;

示例:

sql 复制代码
-- 常规查询
SELECT *
FROM (
    SELECT user_id, SUM(amount) AS total
    FROM orders
    WHERE created_at > '2024-01-01'
    GROUP BY user_id
) AS recent_spending
WHERE total > 1000;

-- 使用 WITH 替代
WITH recent_spending AS (
    SELECT user_id, SUM(amount) AS total
    FROM orders
    WHERE created_at > '2024-01-01'
    GROUP BY user_id
)
SELECT *
FROM recent_spending
WHERE total > 1000;

多次引用示例:

sql 复制代码
WITH top_customers AS (
    SELECT user_id, SUM(amount) AS total
    FROM orders
    GROUP BY user_id
    ORDER BY total DESC
    LIMIT 10
)
SELECT 
    'High Value' AS segment,
    COUNT(*) AS count
FROM top_customers
UNION ALL
SELECT 
    'Avg Order Value',
    AVG(total)
FROM top_customers;

(2)递归查询

递归查询 WITH RECURSIVE 用于处理树形结构(如组织架构、评论回复链、物料清单等)。

示例:

sql 复制代码
WITH RECURSIVE org_chart AS (
    -- 基础情况:CEO(无上级)
    SELECT id, name, manager_id, 1 AS level
    FROM employees
    WHERE manager_id IS NULL

    UNION ALL

    -- 递归情况:下属
    SELECT e.id, e.name, e.manager_id, oc.level + 1
    FROM employees e
    JOIN org_chart oc ON e.manager_id = oc.id
)
SELECT * FROM org_chart ORDER BY level;

(3)数据修改使用 CTE

PostgreSQL 允许在 WITH 中使用 INSERT / UPDATE / DELETE,并返回结果供后续使用(数据修改 CTE)。

示例:删除旧日志并记录删除数量

sql 复制代码
WITH deleted_logs AS (
    DELETE FROM logs
    WHERE created_at < NOW() - INTERVAL '30 days'
    RETURNING id
)
SELECT COUNT(*) AS deleted_count FROM deleted_logs;

示例:插入后立即更新

sql 复制代码
WITH new_user AS (
    INSERT INTO users (name, email)
    VALUES ('Alice', 'alice@example.com')
    RETURNING id
)
UPDATE profiles
SET status = 'active'
WHERE user_id = (SELECT id FROM new_user);

(4)强制物化 CTE

这是 PostgreSQL 12+ 的高级特性,用于避免重复计算。

sql 复制代码
WITH my_cte AS MATERIALIZED (
    SELECT expensive_function(x) AS result
    FROM large_table
)
SELECT * FROM my_cte WHERE result > 0;
  • MATERIALIZED:确保 CTE 只计算一次,结果存入临时空间
  • MATERIALIZED:PostgreSQL 可能将 CTE 内联展开(像宏一样),导致多次执行(如果引用多次)
2.4.8 DISTINCT 子句

(1)DISTINCT

在 PostgreSQL 中,DISTINCT 是一个用于去除结果集中重复行的关键字,常用于 SELECT 语句中,确保返回的结果唯一。

DISTINCT 语法如下:

sql 复制代码
SELECT DISTINCT column1, column2, ...
FROM [table_name];

示例:

sql 复制代码
select distinct sex from users; -- 男,女

select count(sex) from users; -- 10
select count(distinct sex) from users; -- 2

(2)DISTINCT ON (expression)

DISTINCT ON (expression) 是 PostgreSQL 特有的功能,用于对某列去重,但保留该组中的第一行(按指定顺序)

DISTINCT ON (expression) 语法:

sql 复制代码
SELECT DISTINCT ON (column_or_expr) 
    select_list
FROM table
ORDER BY column_or_expr [, ...];
  • 必须配合 ORDER BY,且 ORDER BY 的第一个表达式必须与 DISTINCT ON 的表达式一致。

示例:获取每个用户最近的一条订单

sql 复制代码
SELECT DISTINCT ON (user_id)
    user_id,
    order_id,
    order_date,
    total
FROM orders
ORDER BY user_id, order_date DESC;  -- 每个 user_id 组内,按时间倒序,取第一条

2.5 高级行操作

2.5.1 UPSERT

UPSERT(INSERT ON CONFLICT):实现"存在则更新,不存在则插入":

  • ON CONFLICT (column):指定冲突判断列(必须有唯一约束或主键)。
  • EXCLUDED:代表试图插入但冲突的那一行。

示例,users 表有如下数据:

users 表中新增两条数据:

sql 复制代码
INSERT INTO users(id, name, age, address, salary)
values 
	(1, 'AAA', 99, '美国', 1000),
	(100, 'BBB', 29, '英国', 2000)
ON CONFLICT (id) DO UPDATE 
SET name = EXCLUDED.name,
	age = EXCLUDED.age,
	address = EXCLUDED.address,
	salary = EXCLUDED.salary;

执行结果:

  • id=1 的数据已存在,则执行了更新
  • id=100 的数据不存在,则执行了新增
2.5.2 批量操作与事务

多个行操作通常放在事务中保证一致性:

sql 复制代码
BEGIN;
UPDATE users SET salary = salary - 100 WHERE id = 1;
UPDATE users SET salary = salary + 100 WHERE id = 2;
COMMIT;
2.5.3 行级锁

行级锁 FOR UPDATE 可以在事务中锁定特定行防止并发修改:

sql 复制代码
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 其他会话对该行的 UPDATE/DELETE 将被阻塞
2.5.4 TRUNCATE 命令

PostgreSQL 中 TRUNCATE TABLE 用于删除表的数据,但不删除表结构。

TRUNCATE TABLEDELETE 具有相同的效果,但是由于它实际上并不扫描表,所以速度更快。 此外,TRUNCATE TABLE 可以立即释放表空间,而不需要后续 VACUUM 操作,这在大型表上非常有用。

PostgreSQL VACUUM 操作用于释放、再利用更新/删除行所占据的磁盘空间。

TRUNCATE TABLE 语法如下:

sql 复制代码
TRUNCATE TABLE  table_name;
2.5.5 AUTO INCREMENT

PostgreSQL 中,AUTO INCREMENT 表示自动增长,设置了这个的列会在新记录插入表中时自动生成一个唯一的数字。

PostgreSQL 使用序列 SERIAL 来标识字段的自增长,数据类型有 smallserialserialbigserial,这些属性类似于 MySQL 数据库支持的 AUTO_INCREMENT 属性。

SERIAL 数据类型语法如下:

sql 复制代码
CREATE TABLE tablename (
   colname SERIAL
);

示例:

sql 复制代码
-- MySQL 设置自动增长
CREATE TABLE IF NOT EXISTS `test_table`(
   `id` INT UNSIGNED AUTO_INCREMENT,
   `name` VARCHAR(100) NOT NULL
   PRIMARY KEY ( `runoob_id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- PostgreSQL 使用序列来标识字段的自增长
CREATE TABLE test_table
(
    id serial NOT NULL,
    name text
)

SMALLSERIALSERIALBIGSERIAL 范围:

类型 存储大小 范围
SMALLSERIAL 2字节 1 到 32,767
SERIAL 4字节 1 到 2,147,483,647
BIGSERIAL 8字节 1 到 922,337,2036,854,775,807

三、PostgreSQL 约束

PostgreSQL 中的约束(Constraints)用于限制表中数据的类型,以保证数据的完整性和一致性。

3.1 NOT NULL

NOT NULL 用于确保列不能有 NULL 值。

默认情况下,列可以保存为 NULL 值,如果不希望某列有 NULL 值,那么需要在该列上定义此约束,指定在该列上不允许 NULL 值。

NULL 与没有数据是不一样的,它代表着未知的数据。

sql 复制代码
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

3.2 UNIQUE

UNIQUE 用于确保列(或列组合)中的所有值都是唯一的。

sql 复制代码
-- 单列唯一
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email TEXT UNIQUE
);

-- 多列唯一
CREATE TABLE user_roles (
    user_id INT,
    role_name TEXT,
    UNIQUE (user_id, role_name)
);

3.3 PRIMARY KEY

PRIMARY KEY 是表的主键,是数据表中每一条记录的唯一标识。如果一个表在任何字段上定义了一个主键,那么在这些字段上不能有两个记录具有相同的值。

PRIMARY KEY = NOT NULL + UNIQUE,用于唯一标识表中的每一行。

每个表只能有一个主键,它可以由一个或多个字段组成,当多个字段作为主键,它们被称为复合键。

sql 复制代码
CREATE TABLE products (
    product_id SERIAL PRIMARY KEY,
    name TEXT
);

3.4 FOREIGN KEY

FOREIGN KEY 是表的外键,外键用于建立和强制两个表之间的引用完整性。

子表中的外键值必须在父表的主键(或唯一键)中存在,或者为 NULL(除非同时声明 NOT NULL)。

sql 复制代码
CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    product_id INT REFERENCES products(product_id)
);

也可以显式命名外键约束:

sql 复制代码
CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    product_id INT,
    CONSTRAINT fk_product
        FOREIGN KEY (product_id)
        REFERENCES products(product_id)
        ON DELETE CASCADE
);

支持的操作选项:

  • ON DELETE / ON UPDATE:

    • NO ACTION(默认)
    • RESTRICT
    • CASCADE
    • SET NULL
    • SET DEFAULT

3.5 CHECK

CHECK 约束用于保证列中的所有值满足指定取值范围或满足某一条件,即对输入一条记录要进行检查。如果条件值为 false,则记录违反了约束,且不能输入到表。

sql 复制代码
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    age INT CHECK (age >= 18),
    salary NUMERIC CHECK (salary > 0)
);

3.6 EXCLUSION

排他约束(Exclusion Constraint)是 PostgreSQL 特有的高级约束,用于确保任意两行在指定操作符下不"重叠",即确保如果使用指定的运算符在指定列或表达式上比较任意两行,至少其中一个运算符比较将返回 falsenull

通常与 btree_gist btree_gin 扩展一起使用。

sql 复制代码
-- 示例:确保同一房间在同一时间段内不能被重复预订
CREATE EXTENSION IF NOT EXISTS btree_gist;

CREATE TABLE reservations (
    room TEXT,
    during TSRANGE,
    EXCLUDE USING GIST (room WITH =, during WITH &&)
);

3.7 查看约束信息

可通过系统目录查看约束

sql 复制代码
SELECT conname AS constraint_name,
       contype AS constraint_type,
       conrelid::regclass AS table_name
FROM pg_constraint
WHERE conrelid = 'users'::regclass;
  • contype 含义:

    • p = PRIMARY KEY
    • u = UNIQUE
    • f = FOREIGN KEY
    • c = CHECK
    • x = EXCLUDE

3.8 添加/删除约束

添加约束语法:

sql 复制代码
ALTER TABLE [table_name]
ADD CONSTRAINT [some_name] UNIQUE ([cloumn_name]);

删除约束语法:

sql 复制代码
ALTER TABLE [table_name] 
DROP CONSTRAINT [some_name];

第三章 PostgreSQL 进阶

一、PostgreSQL 进阶查询

1.1 PostgreSQL JOIN

在 PostgreSQL 中,JOIN 用于根据两个或多个表之间的相关列组合数据,它是关系型数据库中实现表关联查询的核心操作。

常见的 JOIN 类型如下:

  • CROSS JOIN :交叉连接
  • INNER JOIN:内连接
  • LEFT OUTER JOIN:左外连接
  • RIGHT OUTER JOIN:右外连接
  • FULL OUTER JOIN:全外连接

假设有 users 表和 users_info 表,如下:

两个表的数据分别如下:

1.1.1 INNER JOIN

INNER JOIN:内连接,只返回两个表中匹配的行。用于联合查询几个表的数据,只有当几个表都满足连接条件才符合,并自动结合成一条数据。

INNER 关键字是可选的,不写 INNER 时,默认为 INNER JOIN,即 JOIN = INNER JOIN

INNER JOIN 的语法如下:

sql 复制代码
SELECT table1.column1, table2.column2...
FROM table1
INNER JOIN table2
ON table1.common_filed = table2.common_field;

INNER JOIN 的结果集可以表示为 R

示例:

sql 复制代码
SELECT users.*, users_info.*
FROM users
INNER JOIN users_info
ON users.id = users_info.user_id;

查询结果:

关联的表中如果有多条数据,则多条数据都会被查询除来,如 users 表中的 id=5 的数据在 users_info 表中存在两条数据,则这两条数据都会关联查询到,但是对应的 users 表的字段的值是一样的。

1.1.2 LEFT OUTER JOIN

LEFT OUTER JOIN 表示左外连接,即 LEFT OUTER JOIN 左边的表为主表,右边的表为副表。查询的时候会以主表为主,主表返回所有数据行,主表的数据在副表中存在则有值,不存在则为 NULL

LEFT OUTER JOIN 也用 LEFT JOIN 表示,OUTER 可以省略。

LEFT OUTER JOIN 的语法如下:

sql 复制代码
SELECT ... FROM table1 
LEFT OUTER JOIN table2 ON conditional_expression ...

LEFT OUTER JOIN 的结果集可以表示为 A+R

示例:

sql 复制代码
SELECT users.*, users_info.*
FROM users
LEFT JOIN users_info
ON users.id = users_info.user_id;

查询结果:

主表(左边的表)返回了所有的数据,主表中 id=2id=4 的数据在副表中不存在,则附表对应的字段为 NULL

1.1.3 RIGHT OUTER JOIN

RIGHT OUTER JOIN 表示右外连接,与 LEFT OUTER JOIN 的左右相反,即 RIGHT OUTER JOIN 右边的表为主表,左边的表为副表。查询的时候会以右边的表为主,主表返回所有数据行,主表的数据在副表中存在则有值,不存在则为 NULL

RIGHT OUTER JOIN 也用 RIGH TJOIN 表示,OUTER 可以省略。

RIGHT OUTER JOIN 的语法如下:

sql 复制代码
SELECT ... FROM table1 
RIGHT OUTER JOIN table2 ON conditional_expression ...

RIGHT OUTER JOIN 的结果集可以表示为 B+R

示例:

sql 复制代码
SELECT users.*, users_info.*
FROM users
RIGHT JOIN users_info
ON users.id = users_info.user_id;

查询结果:

主表(右边的表)返回了所有的数据,主表中 id=4id=5 的数据在副表中不存在,则附表对应的字段为 NULL

1.1.4 FULL OUTER JOIN

FULL OUTER JOIN 表示全外连接,查询的时候会返回两个表的所有行,不匹配的部分用 NULL 填充。。

FULL OUTER JOIN 也用 FULL JOIN 表示,OUTER 可以省略。

FULL OUTER JOIN 的语法如下:

sql 复制代码
SELECT ... FROM table1 
FULL OUTER JOIN table2 ON conditional_expression ...

FULL OUTER JOIN 的结果集可以表示为 A+B+R

示例:

sql 复制代码
SELECT users.*, users_info.*
FROM users
FULL JOIN users_info
ON users.id = users_info.user_id;

查询结果:

1.1.5 CROSS JOIN

CROSS JOIN 表示笛卡尔积,查询时会返回两个表的所有可能的组合,等价于无条件连接表查询。

CROSS JOIN 的语法如下:

sql 复制代码
SELECT ... FROM table1 
CROSS JOIN table2

-- 等价于
SELECT ... FROM table1, table2

示例:

sql 复制代码
SELECT users.*, users_info.*
FROM users
CROSS JOIN users_info;

-- 等价于
SELECT users.*, users_info.*
FROM users, users_info;

查询结果:

users 表有5条数据,users_info 表有6条数据,则结果集有30条数据。

1.1.6 USING vs ON

当多个表连接查询,连接的列名相同时,可使用 USING 简化,语法如下:

sql 复制代码
-- 使用 ON
SELECT * FROM orders
JOIN customers ON orders.customer_id = customers.customer_id;

-- 使用 USING
SELECT * FROM orders
JOIN customers USING (customer_id);

1.2 PostgreSQL UNION

1.2.1 UNION 介绍

在 PostgreSQL 中,UNION 是一种用于合并多个 SELECT 查询结果集的操作符。

UNION 语法如下:

sql 复制代码
SELECT column1, column2, ... FROM table1
UNION [ALL]
SELECT column1, column2, ... FROM table2
[UNION [ALL] SELECT ...]
[ORDER BY ...];

UNION 要求每个 SELECT 语句的列数必须相同,且对应列的数据类型必须兼容(PostgreSQL 会尝试自动转换),结果集的列名会以第一个 SELECT 的列名为准。

UNION vs UNION ALL

特性 UNION UNION ALL
去重 自动去除重复行 保留所有行(包括重复)
性能 较慢(需排序去重) 更快(直接拼接)
使用场景 需要唯一结果集 允许重复,或已知无重复
1.2.2 UNION 排序

使用 UNION 查询时,不能对单个 SELECT 子句排序(除非用子查询),ORDER BY 必须放在整个 UNION 语句的末尾。

sql 复制代码
SELECT colunm_name FROM table1
UNION
SELECT colunm_name FROM table2
ORDER BY colunm_name;

如果需要对子查询排序(通常用于 LIMIT),需用子查询包裹:

sql 复制代码
SELECT * FROM (
    (SELECT colunm_name FROM table1 ORDER BY colunm_name DESC LIMIT 5)
    UNION ALL
    (SELECT colunm_name FROM table2 ORDER BY colunm_name DESC LIMIT 5)
) AS table_new
ORDER BY colunm_name;
1.2.3 INTERSECT / EXCEPT

PostgreSQL 还支持集合操作:

  • INTERSECT:取两个结果集的交集(默认去重)
  • EXCEPT:取第一个结果集减去第二个(差集)

INTERSECT / EXCEPTUNION 具有相同的数据结构要求,并遵循类似的去重规则(可用 ALL 变体)。

示例:

sql 复制代码
SELECT id FROM table_a
EXCEPT
SELECT id FROM table_b;  -- 在 a 中但不在 b 中的 id
1.2.4 UNION 注意事项

(1)NULL 值处理

UNION 会将两个 NULL 视为"相等",因此会被去重。

sql 复制代码
SELECT NULL::INT
UNION
SELECT NULL::INT;
-- 结果:只有一行 NULL

(2)性能影响

UNION 需要临时排序(类似 DISTINCT),大数据量时较慢,如果确定无重复数据,优先用 UNION ALL

(3)使用 LIMIT / OFFSET

不能直接在 UNION 中使用 LIMIT / OFFSET,除非包裹在子查询中。


二、PostgreSQL 函数

2.1 函数介绍

PostgreSQL 的函数(Functions)是数据库编程的核心能力之一,它允许你将复杂的逻辑封装成可重用的代码单元,在 SQL 查询、触发器、存储过程、应用调用等场景中高效执行。

函数必须有返回值,可用于 SELECT, WHERE, FROMSQL 中,函数不能执行数据库事务控制。

函数的语法如下:

sql 复制代码
CREATE [ OR REPLACE ] FUNCTION name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
    [ RETURNS rettype
      | RETURNS TABLE ( column_name column_type [, ...] ) 
	]
{ LANGUAGE lang_name
	| TRANSFORM { FOR TYPE type_name } [, ... ]
	| WINDOW
	| { IMMUTABLE | STABLE | VOLATILE }
	| { LEAKPROOF | NOT LEAKPROOF }
	| { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT }
	| { SECURITY INVOKER | SECURITY DEFINER }
	| PARALLEL { UNSAFE | RESTRICTED | SAFE }
	| COST execution_cost
	| ROWS result_rows
	| SET configuration_parameter { TO value | = value | FROM CURRENT }
	| AS 'definition'
	| AS 'obj_file', 'link_symbol'
} ...
$$
-- 函数体(通常用 $$...$$ 或 $tag$...$tag$ 包裹)
$$

语法说明:

  • 方括号 [...] 表示可选,花括号 {...} 表示必选其一,... 表示可重复。

  • CREATE [OR REPLACE] FUNCTION name (...):创建一个函数

    • OR REPLACE:如果函数已存在,则替换(保留权限和依赖关系)。

    • 函数名:可带 schema,如 my_schema.my_func

    • 重载:PostgreSQL 支持同名函数,只要参数类型不同(数量或类型)。

      sql 复制代码
      CREATE OR REPLACE FUNCTION add(a INT, b INT) RETURNS INT ...;
      CREATE OR REPLACE FUNCTION add(a NUMERIC, b NUMERIC) RETURNS NUMERIC ...; -- 重载
  • ([argmode] [argname] argtype [= default_expr]):函数的参数类型。

    • argmode:参数模式

      • IN:输入参数,这是函数默认的参数模式,所以可省略不写

      • OUT:输出参数(函数返回多值)

        sql 复制代码
        CREATE FUNCTION get_user_info(uid INT, 
                                      OUT name TEXT, 
                                      OUT email TEXT, 
                                      OUT age INT)
        AS $$
            SELECT u.name, u.email, u.age 
            FROM users u 
            WHERE u.id = uid;
        $$ LANGUAGE sql;
        
        -- 调用
        SELECT * FROM get_user_info(100);
        -- 或
        SELECT (get_user_info(100)).name;
      • INOUT:输入+输出

      • VARIADIC:可变参数(数组形式)

        sql 复制代码
        CREATE FUNCTION sum_all(VARIADIC nums INT[])
        RETURNS INT AS $$
        DECLARE
            total INT := 0;
        BEGIN
            FOREACH n IN ARRAY nums LOOP
                total := total + n;
            END LOOP;
            RETURN total;
        END;
        $$ LANGUAGE plpgsql;
        
        SELECT sum_all(1, 2, 3, 4);  -- 10
    • argname:参数名,可选,但建议命名以提高可读性,在函数体内通过名称引用(PL/pgSQL)。

    • argtype:参数类型,必须指定(如 INTTEXTTIMESTAMPTZusers%ROWTYPE 等),支持复合类型、域类型、数组(INT[])。

    • DEFAULT 或 =:参数的默认值

      sql 复制代码
      CREATE FUNCTION greet(name TEXT DEFAULT 'Guest')
      RETURNS TEXT AS $$
      BEGIN
          RETURN 'Hello, ' || name || '!';
      END;
      $$ LANGUAGE plpgsql;
      
      SELECT greet();           -- Hello, Guest!
      SELECT greet('Alice');    -- Hello, Alice!
    • 特殊类型:ANYELEMENTANYARRAYANYENUM 用于泛型函数(需配合 RETURNS 使用)。

  • RETURNS ...:函数的返回类型。

    • 标量返回:如 RETURNS INTEGERRETURNS TEXTRETURNS my_domain_type

      sql 复制代码
      RETURNS INTEGER
      RETURNS TEXT
      RETURNS NUMERIC(10,2)
    • 表返回(多列):如 RETURNS TABLE(id INT, name TEXT),需要函数体内用 RETURN QUERYRETURN NEXT

      sql 复制代码
      RETURNS TABLE(id INT, name TEXT)
      
      -- 示例
      CREATE FUNCTION active_users()
      RETURNS TABLE(id INT, name TEXT) AS $$
      BEGIN
          RETURN QUERY SELECT u.id, u.name FROM users u WHERE u.active;
      END;
      $$ LANGUAGE plpgsql;
      
      -- 调用
      SELECT * FROM active_users();
    • 集合返回(SETOF):

      sql 复制代码
      RETURNS SETOF users
      
      -- 示例
      CREATE FUNCTION top_customers(limit_count INT)
      RETURNS SETOF customers AS $$
      BEGIN
          RETURN QUERY 
          SELECT * FROM customers 
          ORDER BY total_spent DESC 
          LIMIT limit_count;
      END;
      $$ LANGUAGE plpgsql;
    • 无返回:RETURNS VOID

    • RETURNS TABLE(...)OUT 参数的语法糖,RETURNS SETOFRETURNS TABLE 都可用于 SELECT * FROM func()

  • LANGUAGE lang_name:指定函数体使用的语言,支持的语言如下:

    • sql:纯 SQL,单条语句(可多条用 ; 分隔)
    • plpgsql:过程语言,支持变量、控制流、异常
    • internal:C 内置函数(如 upper
    • c:外部 C 函数(需编译 .so 文件)
    • plpython3uplv8 等扩展语言
    sql 复制代码
    CREATE FUNCTION square(x INT) RETURNS INT
    LANGUAGE sql
    AS $$ SELECT x * x; $$;
  • TRANSFORM { FOR TYPE type_name }:为特定数据类型注册 I/O 转换规则,这是比较高级的用法,通常用于外部语言如 Python。

  • WINDOW:表示该函数可用作窗口函数,如 row_number(),这个仅适用于用 C 编写的函数,SQL / PLpgSQL 无法创建窗口函数。

  • { IMMUTABLE | STABLE | VOLATILE }:函数稳定性,用于决定优化器如何处理,正确设置可极大提升性能

    • IMMUTABLE:对相同输入永远返回相同结果,且不访问数据库,可用于缓存和索引
    • STABLE:单次查询中结果不变(如 now()),单次查询内可以用缓存,但不可用于索引
    • VOLATILE:默认值,结果可能随时变,或有副作用(如 random()),不可用于缓存和索引
    sql 复制代码
    -- 错误示例(危险!):
    CREATE FUNCTION get_user_name_bad(id INT)
    RETURNS TEXT
    IMMUTABLE  -- ❌ 错误!会访问数据库
    AS $$ SELECT name FROM users WHERE id = $1; $$ LANGUAGE sql;
    
    -- 正确应为:
    STABLE
  • { LEAKPROOF | NOT LEAKPROOF }:表示函数是否会泄露参数信息,默认 NOT LEAKPROOF(会泄露参数信息)。LEAKPROOF 表示函数不会泄露参数信息(即使执行失败),用于高安全场景(如 RLS 策略中的函数),普通函数无需设置。

  • { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT }:对空参数值的处理,对纯计算函数推荐使用 STRICT 提升性能

    • CALLED ON NULL INPUT:这是默认值,表示即使参数为 NULL 也调用函数
    • RETURNS NULL ON NULL INPUTSTRICT:任一参数为 NULL 时,直接返回 NULL,不执行函数体
    sql 复制代码
    CREATE FUNCTION safe_div(a NUMERIC, b NUMERIC)
    RETURNS NUMERIC
    STRICT  -- 若 a 或 b 为 NULL,直接返回 NULL
    AS $$ SELECT a / b; $$ LANGUAGE sql;
  • { SECURITY INVOKER | SECURITY DEFINER }:安全上下文。

    • SECURITY INVOKER:默认的方式,以调用者权限执行

    • SECURITY DEFINER:以函数定义者权限执行(类似sudo),使用 SECURITY DEFINER 时,必须防止 search_path 攻击

      sql 复制代码
      CREATE FUNCTION dangerous()
      RETURNS VOID
      SECURITY DEFINER
      SET search_path = pg_catalog, pg_temp  -- 关键防护!
      AS $$ ... $$ LANGUAGE plpgsql;
  • PARALLEL { UNSAFE | RESTRICTED | SAFE }:并行安全性(PG ≥10),用于 WHERE / JOIN 的函数若标记为 SAFE,可加速并行查询。

    • PARALLEL UNSAFE:默认的方式,不能在并行 worker 中运行
    • PARALLEL RESTRICTED:可在并行 worker 中运行,但不能写数据
    • PARALLEL SAFE:完全安全,可并行
  • COST execution_cost:估算函数的执行代价(单位:cpu_operator_cost,默认 1),方便优化器用于选择执行计划。ql 函数默认值为100,plpgsql 函数默认值为1000。

    sql 复制代码
    CREATE FUNCTION fast_hash(x TEXT) RETURNS BYTEA
    COST 10  -- 告诉优化器这个函数很快
    ...
  • ROWS result_rows:估算返回的行数(默认 1000),仅用于 SETOFTABLE 返回的函数,改配置会影响连接顺序和内存分配。

    sql 复制代码
    CREATE FUNCTION get_active_users()
    RETURNS SETOF users
    ROWS 50  -- 预计只返回约 50 行
    ...
  • SET configuration_parameter { TO value | = value | FROM CURRENT }:临时修改 GUC 参数,这个修改仅在函数内生效

    sql 复制代码
    CREATE FUNCTION use_english()
    RETURNS TEXT
    SET lc_messages = 'en_US.UTF-8'
    AS $$ ... $$ LANGUAGE plpgsql;
    • 常用于 search_pathtimezoneclient_min_messages 等场景。
  • AS ...:函数体定义

    • AS 'definition':内联定义,最常见的使用方式:

      sql 复制代码
      AS $$ 
      BEGIN 
          RETURN x + y; 
      END; 
      $$
    • AS 'obj_file', 'link_symbol':外部文件,如AS '/path/to/lib.so', 'func_symbol'

2.2 函数操作

2.2.1 创建函数

创建一个函数:计算阶乘

sql 复制代码
CREATE FUNCTION factorial(n INT)
RETURNS INT
IMMUTABLE
AS $$
BEGIN
    IF n <= 1 THEN
        RETURN 1;
    ELSE
        RETURN n * factorial(n - 1);
    END IF;
END;
$$ LANGUAGE plpgsql;
2.2.2 查看函数

PostgreSQL 中,查看已有的函数可以通过下面的方式

sql 复制代码
-- 列出所有函数
\df

-- 查看函数定义
\df+ function_name

示例:

还可以通过查询系统表来查看函数:

sql 复制代码
SELECT proname, prosrc, probin, provolatile, proparallel
FROM pg_proc
WHERE pronamespace = 'public'::regnamespace;

结果:

2.2.3 使用函数

使用已经创建的函数,如前面创建的阶乘函数:

sql 复制代码
SELECT factorial(5)

结果:

2.2.4 删除函数

删除函数的语法如下:

sql 复制代码
DROP FUNCTION IF EXISTS function_name(
    [arg_name] arg_type [= default_value],
    ...
);

删除函数需指定参数类型,因为函数可以重载。

示例:


三、PostgreSQL 存储过程

3.1 存储过程介绍

PostgreSQL 从版本 11(2018年发布) 开始正式支持存储过程(Stored Procedures),通过 CREATE PROCEDURE 语句实现。

存储过程的语法如下:

sql 复制代码
CREATE [ OR REPLACE ] PROCEDURE name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
{ LANGUAGE lang_name
	| TRANSFORM { FOR TYPE type_name } [, ... ]
	| { IMMUTABLE | STABLE | VOLATILE }
	| { LEAKPROOF | NOT LEAKPROOF }
	| { CALLED ON NULL INPUT | STRICT }
	| { SECURITY INVOKER | SECURITY DEFINER }
	| PARALLEL { UNSAFE | RESTRICTED | SAFE }
	| COST execution_cost
	| SET configuration_parameter { TO value | = value | FROM CURRENT }
	| AS 'definition'
	| AS 'obj_file', 'link_symbol'
} ...
$$
-- 过程体(通常用 $$...$$ 包裹)
$$

语法详解:

  • CREATE [ OR REPLACE ] PROCEDURE name ():创建语句

    • OR REPLACE:如果同名过程已存在,则替换它(保留权限和依赖关系)。
    • name:存储过程的名字,可以携带 schema,如 admin.cleanup_logs。存储过程支持重载,只要参数类型不同(数量或类型)。
    sql 复制代码
    CREATE PROCEDURE log_event(msg TEXT);
    CREATE PROCEDURE log_event(msg TEXT, level TEXT DEFAULT 'INFO'); -- 重载
  • [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ]:存储过程的参数列表

    • argmode:参数模式

      • IN:输入参数,默认的模式,所以可省略不写
      • OUT:输出参数(调用后可获取值)
      • INOUT:输入+输出
      • VARIADIC:可变参数(转为数组)
    • argtype:参数类型,必须指定(如 INTTEXT[]my_table%ROWTYPE)。

    sql 复制代码
    CREATE PROCEDURE notify_user(
        user_id INT,
        message TEXT,
        channel TEXT DEFAULT 'email'
    )
    ...
  • LANGUAGE lang_name:指定过程体使用的语言

    • plpgsql:最常用,支持变量、控制流、异常、事务控制
    • sql:仅支持简单 SQL 语句(不支持 COMMIT / ROLLBACK
    • plpython3u:等外部语言(需扩展)
  • TRANSFORM { FOR TYPE type_name } [, ... ]:表示过程是否会泄露参数信息,默认 NOT LEAKPROOF(会泄露参数信息)。LEAKPROOF 表示过程不会泄露参数信息(即使执行失败),用于高安全场景(如 RLS 策略中的函数),普通过程无需设置。

  • { IMMUTABLE | STABLE | VOLATILE }:行为属性

    • IMMUTABLE:结果永不随时间/数据变化,适用于仅纯计算(但过程通常修改数据,故极少用)
    • STABLE:单次查询中结果不变,过程不用于 SELECT
    • VOLATILE:默认的行为属性,结果可能变化,或有副作用,绝大多数存储过程应为 VOLATILE
  • { LEAKPROOF | NOT LEAKPROOF }

  • { CALLED ON NULL INPUT | STRICT }:空值处理

    • CALLED ON NULL INPUT:默认的空值处理方式,即使参数为 NULL 也执行
    • STRICT:任一 IN 参数为 NULL 时,跳过执行(但 OUT 参数仍会被赋值为 NULL
  • { SECURITY INVOKER | SECURITY DEFINER }

    • SECURITY INVOKER:默认的方式,以调用者权限执行
    • SECURITY DEFINER:以过程定义者权限执行(类似sudo),使用 SECURITY DEFINER 时,必须防止 search_path 攻击
    sql 复制代码
    CREATE PROCEDURE safe_drop()
    SECURITY DEFINER
    SET search_path = pg_catalog, pg_temp
    LANGUAGE plpgsql
    AS $$
    BEGIN
        DROP TABLE IF EXISTS temp_data;
        COMMIT;
    END;
    $$;
  • PARALLEL { UNSAFE | RESTRICTED | SAFE }:并行安全性(PG ≥10),用于 WHERE / JOIN 的过程若标记为 SAFE,可加速并行查询。

    • PARALLEL UNSAFE:默认的方式,不能在并行 worker 中运行
    • PARALLEL RESTRICTED:可在并行 worker 中运行,但不能写数据
    • PARALLEL SAFE:完全安全,可并行
  • COST execution_cost:估算过程的执行代价(单位:cpu_operator_cost,默认 1),方便优化器用于选择执行计划。ql 函数默认值为100,plpgsql 函数默认值为1000。

  • SET configuration_parameter { TO value | = value | FROM CURRENT }:临时修改 GUC 参数,这个修改仅在过程内生效

    sql 复制代码
    CREATE PROCEDURE set_timezone()
    SET timezone = 'UTC'
    LANGUAGE plpgsql
    AS $$
    BEGIN
        INSERT INTO logs(event_time) VALUES (NOW()); -- 使用 UTC
        COMMIT;
    END;
    $$;
  • AS ...:过程体定义

    • AS 'definition':内联定义,最常见的使用方式:
    • AS 'obj_file', 'link_symbol':外部文件,如AS '/path/to/lib.so', 'func_symbol'
  • $$ ... $$:过程体

    • 必须是字符串字面量,推荐 $$ ... $$ 避免引号转义

    • plpgsql 中,可包含:

      • 变量声明(DECLARE
      • 控制结构(IFLOOPFOR
      • SQL 语句(INSERTUPDATE 等)
      • 事务命令:COMMITROLLBACKSAVEPOINT

示例:

sql 复制代码
CREATE PROCEDURE batch_process()
LANGUAGE plpgsql
AS $$
BEGIN
    -- 第一批处理
    UPDATE orders SET status = 'processed' WHERE batch = 1;
    COMMIT;  -- ✅ 允许!

    -- 第二批处理
    UPDATE orders SET status = 'processed' WHERE batch = 2;
    COMMIT;
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
END;
$$;

存储过程与传统的 函数(Function) 有本质区别,尤其在事务控制和调用方式上:

特性 存储过程(Procedure) 函数(Function)
创建语句 CREATE PROCEDURE CREATE FUNCTION
返回值 不能直接返回值(但可通过 INOUT/OUT 参数传递) 必须声明 RETURNS
调用方式 CALL procedure_name(...) 可在 SQL 中直接调用(如 SELECT func()
事务控制 支持 COMMITROLLBACKSAVEPOINT 不允许(函数必须在单一事务中)
使用场景 批处理、ETL、复杂业务流程、需要分步提交的操作 计算、转换、触发器、SQL 表达式中使用

3.2 存储过程操作

3.2.1 创建存储过程

创建一个存储过程,在存储过程中修改指定用户的名称

sql 复制代码
CREATE PROCEDURE update_username(user_id INT)
LANGUAGE plpgsql
AS $$
BEGIN
    UPDATE users SET name = '666' WHERE id = user_id;
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
END;
$$;
3.2.2 查看存储过程

通过以下命令可以查看已存在的存储过程

  • \dfp:列出所有存储过程
  • \dfp+ proc_name:查看存储过程的定义

示例:

也可以通过查询系统表来查看存储过程

sql 复制代码
SELECT * FROM pg_proc WHERE prokind = 'p';

示例:

3.2.3 使用存储过程

存储过程调用语法:

sql 复制代码
CALL procedure_name(arg1, arg2, ..., argN);

说明:

  • 必须使用 CALL,不能在 SELECT 中调用

示例:

sql 复制代码
CALL update_username(2);
3.2.4 删除存储过程

存储过程删除语法:

sql 复制代码
DROP PROCEDURE IF EXISTS procedure_name();

示例:


四、PostgreSQL 触发器

4.1 触发器介绍

在 PostgreSQL 中的触发器(Trigger)是一种特殊的数据库对象,触发器不是独立执行的 SQL 语句,而是一种事件驱动机制,当对表(或视图)执行 INSERTUPDATEDELETETRUNCATE 操作时,数据库引擎会自动调用与该事件绑定的触发器函数,该函数可以访问操作前后的数据(OLD / NEW),并决定是否允许操作继续、修改数据、记录日志等。

触发器的分类:

  • 按触发时机分:

    • BEFORE:在数据写入前触发(可修改 NEW,可取消操作)
    • AFTER:在数据写入且约束检查通过后触发(不能修改数据)
    • INSTEAD OF:仅用于视图,替代原操作(实现可更新视图)
  • 按触发粒度分

    • FOR EACH ROW:每行触发一次(可访问 OLD / NEW
    • FOR EACH STATEMENT:整条 SQL 语句触发一次(不可访问 OLD / NEW
  • 特殊类型:事件触发器

    • 监听 DDL 事件(如 CREATE TABLEDROP INDEX
    • 不绑定到具体表,而是全局生效
    • 使用 CREATE EVENT TRIGGER(本文聚焦普通触发器)

触发器的创建语法:

sql 复制代码
CREATE  TRIGGER trigger_name [BEFORE|AFTER|INSTEAD OF] event_name
ON table_name
[
 -- 触发器逻辑....
];

语法详解:

  • CREATE [ CONSTRAINT ] TRIGGER trigger_name:创建触发器

    • trigger_name:触发器名称,不能重复
    • CONSTRAINT:用于创建约束触发器(Constraint Trigger),支持延迟检查(DEFERRABLE),常用于实现跨行/跨表业务规则(如预算控制)
    sql 复制代码
    CREATE CONSTRAINT TRIGGER check_total_expense
    AFTER INSERT OR UPDATE ON expenses
    DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW
    EXECUTE FUNCTION validate_budget();
  • { BEFORE | AFTER | INSTEAD OF }:触发器的触发时机

    • BEFORE:在行写入前触发,可修改数据 NEW,常用于数据校验、默认值填充。BEFORE 返回 NULL 则取消当前行操作
    • AFTER:在行写入且约束通过后触发,不可修改数据,常用于审计日志、通知、缓存失效
    • INSTEAD OF:仅用于视图,替代原操作, 可以自定义逻辑,常用于实现可更新视图
  • { event [ OR ... ] }:事件,支持以下事件组合

    • INSERT:用于插入,BEFOREAFTERINSTEAD OF 都可以触发
    • UPDATE [ OF column\_name [, ...] ]:用于更新(可指定列),BEFOREAFTERINSTEAD OF 都可以触发
    • DELETE:用于删除,BEFOREAFTERINSTEAD OF 都可以触发
    • TRUNCATE:用于清空表(DDL 操作),仅 AFTER + STATEMENT 配置时触发
    sql 复制代码
    CREATE TRIGGER tr_salary_change
    AFTER UPDATE OF salary, bonus ON employees
    FOR EACH ROW
    EXECUTE FUNCTION log_compensation_change();
    -- 仅当 salary 或 bonus 被更新时才触发
  • ON table_name:必须指定目标表(或 可更新视图),指定后触发器属于该表的元数据,如果是分区表则需在每个子分区上单独创建(父表触发器不继承)

  • [ FROM referenced_table_name ]:仅用于外键相关的约束触发器,通常由系统自动生成,用户极少手动使用,如当引用表被删除时,触发级联操作

  • [ NOT DEFERRABLE | [ DEFERRABLE ] { INITIALLY IMMEDIATE | INITIALLY DEFERRED } ]:仅当配置使用 CONSTRAINT TRIGGER 时有效

    • NOT DEFERRABLE:触发器默认的方式,在语句结束时立即检查
    • DEFERRABLE INITIALLY IMMEDIATE:默认是立即检查,但可临时延迟检查
    • DEFERRABLE INITIALLY DEFERRED:默认是延迟到事务提交时检查
  • [ FOR [ EACH ] { ROW | STATEMENT } ]:触发器触发的频率

    • FOR EACH ROW:每行数据被修改时触发一次,可用变量为 OLD / NEW,性能开销较高,常用于行级校验、审计
    • FOR EACH STATEMENT:每条 SQL 执行时触发一次,性能开销低,常用于发送通知、清理全局状态
  • [ WHEN ( condition ) ]:仅行级触发器支持。

    • 条件为 boolean 表达式,可引用 OLDNEW、表列
    • 避免函数调用,会影响性能
    sql 复制代码
    -- 高效示例:
    WHEN (OLD.status IS DISTINCT FROM NEW.status)
    -- 比 WHEN (OLD.status != NEW.status) 更安全(处理 NULL)
    
    -- 低效示例:
    WHEN (expensive_function(NEW.id))  -- 每行都调用!
  • EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments )

    • 必须是触发器函数,不能是普通函数或存储过程(尽管语法允许 PROCEDURE,但实际必须返回 TRIGGER),函数必须声明为 RETURNS TRIGGER
    • 参数传递:参数在函数中通过 TG_ARGV 数组访问(TEXT[] 类型),索引从 0 开始
    sql 复制代码
    -- 创建函数
    CREATE FUNCTION log_change(table_name TEXT, action TEXT)
    RETURNS TRIGGER AS $$
    BEGIN
        RAISE NOTICE 'Table: %, Action: %', TG_ARGV[0], TG_ARGV[1];
        RETURN NEW;
    END;
    $$ LANGUAGE plpgsql;
    
    -- 创建触发器
    CREATE TRIGGER tr_example
    BEFORE UPDATE ON users
    FOR EACH ROW
    EXECUTE FUNCTION log_change('users', 'update');
    -- TG_ARGV[0] = 'users', TG_ARGV[1] = 'update'

触发器执行顺序规则:

  • 同一表上的多个触发器:

    • 所有 BEFORE 先于 AFTER
    • 同类触发器按创建顺序执行
    • 可通过 ALTER TRIGGER name DEPENDS ON other_trigger 调整依赖
  • 与约束的顺序:BEFORE 触发器 → 行约束(CHECK/NOT NULL) → 唯一/外键约束 → AFTER 触发器

  • 递归触发:默认允许递归触发(可能无限循环),可以用 pg_trigger_depth() 限制:IF pg_trigger_depth() > 1 THEN RETURN NEW; END IF;

4.2 触发器操作

4.2.1 创建触发器

创建一个触发器:当 users 每新增一条数据吗,就往 users_info 表插入一条用的数据

sql 复制代码
-- 创建触发器函数
CREATE OR REPLACE FUNCTION sync_user_to_info()
RETURNS TRIGGER AS $$
DECLARE
    random_id INT4;
BEGIN
	-- 生成 1 到 2147483647 之间的随机 int4
    random_id := floor(random() * 2147483647 + 1)::INT4;

    INSERT INTO users_info (id, user_id)
    VALUES (random_id , NEW.id);
    
    RETURN NULL;  -- AFTER 触发器通常返回 NULL
END;
$$ LANGUAGE plpgsql;

NEW 代表刚插入的行,因为是 AFTER INSERT,此时 NEW.id 已经生成(即使是 SERIAL)。

创建触发器

sql 复制代码
CREATE TRIGGER tr_sync_users_to_info
AFTER INSERT ON users
FOR EACH ROW
EXECUTE FUNCTION sync_user_to_info();
4.2.2 查看触发器

通过命令 \d + table_name 可以查看表上所有触发器

示例:

通过查询触发器表列出所有触发器

sql 复制代码
SELECT tgname, tgenabled FROM pg_trigger;

tgenabled 字段含义:

  • O = Origin(默认,主库执行)
  • D = Disabled
  • R = Replica(仅在逻辑复制从库执行)
  • A = Always(主从都执行)

示例:

4.2.3 使用触发器

这是目前 users 表和 users_info 表的数据:

现在往 users 表中新增一条数据:

sql 复制代码
INSERT INTO users (id,username) VALUES (6,'周树人');

新增成功后,users 表和 users_info 表的数据:

4.2.4 启用/禁用触发器

禁用触发器:

sql 复制代码
ALTER TABLE tbl DISABLE TRIGGER tr_name;

启用触发器:

sql 复制代码
ALTER TABLE tbl ENABLE TRIGGER tr_name;

永久启用触发器:

sql 复制代码
ALTER TABLE tbl ENABLE ALWAYS TRIGGER tr_name;

示例:

sql 复制代码
-- 禁用触发器
ALTER TABLE users DISABLE TRIGGER tr_sync_users_to_info;

-- 启用触发器
ALTER TABLE users ENABLE TRIGGER tr_sync_users_to_info;

-- 永久启用触发器
ALTER TABLE users ENABLE ALWAYS TRIGGER tr_sync_users_to_info;
4.2.5 删除触发器

删除触发器语法:

sql 复制代码
DROP TRIGGER [IF EXISTS] tr_name ON table_name;

示例:


五、PostgreSQL 视图

5.1 视图介绍

在PostgreSQL中,视图(View)是一种虚拟表,其内容由查询定义。与普通表不同的是,视图本身不存储数据;相反,它是在你访问视图时运行一个预定义的查询来动态生成数据。

视图常用于简化复杂的查询、提供额外的安全层以及帮助组织和展示数据。

创建视图的语法如下:

sql 复制代码
CREATE VIEW view_name AS
SELECT column1, column2, ...
FROM table_name
WHERE condition;

5.2 视图操作

5.2.1 创建视图

user 表上创建如下两个视图:

sql 复制代码
CREATE VIEW test_view1 AS
SELECT * FROM users;

CREATE VIEW test_view2 AS
SELECT * FROM users
WHERE id < 5;
5.2.2 查看/使用视图

使用如下命令可以查看视图:

  • \dv:列出当前 schema(通常是 public)下的所有视图。
  • \dv+:加 + 可显示更多信息
  • \dv schemaname.*:指定 schema 查看视图

示例:

使用视图:

sql 复制代码
SELECT * FROM view_name

示例:

5.2.3 修改视图

修改视图可以使用 CREATE OR REPLACE VIEW 命令:

sql 复制代码
CREATE OR REPLACE VIEW view_name AS
SELECT column1, column2, ...
FROM table_name
WHERE condition;

示例:

sql 复制代码
CREATE OR REPLACE VIEW test_view2 AS
SELECT * FROM users
WHERE id < 5;
5.2.4 删除视图

删除视图可以使用 DROP VIEW 命令:

sql 复制代码
DROP VIEW IF EXISTS view_name;

示例:


六、PostgreSQL 索引

6.1 索引介绍

索引是加速搜索引擎检索数据的一种特殊表查询,PostgreSQL 的索引是提升查询性能的核心机制,合理使用索引,可以让百万级甚至十亿级数据的查询从"几秒"降到"毫秒"。

按逻辑用途/创建方式分类,PostgreSQL 支持以下索引:

索引类型 定义 创建方式 适用场景 是否自动创建
单列索引 在单个列上创建的索引 CREATE INDEX idx ON table (col); 高频查询、过滤、排序的单字段 否(主键/唯一约束除外)
组合索引 也叫复合索引,在多个列上创建的索引 CREATE INDEX idx ON table (col1, col2, ...); 多条件联合查询、覆盖索引优化
唯一索引 保证索引列的值全局唯一 CREATE UNIQUE INDEX idx ON table (col); 防止重复(如用户名、邮箱) 是(当定义 UNIQUE 约束时)
局部索引 也叫部分索引,只对满足 WHERE 条件的行建索引 CREATE INDEX idx ON table (col) WHERE condition; 状态过滤(如只查"未删除"数据)
隐式索引 系统自动创建的索引 无需手动创建 主键、唯一约束、排除约束等

按数据结构/算法分类,PostgreSQL 支持以下索引:

索引类型 适用场景 是否默认 支持操作符
B-tree 通用(等值、范围、排序) =, <, <=, >, >=, BETWEEN, IN, LIKE 'abc%'
Hash 仅等值查询(= =
GiST 多维数据(几何、全文检索、GIS 多种(如 &&, @>, <@
SP-GiST 非平衡树(如四叉树、k-d 树) 类似 GiST
GIN 倒排索引(数组、JSON、全文检索) @>, &&, = ANY()
BRIN 超大表(按物理顺序存储的数据) =, <, <=, >, >=
RUM(需扩展) 全文检索 + 排序 <->, ts_rank

6.2 索引用途

6.2.1 单列索引

单列索引是一个只基于表的单个列上创建的索引,是最基础、最常用的索引。

单列索引常用于单字段的高频查询、过滤和排序等场景,建立索引时应该优先考虑高选择性字段(如邮箱、手机号),不适用于性别(只有男/女)这类低区分度字段。

单列索引创建语法:

sql 复制代码
CREATE INDEX index_name ON table_name (column_name);

示例:

sql 复制代码
CREATE INDEX idx_userId ON users_info (user_id);
6.2.2 组合索引

组合索引是基于表的多个列创建的索引,创建时遵循"最左前缀原则",查询时条件的顺序必须从最左列开始连续使用。

索引创建语法:

sql 复制代码
CREATE INDEX index_name
ON table_name (column1_name, column2_name);

示例:

sql 复制代码
CREATE INDEX idx_userId_createdTime ON users_info (user_id, created_time);

组合索引的使用必须严格按照创建时使用的列顺序,否则会导致索引失效,如下示例:

sql 复制代码
-- 先 user_id,再 created_time
CREATE INDEX idx_userId_createdTime ON users_info (user_id, created_time);

-- 能用索引
SELECT * FROM users_info WHERE user_id = 100;
SELECT * FROM users_info WHERE user_id = 100 AND created_time > '2025-01-01';

-- 不能用索引(跳过 user_id)
SELECT * FROM users_info WHERE created_time > '2025-01-01';

组合索引的创建建议:

  • 等值列优先放在前面,范围列放后面,如 user_id, created_timecreated_time, user_id 更高效,因为 user_id 是等值查询。
  • 索引包含的列建议 3~4 列,太多会增加维护开销。
6.2.3 唯一索引

唯一索引强制索引列(或列组合)的值不能重复,插入/更新时自动检查唯一性,冲突则报错。唯一约束(UNIQUE)底层就是唯一索引。

唯一索引可以手动创建,也可以通过添加约束时由系统自动创建。

索引创建语法:

sql 复制代码
-- 手动创建
CREATE UNIQUE INDEX index_name on table_name (column_name);

-- 通过约束创建
ALTER TABLE table_name ADD CONSTRAINT index_name UNIQUE (column_name);

示例:

sql 复制代码
-- 手动创建唯一索引
CREATE INDEX uk_mobile ON users_info (mobile);

-- 通过约束创建
ALTER TABLE users ADD CONSTRAINT uk_mobile UNIQUE (mobile);

唯一索引允许 NULL,且多个 NULL 不视为重复(符合 SQL 标准)。

6.2.4 局部索引

局部索引是在表的子集上构建的索引,子集由一个条件表达式上定义,索引只包含满足条件的行。

索引创建语法:

sql 复制代码
CREATE INDEX index_name
ON table_name(column_list)
WHERE condition;
  • column_list:是想要索引的列的列表
  • condition:布尔表达式,用于定义哪些行将被包含在索引中。

示例:

sql 复制代码
-- 只为 状态正常的用户 建索引
CREATE INDEX idx_statusNormal
ON users (status) 
WHERE status = 1 AND is_deleted = 0;

查询时,必须按照创建索引时添加的相同条件才能使用索引:

sql 复制代码
SELECT * FROM users 
WHERE status = 1 
  AND is_deleted = 0;

典型场景:

  • 软删除:WHERE is_deleted = false
  • 状态过滤:WHERE order_status = 'pending'
  • 分区逻辑:WHERE region = 'CN'
6.2.5 隐式索引

在 PostgreSQL 中,隐式索引是在创建对象时,由数据库服务器自动创建的索引,这类索引通常为主键约束和唯一约束自动创建。

当在创建表时声明一个列为主键、唯一约束或外键时,PostgreSQL 会自动为该列创建一个隐式索引。

约束类型 是否创建隐式索引 索引类型
PRIMARY KEY 唯一 B-tree 索引
UNIQUE 唯一 B-tree 索引
EXCLUDE GiST 索引(或其他)
FOREIGN KEY 需手动建索引

示例:

sql 复制代码
-- 创建带主键的表
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name TEXT UNIQUE
);

-- 查看索引(psql)
\d products

6.3 索引结构/算法

6.3.1 B-tree

适用场景:

  • 等值查询:WHERE id = 100
  • 范围查询:WHERE age BETWEEN 20 AND 30
  • 排序:ORDER BY created_at
  • 前缀匹配:WHERE name LIKE 'John%'

复合索引的"最左前缀"原则,索引 (a, b, c) 可用于:

  • WHERE a = 1
  • WHERE a = 1 AND b = 2
  • WHERE a = 1 AND b = 2 AND c = 3
  • WHERE a = 1 ORDER BY b

不能用于:

  • WHERE b = 2(跳过 a)
  • WHERE c = 3
  • WHERE b = 2 AND c = 3
6.3.2 Hash

适用场景:

  • 只用于 = 查询
  • 内存中快速查找(比 B-tree 快 10~20%)
  • PostgreSQL 10+ 开始支持 WAL 日志(可安全用于生产)

创建语法:

sql 复制代码
CREATE INDEX index_name ON table_name  USING HASH (column_name);
6.3.3 GiST

GiST(Generalized Search Tree,泛化搜索树) 是 PostgreSQL 中一种高度灵活的索引结构,专为复杂数据类型(如几何、全文检索、GIS、自定义类型)设计。它不是单一算法,而是一个可扩展的框架,允许开发者为其支持的数据类型定义自己的"搜索策略"。

GiST = 通用索引框架 + 可插拔操作符类。GiST 不直接存储数据,而是通过一致性函数(consistent) 和联合函数(union) 来组织索引页,每个支持 GiST 的数据类型需提供一组操作符类(Operator Class),告诉 GiST 如何比较、合并和搜索。

GiST 索引的优缺点

  • 优点

    • 支持复杂数据类型(几何、范围、全文等)
    • 写入性能好(比 GIN 快,WAL 友好)
    • 索引体积较小
    • 支持 KNN(K 最近邻)搜索(如 <-> 距离排序)
  • 缺点

    • 不支持唯一约束
    • 查询性能略低于 GIN(对包含类查询)
    • 不能用于普通等值/范围查询(如 WHERE id = 100

GiST 支持的数据类型与操作符:

数据类型 常见操作符 说明
几何类型 point, box, circle, polygon &&(相交), <@(在内), @>(包含), <->(距离) 空间关系查询
范围类型 int4range, tsrange, daterange &&(重叠), @>(包含), <@(被包含) 时间/数值区间查询
全文检索 tsvector @@(匹配) 需配合 tsquery
hstore(键值对) @>, ?, ?& 键是否存在、包含等
inet/cidr >>, <<, && IP 地址包含、重叠
自定义类型 --- 通过扩展实现

示例 1:地理空间查询(找附近餐厅)

sql 复制代码
-- 创建表(使用 PostGIS 扩展,底层基于 GiST)
CREATE EXTENSION postgis;

CREATE TABLE restaurants (
    id SERIAL PRIMARY KEY,
    name TEXT,
    location GEOGRAPHY(POINT)  -- 经纬度点
);

-- 插入数据
INSERT INTO restaurants (name, location)
VALUES 
    ('A', ST_Point(-73.994454, 40.750042)),  -- 纽约
    ('B', ST_Point(-0.127758, 51.507351));   -- 伦敦

-- 创建 GiST 索引(关键!)
CREATE INDEX idx_restaurants_location ON restaurants USING GIST (location);

-- 查询:找纽约 10 公里内的餐厅
SELECT name, 
       ST_Distance(location, ST_Point(-73.99, 40.75)) AS dist_meters
FROM restaurants
WHERE ST_DWithin(location, ST_Point(-73.99, 40.75), 10000);  -- 10km

示例 2:时间范围重叠(会议预约冲突检测)

sql 复制代码
-- 会议表:每个会议有开始和结束时间
CREATE TABLE meetings (
    id SERIAL PRIMARY KEY,
    title TEXT,
    during TSRANGE  -- 时间范围类型
);

-- 插入数据
INSERT INTO meetings (title, during)
VALUES 
    ('Team Sync', '[2025-06-01 09:00, 2025-06-01 10:00)'),
    ('Review',     '[2025-06-01 09:30, 2025-06-01 11:00)');

-- 创建 GiST 索引(用于范围重叠查询)
CREATE INDEX idx_meetings_during ON meetings USING GIST (during);

-- 查询:是否有会议与 [09:15, 09:45) 冲突?
SELECT * FROM meetings 
WHERE during && '[2025-06-01 09:15, 2025-06-01 09:45)'::tsrange;
-- 返回 "Team Sync" 和 "Review"

示例 3:全文检索(替代 GIN 的轻量方案)

sql 复制代码
-- 创建文章表
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title TEXT,
    content TEXT,
    tsv TSVECTOR  -- 全文向量
);

-- 生成 tsvector(可用触发器自动维护)
UPDATE articles SET tsv = to_tsvector('english', title || ' ' || content);

-- 创建 GiST 索引(比 GIN 小,写入更快,但查询稍慢)
CREATE INDEX idx_articles_tsv ON articles USING GIST (tsv);

-- 查询
SELECT title FROM articles 
WHERE tsv @@ to_tsquery('english', 'database & postgres');

示例 4:IP 地址包含查询

sql 复制代码
CREATE TABLE networks (
    id SERIAL PRIMARY KEY,
    name TEXT,
    net CIDR
);

INSERT INTO networks VALUES 
    (1, 'LAN', '192.168.0.0/16'),
    (2, 'DMZ', '10.0.0.0/8');

-- GiST 索引加速 IP 包含查询
CREATE INDEX idx_networks_net ON networks USING GIST (net);

-- 查询:192.168.1.100 属于哪个网络?
SELECT name FROM networks 
WHERE net >> '192.168.1.100';  -- >> 表示"包含"
6.3.4 SP-GiST

SP-GiST(Space-Partitioned Generalized Search Tree,空间分区泛化搜索树) 是 PostgreSQL 中一种专为非平衡、分区型数据结构设计的索引方法。它是 GiST 的"表亲",但内部使用分区树(如四叉树、k-d 树、前缀树) 而非平衡树,特别适合处理具有自然聚类或层次结构的数据。

SP-GiST = 分区索引框架 + 支持非平衡树结构:

  • GiST 不同,SP-GiST 不要求树是平衡的,允许某些分支很深、某些很浅。
  • 它将数据空间划分为互斥的子区域(partitioning),每个节点代表一个区域。
  • 查询时,只需遍历可能包含目标的分区,跳过无关区域。

类比理解:

  • B-tree:像字典目录(严格排序)
  • GiST:像图书馆主题分类(可重叠)
  • SP-GiST:像电话区号树或IP 地址前缀树,1xx13x13813800138000,路径不等长,但查找极快!

SP-GiST 的优缺点:

  • 优点

    • 索引体积小(比 B-tree / GiST 更紧凑)
    • 写入性能极佳(适合高频插入)
    • 天然支持前缀/分层数据
    • KNN 搜索高效
  • 缺点

    • 不支持范围重叠查询(如 tsrange && tsrange
    • 不支持全文检索、JSONB、数组
    • 社区使用较少,文档和案例不如 GiST / GIN 丰富
    • 对均匀分布数据优势不明显

SP-GiST 支持的数据类型与操作符:

数据类型 操作符 说明
标量类型 text, varchar, char =, <, <=, >, >=, LIKE 'abc%' 前缀搜索优化
网络类型 inet, cidr =, >>, <<, &&, ~ IP 地址前缀匹配(路由表)
几何类型 point =, <->KNN 2D/3D 点位置查询
范围类型 int4range = 精确范围匹配(不支持重叠 &&!)

SP-GiST 不支持 tsvector(全文检索)或 jsonb

示例 1:手机号前缀搜索(高效 LIKE '138%')

sql 复制代码
-- 用户表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    phone TEXT  -- 存储为 '13800138000'
);

-- 插入 100 万条测试数据(略)

-- 创建 SP-GiST 索引(针对前缀)
CREATE INDEX idx_users_phone_spgist ON users USING SPGIST (phone);

-- 查询:所有 138 开头的用户
EXPLAIN ANALYZE 
SELECT * FROM users WHERE phone LIKE '138%';

-- 执行计划结果:
Index Scan using idx_users_phone_spgist on users  
  Index Cond: (phone ~>=~ '138'::text)  
  Filter: (phone ~~ '138%'::text)

示例 2:IP 路由表(最长前缀匹配)

sql 复制代码
-- 网络路由表
CREATE TABLE routes (
    cidr_block CIDR PRIMARY KEY,
    gateway INET
);

INSERT INTO routes VALUES 
    ('192.168.0.0/16', '10.0.0.1'),
    ('192.168.1.0/24', '10.0.0.2'),
    ('10.0.0.0/8',     '10.0.0.3');

-- 创建 SP-GiST 索引(优化 IP 包含查询)
CREATE INDEX idx_routes_cidr ON routes USING SPGIST (cidr_block);

-- 查询:IP 192.168.1.100 属于哪个网段?(最长匹配)
SELECT * FROM routes 
WHERE cidr_block >> '192.168.1.100'  -- >> 表示"包含"
ORDER BY masklen(cidr_block) DESC   -- 最长前缀优先
LIMIT 1;

示例 3:2D 点最近邻搜索(KNN

sql 复制代码
-- 地点表(仅点,无 PostGIS)
CREATE TABLE locations (
    name TEXT,
    pos POINT  -- PostgreSQL 内置 point 类型
);

INSERT INTO locations VALUES 
    ('A', POINT(0, 0)),
    ('B', POINT(1, 1)),
    ('C', POINT(3, 4));

-- 创建 SP-GiST 索引
CREATE INDEX idx_locations_pos ON locations USING SPGIST (pos);

-- 找离 (0.5, 0.5) 最近的点
SELECT name, pos <-> POINT(0.5, 0.5) AS dist
FROM locations
ORDER BY pos <-> POINT(0.5, 0.5)
LIMIT 1;
6.3.5 GIN

GIN 索引又叫倒排索引,GIN 索引写入开销大,适合读多写少场景。

适用场景:

  • 数组包含:WHERE tags @> ARRAY['postgres']
  • JSONB 包含:WHERE data @> '{"role": "admin"}'
  • 全文检索:WHERE to_tsvector('english', content) @@ to_tsquery('database')

创建语法:

sql 复制代码
-- 数组索引
CREATE INDEX index_name ON table_name USING GIN (column_name);

-- JSONB 索引
CREATE INDEX index_name ON table_name USING GIN (column_name);

-- 全文检索索引
CREATE INDEX index_name ON table_name USING GIN (to_tsvector('english', content));

JSONB 查询加速:

sql 复制代码
-- 表
CREATE TABLE profiles (id INT, data JSONB);
INSERT INTO profiles VALUES (1, '{"name": "Alice", "skills": ["SQL", "Python"]}');

-- 创建 GIN 索引
CREATE INDEX idx_profiles_data ON profiles USING GIN (data);

-- 查询(走索引)
SELECT * FROM profiles WHERE data @> '{"skills": ["SQL"]}';
6.3.6 BRIN

BRIN 索引适用于超大表,数据按照物理顺序插入(如时间、自增),并且查询条件与物理顺序相关的场景。

BRIN 索引的原理是每 N 个数据块(默认 128)记录一个 min / max 范围,查询时先看哪些块可能包含目标数据,跳过无关块。

创建语法:

sql 复制代码
CREATE INDEX idx_name ON table_name USING BRIN (column_name);

示例:优化日志表

sql 复制代码
-- 10 亿行日志表,按时间顺序插入
SELECT * FROM logs WHERE created_at > '2025-01-01'; -- 全表扫描很慢

-- 创建 BRIN 索引后
CREATE INDEX idx_logs_time_brin ON logs USING BRIN (created_at);

-- 查询速度提升 100 倍+,索引大小仅为 B-tree 的 1%
6.3.7 RUM

RUM 索引 是 PostgreSQL 中一种专为高级全文检索(Full-Text Search)设计的索引类型,它在 GIN 索引的基础上扩展了对位置信息和排序支持,特别适合需要 "按相关性排序" 的搜索场景(如 ORDER BY ts_rank(...))。

RUM 不是 PostgreSQL 内置索引,而是通过开源扩展 rum 提供(由 Postgres Professional 开发)。

GIN 的问题:

  • 无法在索引中存储词频/位置信息,ts_rank() 必须在过滤后逐行计算
  • ORDER BY ts_rank(...) 无法使用索引,需要 Sort + Limit,大数据量时极慢
  • 不支持距离操作符(如 phrase <-> search

RUM 索引在倒排列表中额外存储了每个词的出现位置(positions)和时间戳(可选),从而支持:

功能 GIN RUM
基础匹配 @@ 支持 支持
ts_rank() 计算 不支持(需回表) 支持(索引内完成)
ORDER BY ts_rank(...) LIMIT N 不支持(全排序) 支持(索引直接返回 Top-N
距离操作符 <-> 不支持 支持(词间距离)
时间感知搜索 不支持 支持(结合 timestamp

6.4 索引操作

6.4.2 创建索引

创建索引语法:

sql 复制代码
# CREATE INDEX index_name ON table_name (column_name);
6.4.2 查看索引

查看表的所有索引:

sql 复制代码
-- psql 命令
\d users

-- SQL 查询
SELECT indexname, indexdef 
FROM pg_indexes 
WHERE tablename = [table_name];
6.4.3 删除索引

删除索引语法:

sql 复制代码
DROP INDEX index_name;

6.5 索引优化

查看索引是否生效:

sql 复制代码
EXPLAIN ANALYZE [query_sql];

结果:

  • Index Scan using idx_users_email ... :走索引
  • Seq Scan on users ...:全表扫描

七、PostgreSQL 事务

7.1 事务介绍

PostgreSQL 的事务(Transaction) 是保障数据一致性、可靠性和并发控制的核心机制。

事务的四大特性(ACID):

特性 说明 PostgreSQL 实现
A - Atomicity(原子性) 事务是不可分割的最小单元,要么全做,要么全不做 WAL + 回滚段(Undo via MVCC)
C - Consistency(一致性) 事务使数据库从一个合法状态转移到另一个合法状态(如外键、唯一约束) 约束检查在事务提交时验证
I - Isolation(隔离性) 并发事务互不干扰(看似串行执行) MVCC(多版本并发控制) + 隔离级别
D - Durability(持久性) 一旦提交,数据永久保存(即使断电) WAL(Write-Ahead Logging)

事务控制语句:

语句 作用 说明
BEGINSTART TRANSACTION 开始事务 显式开启一个事务块
COMMIT 提交事务 永久保存所有更改
ROLLBACK 回滚事务 撤销所有未提交的更改
SAVEPOINT name 创建保存点 可部分回滚
ROLLBACK TO SAVEPOINT name 回滚到保存点 保留保存点之后的操作可继续
RELEASE SAVEPOINT name 删除保存点 (可选)

在 psql 或大多数客户端中,每条 SQL 自动包裹在 BEGIN; ... COMMIT; 中,默认自动提交,只有显式写 BEGIN 才进入"事务模式"。

示例1:转账(完整事务)

sql 复制代码
-- 假设有 accounts 表:id, name, balance

BEGIN;

-- 扣款
UPDATE accounts SET balance = balance - 100 WHERE name = 'Alice';
-- 加款
UPDATE accounts SET balance = balance + 100 WHERE name = 'Bob';

-- 检查是否足够(可选业务逻辑)
-- 如果 Alice 余额 < 0,可主动 ROLLBACK

COMMIT;  -- 两笔更新同时生效

示例 2:回滚(模拟错误)

sql 复制代码
BEGIN;
UPDATE products SET stock = stock - 1 WHERE id = 100;
-- 假设库存不足,程序检测到错误
ROLLBACK;  -- 所有更改撤销,stock 不变

示例 3:保存点(部分回滚)

sql 复制代码
BEGIN;

INSERT INTO logs VALUES ('step1');
SAVEPOINT sp1;

INSERT INTO logs VALUES ('step2');
-- 出错了!但不想回滚 step1
ROLLBACK TO sp1;

INSERT INTO logs VALUES ('step3');
COMMIT;

-- 最终 logs 有:step1, step3

7.2 事务隔离级别

7.2.1 事务隔离级别介绍

事务隔离级别(Transaction Isolation Levels) 决定了并发事务之间如何相互影响,它直接关系到你的应用是否会出现"脏读"、"不可重复读"或"幻读"等问题。

PostgreSQL 支持 4 种 SQL 标准隔离级别

隔离级别 脏读(Dirty Read) 不可重复读(Non-Repeatable Read) 幻读(Phantom Read) PostgreSQL 是否支持 实际行为
READ UNCOMMITTED 可能 可能 可能 不支持 自动降级为 READ COMMITTED
READ COMMITTED 不可能 可能 可能 默认 每条 SQL 看到已提交的最新快照
REPEATABLE READ 不可能 不可能 不可能(优于标准!) 支持 整个事务看到同一快照
SERIALIZABLE 不可能 不可能 不可能 支持 严格串行化,冲突时报错

PostgreSQL 的 REPEATABLE READ 能防止幻读(大多数数据库做不到),这是其 MVCC 机制的强大之处!

设置隔离级别:

sql 复制代码
-- 方法1:启动事务时指定
BEGIN ISOLATION LEVEL REPEATABLE READ;

-- 方法2:会话级设置(影响后续所有事务)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 方法3:全局默认(postgresql.conf)
default_transaction_isolation = 'read committed'
7.2.2 MVCC 机制

PostgreSQL 不用读锁,而是通过多版本并发控制(MVCC) 实现高并发,其核心思想是:

  • 每行数据包含两个隐藏字段:

    • xmin:创建该行的事务 ID
    • xmax:删除/更新该行的事务 ID(0 表示未删除)
  • 每个事务开始时获取一个"快照(Snapshot)",记录:

    • 当前活跃事务列表
    • 下一个事务 ID
  • 查询时,只返回 对当前快照可见的行版本

  • 每行数据有 xmin(创建事务 ID) 和 xmax(删除/更新事务 ID

  • 每个事务看到的是 符合其快照(snapshot)的行版本

  • 更新 = 插入新版本 + 标记旧版本 xmax

7.2.3 READ COMMITTED

PostgreSQL 默认的隔离级别,每条 SQL 语句执行时,获取一个新的快照,能看到在该语句开始前已提交的更改。

READ COMMITTED 适用于大多数 Web 应用,对一致性要求不极端严格的业务。

示例:不可重复读

sql 复制代码
-- Session A
BEGIN; -- 默认 READ COMMITTED

SELECT balance FROM accounts WHERE id = 1;  -- 返回 1000

-- 此时 Session B 执行:
--   BEGIN; UPDATE accounts SET balance = 2000 WHERE id=1; COMMIT;

SELECT balance FROM accounts WHERE id = 1;  -- 返回 2000!(不可重复读)
COMMIT;
7.2.4 REPEATABLE READ

整个事务使用同一个快照(事务开始时创建),事务内多次查询结果一致,能防止幻读(PostgreSQL 特有!)

示例:防止幻读

sql 复制代码
-- Session A
BEGIN ISOLATION LEVEL REPEATABLE READ;

SELECT COUNT(*) FROM orders WHERE status = 'pending';  -- 返回 5

-- Session B 插入新订单:
--   INSERT INTO orders (status) VALUES ('pending');

SELECT COUNT(*) FROM orders WHERE status = 'pending';  -- 仍返回 5!(无幻读)
COMMIT;

READ COMMITTED 隔离级别中,写偏斜(Write Skew)仍可能发生,示例:

sql 复制代码
场景:两人同时从共享账户转账
- 初始余额:1000
- 每人最多转 600

Session A: SELECT balance → 1000; if <1200 then UPDATE -600
Session B: SELECT balance → 1000; if <1200 then UPDATE -600

结果:余额 = -200!(逻辑错误)

适用场景:

  • 报表生成(需一致快照)
  • 金融对账
  • 任何需要"事务内多次查询结果一致"的场景
7.2.5 SERIALIZABLE

最高的隔离级别,提供完全串行化的执行效果,如果检测到可能导致串行化异常的操作,直接报错 ERROR: could not serialize access due to concurrent update,应用必须捕获异常并重试事务。

示例:写偏斜被阻止

sql 复制代码
-- Session A
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT balance FROM accounts WHERE id = 1;  -- 1000
-- (假设业务逻辑:若 <1200 则扣 600)

-- Session B 同时执行相同逻辑

-- 其中一个 COMMIT 会成功,另一个会报错:
-- ERROR: serialization failure

适用场景:

  • 银行核心交易系统
  • 严格一致性要求的分布式事务模拟
  • 无法容忍任何逻辑冲突的业务

缺点:

  • 可能频繁报错,需重试逻辑
  • 性能略低于 REPEATABLE READ
7.2.6 READ UNCOMMITTED

PostgreSQL 不支持此级别,即使 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;,PostgreSQL 实际生效的是 READ COMMITTED

7.3 并发问题与解决方案

问题 描述 解决方案
丢失更新 两个事务读同一值,各自修改后覆盖对方 SELECT FOR UPDATE 或应用层锁
写偏斜(Write Skew) 两个事务基于过期读做决策 升级到 SERIALIZABLE 隔离级别
死锁(Deadlock) 事务互相等待对方释放锁 PostgreSQL 自动检测并终止一个事务

防止丢失更新:SELECT FOR UPDATE

sql 复制代码
BEGIN;
-- 锁住这行,其他事务不能读(FOR UPDATE)或改
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- 基于当前值计算
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

7.4 高级特性

两阶段提交(2PC) ------ 分布式事务

sql 复制代码
PREPARE TRANSACTION 'txn1';  -- 第一阶段:准备
COMMIT PREPARED 'txn1';      -- 第二阶段:提交
-- 或 ROLLBACK PREPARED 'txn1';

事务 ID 回卷(Wraparound)防护:

  • PostgreSQL 事务 ID 是 32 位,约 20 亿后回卷
  • 必须定期 VACUUM,否则数据库会只读保护!

只读事务(Read-Only Transaction):

sql 复制代码
BEGIN READ ONLY;
SELECT ...;  -- 不能 UPDATE/DELETE
COMMIT;

适用于报表、分析查询,提升并发性。

7.5 监控事务

查看活跃事务:

sql 复制代码
SELECT pid, usename, xact_start, query 
FROM pg_stat_activity 
WHERE state = 'active' AND xact_start IS NOT NULL;

查看长事务:

sql 复制代码
SELECT pid, now() - xact_start AS duration, query
FROM pg_stat_activity
WHERE xact_start < now() - INTERVAL '5 minutes';

八、PostgreSQL 锁

8.1 PostgreSQL 锁介绍

PostgreSQL 的锁(Locking)机制是保障数据一致性和并发控制的核心组件。与许多数据库不同,PostgreSQL 极少使用表级写锁,而是通过 MVCC(多版本并发控制) + 精细锁 实现高并发。

PostgreSQL 支持 6 种锁粒度,从粗到细:

锁级别 说明 典型场景
Database Lock 锁整个数据库 DROP DATABASE
Tablespace Lock 锁表空间 DROP TABLESPACE
Relation (Table) Lock 最常见表级锁 ALTER TABLE, VACUUM FULL
Page Lock 锁数据页(8KB) 内部使用,用户不可见
Tuple (Row) Lock 行级锁 SELECT FOR UPDATE
Transaction ID Lock 锁事务ID 内部用于 2PC

表级锁:

PostgreSQL 有 8 种表级锁模式,按强度递增:

锁模式 获取方式 允许并发读? 允许并发写? 典型命令
ACCESS SHARE SELECT 普通查询
ROW SHARE SELECT FOR SHARE/UPDATE 行锁前先加此锁
ROW EXCLUSIVE INSERT/UPDATE/DELETE DML 操作
SHARE UPDATE EXCLUSIVE VACUUM (non-full), ANALYZE 阻止结构变更
SHARE CREATE INDEX 建索引(非并发)
SHARE ROW EXCLUSIVE --- 几乎不用
EXCLUSIVE --- 极少用
ACCESS EXCLUSIVE ALTER TABLE, DROP TABLE, VACUUM FULL 阻塞所有操作!

行级锁:

行锁通过 SELECT ... FOR UPDATE/SHARE 显式获取,或由 UPDATE/DELETE 自动加锁。

4 种行锁模式:

锁模式 SQL 语法 作用 兼容性
FOR UPDATE SELECT ... FOR UPDATE 获取排他锁,阻止其他 UPDATE/DELETE/FOR UPDATE 仅与 FOR KEY SHARE 兼容
FOR NO KEY UPDATE SELECT ... FOR NO KEY UPDATE 更新非主键列时使用(9.6+) FOR UPDATE 稍宽松
FOR SHARE SELECT ... FOR SHARE 共享锁,阻止 UPDATE/DELETE,但允许多个 FOR SHARE FOR SHARE / FOR KEY SHARE 兼容
FOR KEY SHARE SELECT ... FOR KEY SHARE 最弱行锁,仅阻止删除或更新主键 与所有行锁兼容

8.2 锁冲突与死锁

PostgreSQL 自动检测死锁(默认 deadlock_timeout = 1s),检测到死锁时自动终止其中一个事务,报错:

sql 复制代码
ERROR: deadlock detected
DETAIL: Process 12345 waits for ShareLock on transaction 67890; 
        blocked by process 54321.
HINT: See server log for query details.

九、PostgreSQL 权限

9.1 PostgreSQL 权限介绍

PostgreSQL 的权限系统(Privileges System)是保障数据安全的核心机制,它基于角色(Roles)模型,支持精细的访问控制,从数据库连接、表操作,到列、行甚至特定函数的调用。

角色(Roles)代替用户/组:PostgreSQL 没有"用户"和"组"的区分,统一使用 ROLE

  • 登录角色(Login Role) = 传统"用户"
  • 非登录角色(Non-Login Role) = 传统"用户组"或"权限集合"

创建角色:

sql 复制代码
-- 创建可登录用户(带密码)
CREATE ROLE alice WITH LOGIN PASSWORD 'secure123';

-- 创建权限组(不可登录)
CREATE ROLE analysts;

-- 将用户加入组
GRANT analysts TO alice;

9.2 权限层级与对象类型

PostgreSQL 权限作用于数据库对象,层级如下:

sql 复制代码
Cluster(实例)
└── Database
    ├── Schema
    │   ├── Table / View / Sequence
    │   ├── Column(列级)
    │   └── Function / Procedure
    └── Language / Tablespace

支持权限的对象类型:

对象类型 可授予权限
DATABASE CONNECT, CREATE, TEMPORARY
SCHEMA USAGE, CREATE
TABLE / VIEW SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
COLUMN SELECT, INSERT, UPDATE(列级)
SEQUENCE USAGE, SELECT, UPDATE
FUNCTION EXECUTE

9.3 权限操作

PostgreSQL 中,权限操作使用 GRANT / REVOKE 命令,语法如下:

sql 复制代码
-- 授予权限
GRANT privilege [, ...] ON object_type object_name TO role_name;

-- 撤销权限
REVOKE privilege [, ...] ON object_type object_name FROM role_name;

示例 1:基础表权限

sql 复制代码
-- 创建表(属于当前用户,如 postgres)
CREATE TABLE sales (
    id SERIAL,
    amount NUMERIC,
    region TEXT,
    created_by TEXT
);

-- 创建只读角色
CREATE ROLE reader;
GRANT CONNECT ON DATABASE mydb TO reader;
GRANT USAGE ON SCHEMA public TO reader;
GRANT SELECT ON TABLE sales TO reader;

-- 创建分析员角色(可读写)
CREATE ROLE analyst;
GRANT SELECT, INSERT, UPDATE ON sales TO analyst;

示例 2:列级权限(敏感字段保护)

sql 复制代码
-- 防止 analyst 修改 created_by 字段
REVOKE UPDATE(created_by) ON sales FROM analyst;

-- 或只允许更新特定列
GRANT UPDATE(amount, region) ON sales TO analyst;

避免每次建新表都要手动授权,可用 默认权限 ALTER DEFAULT PRIVILEGES

示例:自动授权给新表

sql 复制代码
-- 设置:未来所有由 postgres 创建的表,自动给 analyst 读写权
ALTER DEFAULT PRIVILEGES 
    FOR ROLE postgres          -- 谁创建的对象
    IN SCHEMA public           -- 在哪个 schema
    GRANT SELECT, INSERT, UPDATE ON TABLES TO analyst;

-- 同样适用于序列、函数等
ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT USAGE ON SEQUENCES TO analyst;

所有权(Ownership)与超级用户:

  • 对象创建者 = 所有者(Owner)
  • 所有者自动拥有 ALL 权限,且不能被 REVOKE
  • 只有所有者或超级用户能 DROP / ALTER 对象
  • 超级用户(SUPERUSER)绕过所有权限检查

查看对象所有者:

sql 复制代码
SELECT tablename, tableowner 
FROM pg_tables 
WHERE schemaname = 'public';

十、PostgreSQL 操作符与函数

10.1 日期/时间操作符

操作符 例子 结果
+ date '2001-09-28' + integer '7' date '2001-10-05'
+ date '2001-09-28' + interval '1 hour' timestamp '2001-09-28 01:00:00'
+ date '2001-09-28' + time '03:00' timestamp '2001-09-28 03:00:00'
+ interval '1 day' + interval '1 hour' interval '1 day 01:00:00'
+ timestamp '2001-09-28 01:00' + interval '23 hours' timestamp '2001-09-29 00:00:00'
+ time '01:00' + interval '3 hours' time '04:00:00'
- - interval '23 hours' interval '-23:00:00'
- date '2001-10-01' - date '2001-09-28' integer '3' (days)
- date '2001-10-01' - integer '7' date '2001-09-24'
- date '2001-09-28' - interval '1 hour' timestamp '2001-09-27 23:00:00'
- time '05:00' - time '03:00' interval '02:00:00'
- time '05:00' - interval '2 hours' time '03:00:00'
- timestamp '2001-09-28 23:00' - interval '23 hours' timestamp '2001-09-28 00:00:00'
- interval '1 day' - interval '1 hour' interval '1 day -01:00:00'
- timestamp '2001-09-29 03:00' - timestamp '2001-09-27 12:00' interval '1 day 15:00:00'
* 900 * interval '1 second' interval '00:15:00'
* 21 * interval '1 day' interval '21 days'
* double precision '3.5' * interval '1 hour' interval '03:30:00'
/ interval '1 hour' / double precision '1.5' interval '00:40:00'

10.2 日期/时间函数

函数 返回类型 描述 示例 结果
age(timestamp, timestamp) interval 减去参数后的"符号化"结果,使用年和月,不只是使用天 age(timestamp '2001-04-10', timestamp '1957-06-13') 43 years 9 mons 27 days
age(timestamp) interval current_date 减去参数后的结果(在午夜) age(timestamp '1957-06-13') 43 years 8 mons 3 days
clock_timestamp() timestamp with time zone 实时时钟的当前时间戳(在语句执行时变化) --- ---
current_date date 当前的日期 --- ---
current_time time with time zone 当日时间 --- ---
current_timestamp timestamp with time zone 当前事务开始时的时间戳 --- ---
date_part(text, timestamp) double precision 获取子域(等效于 extract date_part('hour', timestamp '2001-02-16 20:38:40') 20
date_part(text, interval) double precision 获取子域(等效于 extract date_part('month', interval '2 years 3 months') 3
date_trunc(text, timestamp) timestamp 截断成指定的精度 date_trunc('hour', timestamp '2001-02-16 20:38:40') 2001-02-16 20:00:00
date_trunc(text, interval) interval 截取指定的精度 date_trunc('hour', interval '2 days 3 hours 40 minutes') 2 days 03:00:00
extract(field from timestamp) double precision 获取子域 extract(hour from timestamp '2001-02-16 20:38:40') 20
extract(field from interval) double precision 获取子域 extract(month from interval '2 years 3 months') 3
isfinite(date) boolean 测试是否为有穷日期(不是 ±∞) isfinite(date '2001-02-16') true
isfinite(timestamp) boolean 测试是否为有穷时间戳(不是 ±∞) isfinite(timestamp '2001-02-16 21:28:30') true
isfinite(interval) boolean 测试是否为有穷时间间隔 isfinite(interval '4 hours') true
justify_days(interval) interval 按照每月 30 天调整时间间隔 justify_days(interval '35 days') 1 mon 5 days
justify_hours(interval) interval 按照每天 24 小时调整时间间隔 justify_hours(interval '27 hours') 1 day 03:00:00
justify_interval(interval) interval 使用 justify_daysjustify_hours 调整时间间隔的同时进行正负号调整 justify_interval(interval '1 mon -1 hour') 29 days 23:00:00
localtime time 当日时间 --- ---
localtimestamp timestamp 当前事务开始时的时间戳 --- ---
make_date(year int, month int, day int) date 为年、月和日字段创建日期 make_date(2013, 7, 15) 2013-07-15
make_interval(years int DEFAULT 0, months int DEFAULT 0, weeks int DEFAULT 0, days int DEFAULT 0, hours int DEFAULT 0, mins int DEFAULT 0, secs double precision DEFAULT 0.0) interval 从年、月、周、天、小时、分钟和秒字段中创建间隔 make_interval(days := 10) 10 days
make_time(hour int, min int, sec double precision) time 从小时、分钟和秒字段中创建时间 make_time(8, 15, 23.5) 08:15:23.5
make_timestamp(year int, month int, day int, hour int, min int, sec double precision) timestamp 从年、月、日、小时、分钟和秒字段中创建时间戳 make_timestamp(2013, 7, 15, 8, 15, 23.5) 2013-07-15 08:15:23.5
make_timestamptz(year int, month int, day int, hour int, min int, sec double precision, [ timezone text ]) timestamp with time zone 从年、月、日、小时、分钟和秒字段中创建带有时区的时间戳。未指定 timezone 时,使用当前时区。 make_timestamptz(2013, 7, 15, 8, 15, 23.5) 2013-07-15 08:15:23.5+01
now() timestamp with time zone 当前事务开始时的时间戳 --- ---
statement_timestamp() timestamp with time zone 实时时钟的当前时间戳(语句开始执行时) --- ---
timeofday() text clock_timestamp 相同,但结果是 text 字符串 --- ---
transaction_timestamp() timestamp with time zone 当前事务开始时的时间戳(等价于 now() --- ---

聚合函数

函数 说明 示例 备注
COUNT(*) 统计行数(含 NULL) SELECT COUNT(*) FROM users; 最常用
COUNT(column) 统计非 NULL 值数量 SELECT COUNT(email) FROM users; 忽略 NULL
MAX(column) 返回列中最大值 SELECT MAX(salary) FROM employees; 支持数字、日期、字符串
MIN(column) 返回列中最小值 SELECT MIN(created_at) FROM logs; 同上
AVG(column) 计算列的平均值 SELECT AVG(score) FROM exams; 仅数字类型
SUM(column) 计算列的总和 SELECT SUM(amount) FROM orders; 仅数字类型

10.3 数学函数

函数 返回类型 描述 示例
abs(x) 绝对值 abs(-17.4) = 17.4
cbrt(double) 立方根 cbrt(27.0) = 3
ceil(double/numeric) 不小于参数的最小的整数 ceil(-42.8) = -42
degrees(double) 把弧度转为角度 degrees(0.5) = 28.6478897565412
exp(double/numeric) 自然指数 exp(1.0) = 2.71828182845905
floor(double/numeric) 不大于参数的最大整数 floor(-42.8) = -43
ln(double/numeric) 自然对数 ln(2.0) = 0.693147180559945
log(double/numeric) 10为底的对数 log(100.0) = 2
log(b numeric,x numeric) numeric 指定底数的对数 log(2.0, 64.0) = 6.0000000000
mod(y, x) 取余数 mod(9,4) = 1
pi() double "π"常量 pi() = 3.14159265358979
power(a double, b double) double 求a的b次幂 power(9.0, 3.0) = 729
power(a numeric, b numeric) numeric 求a的b次幂 power(9.0, 3.0) = 729
radians(double) double 把角度转为弧度 radians(45.0) =0.785398163397448
random() double 0.0到1.0之间的随机数值 random()
round(double/numeric) 圆整为最接近的整数 round(42.4) = 42
round(v numeric, s int) numeric 圆整为s位小数数字 round(42.438,2) = 42.44
sign(double/numeric) 参数的符号(-1,0,+1) sign(-8.4) = -1
sqrt(double/numeric) 平方根 sqrt(2.0) = 1.4142135623731
trunc(double/numeric) 截断(向零靠近) trunc(42.8) = 42
trunc(v numeric, s int) numeric 截断为s小数位置的数字 trunc(42.438,2) = 42.43

10.4 三角函数

函数 说明 输入 输出 示例
sin(x) 正弦 弧度 浮点数 sin(radians(30))0.5
cos(x) 余弦 弧度 浮点数 cos(radians(60))0.5
cot(x) 余切(需自定义) 弧度 浮点数 1 / tan(radians(45))1.0
tan(x) 正切 弧度 浮点数 tan(radians(45))1.0
asin(x) 反正弦 (arcsin) [-1, 1] 弧度 degrees(asin(0.5))30
acos(x) 反余弦 (arccos) [-1, 1] 弧度 degrees(acos(0))90
atan(x) 反正切 (arctan) 任意实数 弧度 degrees(atan(1))45
atan2(y, x) 四象限反正切 任意实数 弧度(-π 到 π) degrees(atan2(1, -1))135

弧度与角度的转换函数:

  • radians(角度):角度 → 弧度
  • degrees(弧度):弧度 → 角度

10.5 字符串函数和操作符

函数 返回类型 描述 示例 结果
`string string` text 字符串连接
bit_length(string) int 字符串中二进制位的个数 bit_length('jose') 32
char_length(string) int 字符串中的字符个数 char_length('jose') 4
convert(string using conversion_name) text 使用指定的转换名改变编码 convert('PostgreSQL' using iso_8859_1_to_utf8) 'PostgreSQL'
lower(string) text 将字符串转为小写 lower('TOM') tom
octet_length(string) int 字符串中的字节数 octet_length('jose') 4
overlay(string placing string from int [for int]) text 替换子字符串 overlay('Txxxxas' placing 'hom' from 2 for 4) Thomas
position(substring in string) int 指定子字符串的位置(从1开始) position('om' in 'Thomas') 3
substring(string [from int] [for int]) text 抽取子字符串(按位置) substring('Thomas' from 2 for 3) hom
substring(string from pattern) text 抽取匹配 POSIX 正则表达式的子串 substring('Thomas' from '...$') mas
substring(string from pattern for escape) text 抽取匹配 SQL 正则表达式的子串 substring('Thomas' from '%#"o_a#"_' for '#') oma
`trim([leading trailing both] [characters] from string)` text 从字符串开头/结尾/两边删除指定字符(默认空白)
upper(string) text 将字符串转为大写 upper('tom') TOM
ascii(text) int 返回参数第一个字符的 ASCII 码 ascii('x') 120
btrim(string [, characters]) text 从字符串两端删除指定字符(默认空白) btrim('xyxtrimyyx', 'xy') trim
chr(int) text 返回对应 ASCII 码的字符 chr(65) A
convert(string, [src_encoding,] dest_encoding) text 将字符串从一种编码转换为另一种 convert('text_in_utf8', 'UTF8', 'LATIN1') 以 ISO 8859-1 编码表示的 text_in_utf8
initcap(text) text 将每个单词首字母大写,其余小写 initcap('hi thomas') Hi Thomas
length(string) int 字符串中字符的数目(同 char_length length('jose') 4
lpad(string, length [, fill]) text 在左侧用 fill 填充至指定长度 lpad('hi', 5, 'xy') xyxhi
ltrim(string [, characters]) text 从字符串开头删除指定字符 ltrim('zzzytrim','xyz') trim
md5(string) text 计算字符串的 MD5 散列(十六进制) md5('abc') 900150983cd24fb0d6963f7d28e17f72
repeat(string, number) text 重复字符串指定次数 repeat('Pg', 4) PgPgPgPg
replace(string, from, to) text 将字符串中所有 from 子串替换为 to replace('abcdefabcdef', 'cd', 'XX') abXXefabXXef
rpad(string, length [, fill]) text 在右侧用 fill 填充至指定长度 rpad('hi', 5, 'xy') hixyx
rtrim(string [, characters]) text 从字符串结尾删除指定字符 rtrim('trimxxxx','x') trim
split_part(string, delimiter, field) text 按分隔符拆分,返回第 field 个字段(从1开始) split_part('abc~@~def~@~ghi', '~@~', 2) def
strpos(string, substring) int 子字符串位置(等价于 position strpos('high','ig') 2
substr(string, from [, count]) text 抽取子字符串(等价于 substring substr('alphabet', 3, 2) ph
to_ascii(text [, encoding]) text 将文本从其他编码转换为 ASCII(去除重音等) to_ascii('Karel') Karel
to_hex(number) text 将整数转为十六进制字符串 to_hex(9223372036854775807) 7fffffffffffffff
translate(string, from, to) text stringfrom 的每个字符替换为 to 中对应字符 translate('12345', '14', 'ax') a23x5
  • length()char_length() 功能相同。
  • strpos()position() 的别名,但参数顺序更符合直觉(strpos(str, substr))。
  • substr()substring() 的别名。
  • md5() 返回 32 位小写十六进制字符串。
  • convert()to_ascii() 用于多语言或编码迁移场景,需确保数据库支持相应编码。

10.6 类型转换相关函数

函数 返回类型 描述 示例
to_char(timestamp, text) text 将时间戳转换为格式化字符串 to_char(current_timestamp, 'HH12:MI:SS')
to_char(interval, text) text 将时间间隔转换为格式化字符串 to_char(interval '15h 2m 12s', 'HH24:MI:SS')
to_char(int, text) text 将整型数字转换为格式化字符串 to_char(125, '999')
to_char(double precision, text) text 将双精度浮点数转换为格式化字符串 to_char(125.8::real, '999D9')
to_char(numeric, text) text 将 numeric 类型数字转换为格式化字符串 to_char(-125.8, '999D99S')
to_date(text, text) date 将符合格式的字符串解析为日期 to_date('05 Dec 2000', 'DD Mon YYYY')
to_number(text, text) numeric 将格式化字符串解析为数字 to_number('12,454.8-', '99G999D9S')
to_timestamp(text, text) timestamp 将符合格式的字符串解析为时间戳(不带时区) to_timestamp('05 Dec 2000', 'DD Mon YYYY')
to_timestamp(double precision) timestamp with time zone 将 UNIX 时间戳(秒)转换为带时区的时间戳 to_timestamp(1284352323)
  • to_char(..., format)to_timestamp/to_date/to_number(text, format)互逆操作
  • 使用 to_timestamp(unix_time) 时,输入单位为秒(可含小数) ,返回 timestamptz
  • 格式模板区分大小写,且必须与输入字符串严格匹配,否则会报错。

10.7 常用格式化符号

符号 含义 示例
YYYY 四位年份 2025
MM / Mon / Month 月份数字 / 缩写 / 全名 12 / Dec / December
DD 05
HH24 / HH12 24小时制 / 12小时制 14 / 02
MI 分钟 30
SS 45
D 小数点(本地化) 125.8'999D9''125.8'
G 千位分隔符(本地化) '12,454'
S 符号(正/负) -125.8'999D99S''125.80-'
FM 填充模式(去除前导/尾随空格) to_char(5, 'FM00')'05'

相关资源:

相关推荐
SmartRadio15 小时前
物联网云平台数据库选型与搭建全指南(NRF52840, CH585M,ESP32-S3的硬件资源要求选型对比、方案设计、搭建步骤)
c语言·数据库·物联网·lora·lorawan
知行学思1 天前
Python配置管理完全指南:从dotenv到pydantic_settings
数据库·python·fastapi·环境变量·配置管理·pydantic·dotenv
计算机网恋1 天前
Ubuntu22.04Server虚拟机网络配置
网络·数据库·postgresql
一只大黄猫1 天前
【数据库-入门2】基本概念
数据库
实泽有之,无泽虚之1 天前
MySQL主机因多次连接数据库错误而被阻塞
数据库·sql·mysql
Knight_AL1 天前
从自然语言到 SQL:为什么向量数据库是更好的选择
数据库·sql
Maybe I Simple1 天前
MySql 数据库分表 简单思路
数据库·php·webman
智航GIS1 天前
8.11 sys 模块
数据库·windows·microsoft
陈天伟教授1 天前
国产数据库快速入门《数据库技术原理及应用》(DM8)
数据库·数据挖掘