pgsql-事务与锁
事务 用最通俗的话说,事务就是:“要么全做完,要么一点都不做,绝不允许停在半路。” 场景:用户花 100 元买 VIP。如果钱扣了,结果数据库断电了,会员没加上 -- 准备表 CREATE TA
pgsql-事务与锁
发布时间:2026-04-16 (1天前)

事务

用最通俗的话说,事务就是:“要么全做完,要么一点都不做,绝不允许停在半路。”

场景:用户花 100 元买 VIP。如果钱扣了,结果数据库断电了,会员没加上

-- 准备表
CREATE TABLE accounts (
    username TEXT PRIMARY KEY,
    balance DECIMAL(10, 2)
);

CREATE TABLE members (
    username TEXT PRIMARY KEY,
    expire_date DATE
);

INSERT INTO accounts VALUES ('小红', 150.00);
INSERT INTO members VALUES ('小红', '2026-01-01');
BEGIN; -- 开启事务,立下“生死状”

-- 1. 扣钱
UPDATE accounts SET balance = balance - 100 WHERE username = '小红';

-- 2. 增加会员时长 (假设这里 SQL 写错了或网络断了)
UPDATE members SET expire_date = expire_date + INTERVAL '1 year' WHERE username = '小红';

-- 检查一下:如果钱扣对了,会员也加了
COMMIT; -- 确认无误,正式写入硬盘

如果中途出错了

ROLLBACK; -- 刚才的所有操作全部撤销,钱会回到小红账上,像没发生过一样

还可以在中途设置存档点

BEGIN;             -- 开启事务
UPDATE ...;        -- 执行业务逻辑
SAVEPOINT my_sp;   -- (可选) 设置保存点,类似于单机游戏的存档
UPDATE ...;
ROLLBACK TO my_sp; -- (可选) 发现错了,回滚到存档点
COMMIT;            -- 提交,所有改动生效

ACID

不管是 PG 还是其他数据库,事务都必须遵守ACID 原则:

  • A (Atomicity) 原子性:就是你说的“要么全做,要么不做”。
  • C (Consistency) 一致性:数据必须从一个合法状态变到另一个合法状态(比如余额不能变成负数)。
  • I (Isolation) 隔离性重点! 多个用户同时操作时,他们之间不能互相干扰。
  • D (Durability) 持久性:只要你点了COMMIT,就算立马拔掉电源,数据也已经写死在硬盘里了。

在 PostgreSQL 中,锁是确保数据库并发安全(Consistency & Isolation)的核心机制。简单来说,它防止了多个用户同时修改同一条数据而导致的混乱。

PostgreSQL 的锁体系非常庞大,主要可以分为以下三个层次

1. 表级锁 (Table-Level Locks)

即使你只是对表进行简单的SELECTUPDATE,PostgreSQL 也会自动加上表级锁。

  • 常见的锁模式:
    • Access Share (访问共享锁):SELECT 时自动获取,允许其他事务读写,但不允许修改表结构。
    • Row Exclusive (行排他锁):INSERTUPDATEDELETE 时自动获取。
    • Share (共享锁): 常用于创建索引,允许读,禁止改。
    • **Access Exclusive (访问排他锁):**最强锁。ALTER TABLEDROP TABLETRUNCATE时触发。它会阻塞一切操作(包括 SELECT)

2. 行级锁 (Row-Level Locks)

行级锁主要用于处理具体的数据记录。它们不在内存中维护列表,而是直接在数据页中标记。

  • FOR SHARE: 保证你读取的行不会被别人修改或删除,直到你提交事务。
  • **FOR UPDATE:**锁定行以进行更新。其他事务无法对这些行进行UPDATEDELETESELECT FOR UPDATE
  • FOR NO KEY UPDATE / FOR KEY SHARE: 更细粒度的锁,主要为了优化外键关联时的并发性能。

3. 建议锁 (Advisory Locks)

这是 PostgreSQL 的一大特色。它不是由数据库引擎自动加的,而是由程序员定义的一种逻辑锁。

  • 用途: 比如你想锁定“发送邮件”这个动作,或者确保某个后台任务在同一时间只有一个实例在运行,就可以用建议锁。
  • 特点: 它不锁定具体的表或行,只是在内存里占一个“标识位”。

如何使用?

自动锁定

绝大多数情况下,你不需要手动加锁。

  • 执行UPDATE users SET name = 'Gemini' WHERE id = 1; 时,数据库会自动给该行加排他锁。

手动锁定表

如果你需要进行大面积维护,不希望别人干扰:

BEGIN;
-- 需要把这个锁放到事务里面
LOCK TABLE my_table IN ACCESS EXCLUSIVE MODE; -- 彻底锁死表
-- 执行操作
COMMIT;

手动锁定行(解决“超卖”等问题)

这是处理高并发业务(如扣减库存)时的标准写法:

BEGIN;
-- 锁定 id 为 100 的行,别人必须等我处理完
SELECT * FROM products WHERE id = 100 FOR UPDATE; 

UPDATE products SET stock = stock - 1 WHERE id = 100;
COMMIT;

别的连接只能进行普通的select查询,不能进行更新或者删除操作

使用建议锁

-- 获取一个编号为 12345 的逻辑锁
SELECT pg_advisory_lock(12345);

-- 执行你的业务逻辑...

-- 释放锁
SELECT pg_advisory_unlock(12345);

如何查看当前的锁?

如果发现数据库“卡住了”,可以用这张表查谁在锁谁:

SELECT * FROM pg_locks l 
JOIN pg_stat_activity a ON l.pid = a.pid;