Skip to content

PostgreSQL

PostgreSQL Vs MySQL

对比维度MySQLPostgreSQL
产品定位轻量开源、互联网标配、主打简单 CRUD 高并发企业级全能型、SQL 标准严格、复杂查询 / 高级特性拉满
架构层级数据库 → 表,无独立 Schema 概念,库等价模式数据库 → Schema 模式 → 表,三层结构,可多模式隔离表
上手难度低,配置简单、语法通俗、新手友好中等,概念多(库 / 模式分离)、规则严谨、学习成本稍高
资源占用轻量,内存 / CPU 消耗低,低配服务器可流畅运行偏重,默认内存占用更高,更吃硬件配置
数据类型基础类型够用,JSON 支持一般,无原生数组、GIS 类型支持数组、JSON/JSONB、GIS 地理、IP、区间、枚举等高级类型
数据严谨性约束宽松,非法数据可能自动截断,易产生脏数据严格遵循 SQL 标准,约束 / 外键强校验,拒绝脏数据,金融政务友好
事务 MVCCInnoDB 支持事务、MVCC,基础够用事务一致性更强,MVCC 机制更成熟,隔离级别更完善
SQL 能力支持基础联表,CTE / 递归 / 窗口函数支持较弱原生支持 CTE、递归查询、窗口函数、自定义函数、复杂子查询
性能表现简单增删改查、高并发短事务性能优秀复杂多表 JOIN、大表聚合、报表统计性能碾压 MySQL
并发能力高并发简单读写场景优化成熟并发也强,但复杂查询多的时候资源开销更大
扩展能力扩展有限,依赖第三方插件可自定义数据类型、函数、插件,原生支持时序 / 全文检索 / GIS
生态人才生态极大、教程海量、运维开发人才遍地都是生态逐年火爆,但资深运维和优化人才比 MySQL 少
ORM 适配完美兼容 MyBatis、Prisma、Drizzle、JPA 等全 ORM 兼容,Node/TS、Java、Go 项目都常用
部署运维安装简单、配置少、备份迁移工具丰富配置参数多、调优复杂,库与 Schema 概念易混淆
适用场景后台管理、小程序、电商普通业务、高并发简单 CRUD复杂报表、数据分析、GIS 地理、JSON 业务、树形层级、政务金融

基本结构

核心层级结构

逻辑结构从大到小依次为:实例 (Instance) > 数据库 (Database) > 模式 (Schema) > 表 (Table) > 行与列 (Row & Column)

PostgreSQL 比 MySQL 多一层 Schema

为了做逻辑隔离、命名隔离、权限隔离,不用频繁新建数据库

加一层Schema的优势在于

  • 单库多模块:一个数据库里用不同 Schema 分用户、订单、库存等模块,不用像 MySQL 那样拆多个库
  • 表名可重复:不同 Schema 可以有同名表,互不冲突,避免命名混乱
  • 权限更精细:可以按模式单独给用户授权,只允许访问某个业务模块,比 MySQL 按整库授权更安全
  • 无需跨库联表:所有业务在同一个库,跨模块连表查询简单、性能更好
  • 便于管理维护:整个项目只用一个库,备份、迁移、清理只需要维护这一个库,不用管一堆分库

库操作

postgresql
create database db1;
postgresql
drop database db1;
drop database db1 with (force);
postgresql
select datname
from pg_database;
postgresql
create SCHEMA IF NOT EXISTS storage;
postgresql
drop SCHEMA IF EXISTS storage;
postgresql
drop schema if exists storage cascade;

表操作

postgresql
CREATE TABLE IF NOT EXISTS storage.files
(
    id         BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, -- 自增主键
    file_id    UUID        DEFAULT gen_random_uuid(),               -- uuid
    file_name  TEXT NOT NULL,                                       -- 不能为空
    file_size  BIGINT CHECK (file_size >= 0),                       -- 检查约束:大小不能为负
    is_public  BOOLEAN     DEFAULT false,                           -- 默认值
    tags       TEXT[],                                              -- 数组类型(PG特色!)
    created_at TIMESTAMPTZ DEFAULT NOW()                            -- 带时区的时间
);
postgresql
SELECT schemaname AS 模式名,
       tablename  AS 表名,
       tableowner AS 所有人
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY schemaname, tablename;
postgresql
SELECT column_name              AS 字段名,
       data_type                AS 数据类型,
       is_nullable              AS 是否允许为空,
       column_default           AS 默认值,
       character_maximum_length AS 最大长度
FROM information_schema.columns
WHERE table_name = 'files'
ORDER BY ordinal_position; -- 按照表中定义的顺序排列
postgresql
-- 加字段
ALTER TABLE storage.files
    ADD COLUMN download_count INT DEFAULT 0;
-- 改字段类型
ALTER TABLE storage.files
    ALTER COLUMN file_name TYPE VARCHAR(255);
-- 重命名列名
ALTER TABLE storage.files
    RENAME COLUMN is_public TO is_shared;
-- 删除列
ALTER TABLE storage.files
    DROP COLUMN tags;
postgresql
-- 基本删除
DROP TABLE IF EXISTS storage.files;

-- 级联删除(如果这个表被其他表作为外键关联,用CASCADE会连带删除关联)
DROP TABLE storage.files CASCADE;
postgresql
TRUNCATE TABLE storage.files;
-- 清空表并且重置自增id
TRUNCATE TABLE storage.files i;
postgresql
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'files';
postgresql
-- 父表:定义基础字段
CREATE TABLE content
(
    title  TEXT,
    author TEXT
);

-- 子表:继承基础字段,并增加自己的字段
CREATE TABLE video
(
    duration INT
) INHERITS (content);
postgresql
INSERT INTO storage.files (file_name, file_size, is_public, tags)
VALUES ('课程大纲.pdf', 1024576, true, ARRAY ['教育', 'PDF']);


INSERT INTO storage.files (file_name, file_size, tags)
VALUES ('demo.mp4', 50000000, '{"视频", "测试"}'),
       ('config.yaml', 1024, '{"配置"}');

-- 插入+回显
INSERT INTO storage.files (file_name, file_size)
VALUES ('db.zip', 1024 * 124 * 8)
RETURNING *;
postgresql
SELECT *
FROM storage.files;

SELECT *
FROM storage.files
WHERE '视频' = ANY (tags);
postgresql
UPDATE storage.files
SET is_public = false
WHERE file_name = 'demo.mp4';
postgresql
DELETE
FROM storage.files
WHERE file_id = '018a72a4-ab27-4944-a922-446bf36052ff';

表结构

常用数据类型

类型分类数据类型说明举例
数值类型smallint短整型,占用 2 字节,取值范围小100、-50
数值类型integer / int常规整型,开发最常用整数类型2026、999
数值类型bigint大整型,适合存储超大整数值9223372036854775807
数值类型decimal(p,s)高精度定点小数,财务金额专用,无精度丢失decimal(10,2) 存 99.99
数值类型numeric(p,s)与 decimal 完全等价,适合精密计算场景numeric(8,3) 存 123.456
数值类型real单精度浮点数,精度较低,不建议存金额3.14、0.618
数值类型double precision双精度浮点数,小数近似存储3.1415926
数值类型serial自增整型,等价自增主键自动生成 1,2,3,4...
数值类型bigserial大自增整型,适用于数据量极大的自增场景自动生成超大自增序列
字符文本char(n)固定长度字符串,长度不足自动补空格char(6) 存 'abc' 自动补空格到 6 位
字符文本varchar(n)可变长度字符串,可限制最大长度varchar(50) 存 '张三'
字符文本text无长度限制长文本,适合存大段文字文章内容、备注长文本
字符文本citext不区分大小写的文本类型,匹配忽略大小写查询 Admin 和 admin 视为相同
日期时间date仅存储年月日日期'2026-05-11'
日期时间time仅存储时分秒时间'14:30:59'
日期时间timestamp日期 + 时间,不带时区'2026-05-11 14:30:59'
日期时间timestamptz日期 + 时间 + 时区,项目推荐优先使用'2026-05-11 14:30:59+08'
日期时间interval时间间隔类型,可存储几天、几小时等时间跨度'3 days'、'2 hours 30 min'
基础布尔boolean布尔类型,取值 true /falsetrue、false
特色高级json文本格式 JSON,读取解析慢,无索引优化'{""name"":""张三"",""age"":20}'
特色高级jsonb二进制 JSON,支持索引、查询高效,业务首选'{""status"":1,""list"":[1,2,3]}'
特色高级uuid全局唯一标识,分布式系统主键首选'550e8400-e29b-41d4-a716-446655440000'
特色高级array数组类型,支持 int []、text [] 等任意类型数组array[1,2,3]、array['男','女']
特色高级point坐标点类型,用于 GIS 地理位置存储'(116.40,39.90)' 经纬度
特色高级inet专门存储 IP 地址,自带 IP 校验和查询能力'192.168.1.100'
特色高级macaddr存储网卡 MAC 地址'08:00:2b:01:02:03'
特色高级tsvector全文检索专用分词类型,适配 PG 全文搜索'中文 全文 检索' 分词向量

列约束

约束类型描述示例
NOT NULL列不允许为 NULL,必须填写值name VARCHAR(50) NOT NULL
UNIQUE列的值必须唯一,不能重复phone VARCHAR(20) UNIQUE
PRIMARY KEY主键,唯一标识一行,等价 NOT NULL + UNIQUEid INT PRIMARY KEY
FOREIGN KEY外键,关联另一张表的主键user_id INT REFERENCES users(id)
CHECK自定义条件检查,满足条件才能写入age INT CHECK (age >= 18)
DEFAULT未指定值时,使用默认值status INT DEFAULT 1
SERIAL自增整数(简化主键写法)id SERIAL PRIMARY KEY

单表查询

postgresql
-- 查找所有 png 图片,ilike表示忽略大小写
select *
from "storage".file_details fd
where fd.file_name ilike '%png';

-- 查找 2026 年上传的文件
select *
from "storage".file_details fd
where fd.created_at >= '2026-01-01'
  and fd.created_at < '2027-01-01';

select *
from "storage".file_details fd
where EXTRACT(YEAR FROM fd.created_at) = 2026;
postgresql
-- tag中有一个是文档的
select *
from "storage".file_details fd
where '文档' = any (fd.tags);
-- tag中同时包含素材和封面
select *
from "storage".file_details fd
where fd.tags @> Array ['素材','封面'];
-- tag中包含素材或者封面
select *
from "storage".file_details fd
where fd.tags && Array ['素材','封面'];
postgresql
-- `->>`提取出来的是text,`->`提取出来的是json对象
-- 提取:查找作者是 "modify" 的文件
select *
from "storage".file_details fd
where fd.metadata ->> 'author' = 'modify';

-- 如果取出来的字段还是一个json,就得用->取了
select *
from "storage".file_details fd
where (fd.metadata -> 'video' -> 'resolution' ->> 'width') :: int = 1920;

-- 键值对存在性判断:查询所有定义了 "color" 属性的文件
select *
from "storage".file_details fd
where fd.metadata ? 'color';
postgresql
-- 精确IP匹配【略】
-- 范围匹配:查找属于 172.16.0.0/16 网段的所有上传记录
select *
from "storage".file_details fd
where fd.uploader_ip << '172.16.0.0/16';
postgresql
SELECT *
FROM "storage".file_details
ORDER BY created_at DESC
LIMIT 20 OFFSET 0;
postgresql
-- 统计每个 IP 上传了多少次,并计算每个 IP 上传文件包含的总标签数
SELECT uploader_ip,
       COUNT(*)                   AS upload_count,
       SUM(array_length(tags, 1)) AS total_tags
FROM "storage".file_details
GROUP BY uploader_ip;
postgresql
-- 格式化输出:ID 前 8 位 + 文件名 + 简写日期
SELECT left(detail_id::text, 8)          AS short_id,
       file_name,
       to_char(created_at, 'YYYY-MM-DD') AS simple_date
FROM "storage".file_details;

函数

函数分类函数写法功能说明简单示例
字符串函数concat(s1,s2,...)拼接多个字符串concat('张','三') → 张三
字符串函数concat_ws(sep,s1,s2)带分隔符拼接concat_ws('-','2026','05','11')
字符串函数length(str)获取字符串字符长度length('abc') → 3
字符串函数upper(str)转大写upper('abc') → ABC
字符串函数lower(str)转小写lower('ABC') → abc
字符串函数trim(str)去除首尾空格trim(' test ') → test
字符串函数ltrim(str)去除左侧空格ltrim(' abc') → abc
字符串函数rtrim(str)去除右侧空格rtrim('abc ') → abc
字符串函数substring(str from n for len)截取子串substring('12345' from 1 for 2) → 12
字符串函数replace(str,old,new)替换指定字符replace('a-b','-','') → ab
字符串函数position(sub in str)查找子串起始位置position('b' in 'abc') → 2
字符串函数split_part(str,sep,num)按分隔符拆分取值split_part('a,b,c',',',2) → b
数值函数abs(num)取绝对值abs(-10) → 10
数值函数round(num)四舍五入取整round(3.6) → 4
数值函数round(num,d)保留 d 位小数四舍五入round(3.1415,2) → 3.14
数值函数ceil(num)向上取整ceil(3.1) → 4
数值函数floor(num)向下取整floor(3.9) → 3
数值函数mod(a,b)取模 / 取余mod(10,3) → 1
数值函数power(a,b)计算 a 的 b 次方power(2,3) → 8
数值函数sqrt(num)开平方sqrt(16) → 4
数值函数random()生成 0~1 随机小数random() → 0.xxx
日期时间函数now()获取当前带时区时间戳now() → 2026-05-11 …
日期时间函数current_date获取当前年月日current_date → 2026-05-11
日期时间函数current_time获取当前时分秒current_time → 15:30:20
日期时间函数current_timestamp当前完整时间戳current_timestamp
日期时间函数date_part(单位,时间)提取年 / 月 / 日 / 时 / 分 / 秒date_part('year',now())
日期时间函数age(t1,t2)计算两个日期时间间隔age('2026-01-01','2025-01-01')
日期时间函数时间 + interval日期加减时间间隔now() + interval '3 day'
日期时间函数to_char(时间,格式)时间转格式化字符串to_char(now(),'yyyy-MM-dd')
日期时间函数to_timestamp(str)字符串转为时间戳to_timestamp('2026-05-11','yyyy-MM-dd')

多表关系

一对一

一对多

多对多

索引

创建一张表,插入1000000条测试数据,进行全表扫描看耗时:约0.036s

postgresql
CREATE TABLE cloud_files
(
    id         BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    file_name  TEXT NOT NULL,
    file_type  VARCHAR(20),
    created_at TIMESTAMPTZ DEFAULT NOW()
);
INSERT INTO cloud_files (file_name, file_type)
SELECT 'file_' || i || '.pdf',
       (ARRAY ['video', 'image', 'doc'])[floor(random() * 3) + 1]
FROM generate_series(1, 1000000) s(i);

SELECT *
FROM cloud_files
WHERE file_name = 'file_999999.pdf';

创建索引再看其耗时:约0.004s

postgresql
CREATE INDEX idx_file_name ON cloud_files (file_name);

其他类型的索引

postgresql
-- 显式指定或默认不写都是 B-Tree
CREATE INDEX idx_file_name ON cloud_files USING btree (file_name);

-- 仅适用于 = 查询
CREATE INDEX idx_file_hash ON cloud_files USING hash (file_name);

-- 注意字段顺序:(user_id, file_type) 遵循最左匹配原则
CREATE INDEX idx_user_file_type ON cloud_files (user_id, file_type);

-- 如果你的网盘支持“标签查询”或者存储了 JSON 格式的元数据,GIN 索引是神器。
-- 假设有一个 tags 字段(类型为 TEXT[])
CREATE INDEX idx_files_tags ON cloud_files USING gin (tags);

-- 假设有一个 meta_data 字段(类型为 JSONB)
CREATE INDEX idx_files_meta ON cloud_files USING gin (meta_data jsonb_path_ops);

-- 唯一索引
CREATE UNIQUE INDEX idx_file_md5 ON cloud_files (file_md5);

这些情况下索引会“失效”

失效场景原因说明示例
字段参与运算 / 函数操作索引列做计算、套函数,无法走索引where length(file_name) = 10、where id + 1 = 100
隐式类型转换字符串字段跟数字比较、类型不匹配,PG 自动转换导致失效where phone = 13800138000(phone 是 varchar)
模糊查询左匹配通配符% 或 _ 放开头,无法走普通 B 树索引where file_name like '%.pdf'
使用 or 连接无索引字段or 后面字段没建索引,整句索引失效where name='a' or age=20(age 无索引)
复合索引不满足最左前缀原则联合索引没用到最左侧列,后续列无法触发索引索引(a,b,c),查询where b=1 and c=2
使用 is not null大量非空数据时,优化器倾向全表扫描,不走索引where file_name is not null
使用 != / <> / not in反向排除条件,数据量大时易放弃索引走全表where status != 1、where id not in (1,2,3)
in 范围过大in 列表值太多,优化器认为全表扫描更快,放弃索引where id in (1,2,...1000+)
数据分布极度倾斜符合条件数据占比过高(通常超 20%),PG 觉得全表更快表里 90% 记录 status=1,查where status=1
索引列参与排序函数 / 分组不当order by、group by 字段没遵循索引顺序,导致失效索引(a,b),order by b
分区表跨分区查询无裁剪跨多个分区且无分区键过滤,索引优势丧失时间分区表,不带时间条件查全量数据
索引失效 / 臃肿、统计信息陈旧索引膨胀、长时间未 vacuum,统计信息不准,优化器选错计划大表长期增删改、未做 Analyze

事务 & 锁

CTE & 视图

CTE也就是相当于给原本的嵌套子查询做了一层抽离,如下代码:把一条特定的SQL结果作为一个变量进行存起来,后面就可以直接用这个变量

postgresql
WITH user_age_stats AS (SELECT *
                        FROM users
                        where age > 18)

SELECT *
FROM user_age_stats
WHERE sex = '男'

视图是一个虚拟表

postgresql
CREATE VIEW user_age_stats_view AS
SELECT *
FROM users
where age > 18

SELECT *
FROM user_age_stats_view
WHERE sex = '男';

CTE和视图的对比

对比维度CTE(公共表表达式)视图(View)
定义临时一次性结果集,WITH ... AS 定义存储在数据库中的虚拟表,永久保存定义
生命周期仅当前单条 SQL 执行内有效,执行完立即销毁创建后永久存在,手动删除才失效
存储方式不存数据,仅临时逻辑,实时计算不存物理数据,只存 SQL 定义,查询时实时执行
复用性只能当前 SQL 内复用,其他语句不能用全局可复用,任意 SQL、程序都能直接调用
能否加索引不能,临时结果集无法建索引不能,视图是虚拟表;可给底层基表建索引
适用场景递归查询、拆分复杂 SQL、层级树形结构、简化子查询封装常用复杂查询、统一口径、权限控制、简化开发
可修改性仅查询,一般不用于增删改可更新视图(满足条件),能通过视图改基表数据
依赖关系无依赖,随写随用依赖底层基表,基表删了视图会失效
性能特点每次执行重新生成结果集,适合单次复杂逻辑每次查询都执行封装 SQL,无缓存(物化视图除外)
语法示例WITH t AS (SELECT * FROM tb) SELECT * FROM t;CREATE VIEW v_tb AS SELECT * FROM tb;

用户 & 角色

PostgreSQL 里:用户本质就是一种带登录权限的角色

  • 角色(Role):默认不能登录,用来做权限分组、授权
  • 用户(User):就是可以登录的角色,专门给人 / 程序登录用
postgresql
CREATE ROLE managers;

CREATE USER modify WITH PASSWORD '123456';

GRANT managers TO modify;
postgresql
SELECT rolname     AS 角色名,
       rolcanlogin AS 能否登录,
       rolsuper    AS 是否超级管理员,
       rolcreatedb AS 能否建库
FROM pg_roles
WHERE rolname NOT LIKE 'pg_%';
postgresql
CREATE USER reader_user WITH PASSWORD '123456';

GRANT CONNECT ON DATABASE db TO reader_user; -- 可以进入db这个库
GRANT USAGE ON SCHEMA public TO reader_user;

GRANT SELECT ON ALL TABLES IN SCHEMA public TO reader_user;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO reader_user;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT SELECT ON TABLES TO reader_user;
postgresql
-- 针对表:赋予全部增删改查权限
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO feng_app;

-- 针对序列:必须给权限!否则无法执行自增 ID 的插入
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO feng_app;

-- 后续新创建的表也拥有权限
ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO feng_app;

备份 & 恢复

pg_dump单库备份

  • -U: 指定用户名(通常用超级用户 postgres)
  • -t: 只备份某张表(例如:-t cloud_files
  • -n: 只备份某个模式(例如:-n storage
  • -F: 指定格式。-F c 会生成自定义二进制压缩格式,体积更小,且支持并行恢复(推荐!)
postgresql
-- 格式:pg_dump -U [用户名] -d [数据库名] > [备份文件名.sql]
pg_dump -U postgres -d db > db_0512.sql
postgresql
-- 备份为压缩的二进制格式
pg_dump -U postgres -F c -d db > db_0512.bak
postgresql
-- 格式:psql -U [用户名] -d [目标数据库] < [备份文件]
psql -U postgres -d db1 < db_0512.sql
postgresql
pg_restore -U postgres -d db2 -c db_0512.bak

pg_dumpall全库备份

  • -f: 代表 file,即指定要运行的 SQL 脚本文件
  • postgres (最后的参数):这是连接的初始数据库。因为pg_dumpall脚本内部包含了CREATE DATABASE\c(切换库) 的命令,所以你只需要先连上一个默认存在的库(比如postgres),它会自动帮你创建并切换到其他的库
postgresql
pg_dumpall -U postgres > all_databases_backup.sql
postgresql
-- 格式:psql -U [用户名] -f [备份文件路径] [数据库名]
psql -U postgres -f all_databases_backup.sql postgres

全文搜索

查看有哪些扩展和已经安装了那些扩展

postgresql
SELECT *
FROM pg_available_extensions;

SELECT *
FROM pg_extension;

英文全文检索案例:

postgresql
-- 场景:搜索包含 "PostgreSQL" 或 "Guide" 的文章
-- 使用 'english' 分词配置
SELECT title, content
FROM posts
WHERE to_tsvector('english', title || ' ' || content) @@ to_tsquery('english', 'PostgreSQL | Guide');
  • to_tsvector:它会把一段话拆成一个个“词元”,并去掉“the”、“is”这种没意义的虚词
  • @@:这是全文搜索的操作符,表示“匹配”
  • &(与)、|(或)、!(非):可以在查询中使用逻辑判断

地理位置

向量数据库

By Modify.

选择字体进行切换