能当关系型数据库还能玩对象特性,能拆复杂查询还能自动管库存,PostgreSQL 凭什么这么香?

1.1 PostgreSQL的核心定位:对象-关系型数据库(ORDBMS)

如果你接触过数据库,一定听过"关系型数据库"(比如MySQL、Oracle)------它们用表、行、列 存储数据,用SQL语言操作。而PostgreSQL不一样,它是对象-关系型数据库(ORDBMS):既保留了关系型数据库的"规矩"(比如表结构、SQL标准),又融入了对象型数据库的"灵活"(比如自定义数据类型、方法)。

举个通俗的例子:如果要存储"几何图形"数据,传统关系型数据库可能会用多个字段(比如x1,y1,x2,y2存矩形),但PostgreSQL允许你自定义一个rectangle类型 ,直接存储矩形的坐标,还能给这个类型加个area()方法(计算面积)。这就是"对象"特性的体现------把"数据"和"操作数据的方法"打包在一起;而"关系"特性则是它依然遵守SQL规则,能用表、JOIN、外键这些传统玩法。

1.2 PostgreSQL的起源与发展:从伯克利到全球社区

PostgreSQL不是"凭空造出来的",它的祖先叫POSTGRES ,是1986年美国加州大学伯克利分校(UC Berkeley)的科研项目。当时的关系型数据库有个大痛点:无法处理复杂数据类型(比如几何图形、文档)。POSTGRES的目标就是解决这个问题------它首次提出"对象-关系"模型,允许用户自定义数据类型和函数。

1996年,POSTGRES的开源版本发布,改名为"PostgreSQL"(意为"PostgreSQL是POSTGRES的后继者")。从那以后,全球开发者一起维护它,现在已经更新到17版本(2024年),成为最流行的开源数据库之一

1.3 PostgreSQL的核心特性:为什么它能打?

PostgreSQL能火,不是因为"开源免费",而是因为它的"硬实力"------支持很多现代数据库的核心特性,而且做得很极致。我们逐一拆解:

1.3.1 复杂查询:搞定"绕弯子"的需求

PostgreSQL的查询能力堪称"全能",支持:

  • 多表JOIN :比如查"每个订单的用户姓名和商品名称",可以把orders(订单表)、users(用户表)、products(商品表)JOIN起来;
  • 子查询:比如查"工资高于平均工资的员工",先用子查询算出平均工资,再筛选;
  • CTE(Common Table Expressions) :用WITH语句把复杂查询拆成"临时表",比如查"每个部门的TOP3工资员工",用CTE先算每个部门的工资排名,再取前3。

举个电商系统的实际例子------热销商品统计

sql 复制代码
-- 第一步:用CTE算出每个商品的总销量
WITH product_sales AS (
  SELECT product_id, SUM(quantity) AS total_sold
  FROM orders
  GROUP BY product_id
)
-- 第二步:关联商品表,得到热销TOP10
SELECT p.name, ps.total_sold
FROM products p
JOIN product_sales ps ON p.id = ps.product_id
ORDER BY ps.total_sold DESC LIMIT 10;

这个查询用CTE拆解了复杂逻辑,PostgreSQL处理起来毫无压力。

1.3.2 事务完整性:保证数据"不出错"

你肯定遇到过"转账失败"的坑:从A账户扣了钱,B账户却没收到------这就是"事务不完整"。PostgreSQL支持ACID特性,彻底解决这个问题:

  • 原子性(Atomicity):事务里的操作要么全做,要么全不做。比如转账的两个步骤(扣钱、加钱),只要有一个失败,整个事务回滚;
  • 一致性(Consistency):事务前后数据要符合规则。比如你的账户余额不能是负数,PostgreSQL会自动检查;
  • 隔离性(Isolation):多个事务同时运行,互不干扰。比如你在查余额时,别人在转账,你看到的是转账前的余额;
  • 持久性(Durability):事务提交后,数据永远不会丢(即使服务器宕机)。

1.3.3 MVCC:让"读写"互不干扰

传统数据库里,"读"和"写"会互相阻塞------比如你在看数据时,别人不能改;别人在改数据时,你不能看。这在高并发场景下(比如秒杀、电商大促)会卡死。

PostgreSQL用**MVCC(多版本并发控制)**解决了这个问题:每个事务看到的数据是一个"快照"。比如:

  • 用户A在"读"订单表(看自己的订单);
  • 用户B在"写"订单表(修改订单状态);
  • 用户A看到的还是修改前的订单状态,直到用户B提交事务。

这样一来,"读"和"写"可以同时进行,不会阻塞,大大提高了并发性能。

1.3.4 外键、触发器:自动维护数据"规矩"

PostgreSQL还能帮你"自动做事",不用手动检查数据:

  • 外键(Foreign Key) :比如orders表的user_id必须指向users表的id。如果有人想插入一个不存在的user_id,PostgreSQL会直接拒绝,防止"无效订单";
  • 触发器(Trigger):比如当订单状态改为"完成"时,自动减少商品库存。你不用写代码调库存接口,PostgreSQL会帮你做:
sql 复制代码
-- 1. 创建触发器函数(要做的事情)
CREATE OR REPLACE FUNCTION update_inventory()
RETURNS TRIGGER AS $$
BEGIN
  -- 减少商品库存(NEW代表新插入的订单记录)
  UPDATE products
  SET stock = stock - NEW.quantity
  WHERE id = NEW.product_id;
  RETURN NEW; -- 返回新记录,继续执行插入操作
END;
$$ LANGUAGE plpgsql; -- 用PL/pgSQL写函数(PostgreSQL内置语言)

-- 2. 绑定触发器到orders表的INSERT操作
CREATE TRIGGER order_insert_trigger
AFTER INSERT ON orders -- 当插入订单后执行
FOR EACH ROW -- 每插入一行都执行
EXECUTE FUNCTION update_inventory(); -- 执行上面的函数

当你插入一个订单时,这个触发器会自动更新商品库存------是不是很省心?

1.4 PostgreSQL的"超能力":可扩展性

如果你觉得PostgreSQL的"默认功能"不够用,没关系------它允许你自定义几乎所有东西,堪称"数据库里的乐高":

1.4.1 自定义数据类型

比如你要存储"电话号码",可以定义一个phone_number类型:

sql 复制代码
-- 1. 创建自定义类型(包含国家码、区号、号码)
CREATE TYPE phone_number AS (
  country_code VARCHAR(3), -- 国家码(比如+86)
  area_code VARCHAR(3),    -- 区号(比如010)
  number VARCHAR(8)        -- 号码(比如12345678)
);

-- 2. 用这个类型建表
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100),
  phone phone_number -- 使用自定义类型
);

-- 3. 插入数据(用ROW()包裹自定义类型的值)
INSERT INTO users (name, phone)
VALUES ('张三', ROW('+86', '010', '12345678'));

-- 4. 查询数据(用.(字段名)取自定义类型的值)
SELECT name, (phone).country_code || '-' || (phone).area_code || '-' || (phone).number AS full_phone
FROM users;

1.4.2 自定义函数

PostgreSQL支持用多种语言写函数(比如PL/pgSQL、Python、Java)。比如你要计算两个点之间的距离,可以写一个函数:

sql 复制代码
-- 1. 定义点类型(PostgreSQL内置了geometry类型,但这里用自定义类型举例)
CREATE TYPE point AS (x INT, y INT);

-- 2. 定义计算距离的函数
CREATE OR REPLACE FUNCTION distance(p1 point, p2 point)
RETURNS FLOAT AS $$ -- 返回浮点型(距离)
BEGIN
  -- 勾股定理计算距离:√[(x1-x2)² + (y1-y2)²]
  RETURN SQRT(POW(p1.x - p2.x, 2) + POW(p1.y - p2.y, 2));
END;
$$ LANGUAGE plpgsql;

-- 3. 使用函数(计算(0,0)到(3,4)的距离)
SELECT distance(ROW(0,0)::point, ROW(3,4)::point); -- 返回5.0

1.4.3 自定义索引

如果默认的B-tree索引不够用(比如要查"距离某点1公里内的商店"),PostgreSQL允许你用GiSTGIN索引。比如用GiST索引加速几何查询:

sql 复制代码
-- 1. 创建存储商店位置的表(用PostgreSQL内置的geometry类型)
CREATE TABLE shops (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100),
  location geometry(Point, 4326) -- Point代表点,4326是坐标系(WGS84)
);

-- 2. 添加GiST索引(加速几何查询)
CREATE INDEX shops_location_idx ON shops USING GIST (location);

-- 3. 查询"天安门附近1公里内的商店"(116.403874, 39.914885是天安门的坐标)
SELECT name
FROM shops
WHERE ST_DWithin(
  location, 
  ST_SetSRID(ST_MakePoint(116.403874, 39.914885), 4326), -- 天安门的点
  1000 -- 距离(米)
);

1.5 课后Quiz:巩固你的理解

Q1:PostgreSQL作为ORDBMS,"对象"特性主要体现在哪里?
A1 :支持用户自定义数据类型、函数、运算符等"对象"特性。比如你可以定义phone_number类型(包含国家码、区号、号码),或定义distance函数(像"对象的方法"一样计算距离)。

Q2:MVCC(多版本并发控制)解决了什么问题?
A2:解决了"读"和"写"互相阻塞的问题。每个事务看到的是数据的"快照",比如你在查订单时,别人可以修改订单,你看到的还是修改前的状态,直到别人提交事务。高并发场景下(比如秒杀),读写可以同时进行,不会卡死。

Q3:PostgreSQL的触发器能做什么?举个例子。
A3:触发器可以在表发生插入、更新、删除时自动执行操作。比如当订单状态改为"完成"时,自动减少商品库存;或当用户注册时,自动发送欢迎邮件(需要结合外部服务)。

1.6 常见报错及解决

报错1:ERROR: relation "table_name" does not exist

原因

  • 表名拼写错误(比如把orders写成order);
  • 表不在当前schema下(比如表在sales schema里,但你用了public schema);
  • 用户没有访问表的权限。

解决办法

  1. 检查表名拼写:用\dt命令(psql客户端)查看当前schema下的表;
  2. 指定schema:比如SELECT * FROM sales.orders;(如果表在sales schema里);
  3. 授予权限:用GRANT SELECT ON orders TO your_user;给用户读权限。

预防

  • 用全小写、下划线分隔的命名规范(比如user_orders而不是UserOrders);
  • 总是指定schema(比如public.orders);
  • 给用户最小必要的权限(比如只读用户只给SELECT权限)。

报错2:ERROR: insert or update on table "orders" violates foreign key constraint "orders_user_id_fkey"

原因 :你插入的user_idusers表中不存在(比如users表没有id=100的用户,但你插入了user_id=100的订单)。

解决办法

  1. 检查user_id是否存在:SELECT * FROM users WHERE id = 100;
  2. 如果不存在,先插入用户,再插入订单。

预防

  • 用外键约束(PostgreSQL会自动检查);
  • 插入数据前先验证关联ID的存在。

参考链接

  1. What Is PostgreSQL?:www.postgresql.org/docs/17/int...
  2. Data Types:www.postgresql.org/docs/17/dat...
  3. Triggers:www.postgresql.org/docs/17/tri...
  4. MVCC:www.postgresql.org/docs/17/mvc...
相关推荐
生无谓2 小时前
拦截器和过滤器的区别
后端
Cache技术分享2 小时前
194. Java 异常 - Java 异常处理之多重捕获
前端·后端
张高培是我的爸爸2 小时前
(不用看视频)一文搞懂maven基础以及高级用法
后端
福大大架构师每日一题2 小时前
Rust 1.90.0 发布:新特性、编译器改进与兼容性更新详解
后端
BingoGo2 小时前
phpkg 让 PHP 摆脱 Composer 依赖地狱
后端·php
许雪里2 小时前
XXL-TOOL v2.1.0 发布 | Java工具类库
后端·github·代码规范
CodeWolf3 小时前
面试题之Redis的穿透、击穿和雪崩问题
redis·后端·面试
vker3 小时前
第 2 天:工厂方法模式(Factory Method Pattern)—— 创建型模式
java·后端·设计模式
我不是混子3 小时前
什么是MySQL的回表?
后端·mysql