PostgreSQL 事务管理

PostgreSQLBeginner
立即练习

简介

在本实验中,你将了解 PostgreSQL 事务管理,这是确保数据完整性的关键环节。你将学习如何开始和提交事务,把一系列操作作为一个完整的工作单元处理。你还将学习如何回滚失败的事务、设置隔离级别,以及通过并发更新模拟锁。

开始并提交事务

本步骤将介绍如何在 PostgreSQL 中开始并提交事务。事务会把一系列操作作为一个完整的工作单元处理,从而确保数据完整性。如果事务中的任何操作失败,整个事务都会回滚,避免只更新一部分数据并保持数据一致性。

首先,以 postgres 用户身份连接 PostgreSQL 数据库。打开终端并运行以下命令:

sudo -u postgres psql

此时应看到 postgres=# 提示符。

接下来,创建一个名为 accounts 的表,用于演示事务。

CREATE TABLE accounts (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    balance DECIMAL(10, 2)
);

向 accounts 表中插入一些初始数据。

INSERT INTO accounts (name, balance) VALUES ('Alice', 100.00);
INSERT INTO accounts (name, balance) VALUES ('Bob', 50.00);

现在,使用 BEGIN 命令开始一个事务。

BEGIN;

在事务中,从 Alice 的账户向 Bob 的账户转入 $20。

UPDATE accounts SET balance = balance - 20.00 WHERE name = 'Alice';
UPDATE accounts SET balance = balance + 20.00 WHERE name = 'Bob';

要使这些更改永久生效,请使用 COMMIT 命令提交事务。

COMMIT;

查询 accounts 表,确认事务是否成功。

SELECT * FROM accounts;

你应该能看到 Alice 的余额减少了 $20,Bob 的余额增加了 $20。

PostgreSQL 事务示例输出

最后,退出 psql shell。

\q

回滚失败的事务

本步骤将介绍如何在 PostgreSQL 中回滚失败的事务。当一系列数据库操作中出现错误时,回滚事务至关重要,它可以确保数据库保持一致状态。

你应该仍然连接着上一步使用的 PostgreSQL 数据库。如果已经断开连接,请重新运行以下命令:

sudo -u postgres psql

开始一个新事务。

BEGIN;

在这个事务中,尝试执行一个会故意失败的操作:插入重复的主键。先查询下一个可用的 id 值。

SELECT MAX(id) FROM accounts;

假设查询结果为 2。现在,尝试插入一条 id = 1 的新账户记录,因为这个 ID 已经存在,所以插入会失败。

INSERT INTO accounts (id, name, balance) VALUES (1, 'Eve', 25.00);

运行这条命令会出现 ERROR: duplicate key value violates unique constraint "accounts_pkey" 错误。

事务中出现错误后,使用 ROLLBACK 命令回滚事务,丢弃事务中所做的更改。

ROLLBACK;

查询 accounts 表,确认 ROLLBACK 是否成功。

SELECT * FROM accounts;

你应该能看到,表中仍然只有 Alice 和 Bob 的账户,余额与步骤 1 结束时相同。失败的 INSERT 操作已成功回滚。

PostgreSQL 事务回滚示例

最后,退出 psql shell。

\q

设置并测试隔离级别

本步骤将介绍 PostgreSQL 中的事务隔离级别,以及如何设置和测试它们。隔离级别决定了并发事务之间相互隔离的程度。较高的隔离级别能更好地防止数据损坏,但可能会降低并发性。

你应该仍然连接着上一步使用的 PostgreSQL 数据库。如果已经断开连接,请重新运行以下命令:

sudo -u postgres psql

打开两个独立的终端窗口。在每个终端中,都以 postgres 用户身份连接 PostgreSQL 数据库。此时应该能看到两个 postgres=# 提示符。

终端 1:

sudo -u postgres psql

终端 2:

sudo -u postgres psql

在终端 1中开始一个事务,然后将隔离级别设为 READ COMMITTED(默认级别)。SET TRANSACTION 必须在事务中、执行第一条查询之前运行。

BEGIN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

在终端 1中查询 Alice 的余额。

SELECT balance FROM accounts WHERE name = 'Alice';

记下查询结果。接着,在终端 2中开始一个事务并更新 Alice 的余额。

BEGIN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
UPDATE accounts SET balance = 90.00 WHERE name = 'Alice';
COMMIT;

回到终端 1,再次查询 Alice 的余额。

SELECT balance FROM accounts WHERE name = 'Alice';

由于隔离级别是 READ COMMITTED,你会看到终端 2 已提交的更新后余额(90.00)。

READ COMMITTED 下 Alice 的余额已更新

现在测试 REPEATABLE READ 隔离级别。在终端 1中,先回滚当前事务,再开始一个新事务,并在读取任何数据之前设置隔离级别。

ROLLBACK;
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

在终端 1中再次查询 Alice 的余额。

SELECT balance FROM accounts WHERE name = 'Alice';

记下查询结果。接着,在终端 2中开始一个事务,再次更新 Alice 的余额。

BEGIN;
UPDATE accounts SET balance = 100.00 WHERE name = 'Alice';
COMMIT;

回到终端 1,再次查询 Alice 的余额。

SELECT balance FROM accounts WHERE name = 'Alice';

由于隔离级别是 REPEATABLE READ,你仍会看到 90.00。这是该事务中第一条查询建立的快照所记录的余额,即使终端 2 已提交了 100.00 也一样。

最后,在终端 1中提交事务。

COMMIT;

现在,在终端 1中再次查询 Alice 的余额。事务结束后,你会看到最新提交的值(100.00)。

SELECT balance FROM accounts WHERE name = 'Alice';

退出两个 psql shell。

\q

通过并发更新模拟锁

本步骤将通过 PostgreSQL 中的并发更新模拟锁。锁用于防止并发事务相互干扰,从而确保数据完整性。

你应该仍然连接着上一步使用的 PostgreSQL 数据库。如果已经断开连接,请重新运行以下命令:

sudo -u postgres psql

打开两个独立的终端窗口。在每个终端中,都以 postgres 用户身份连接 PostgreSQL 数据库。此时应该能看到两个 postgres=# 提示符。

终端 1:

sudo -u postgres psql

终端 2:

sudo -u postgres psql

在终端 1中开始一个事务并查询 Alice 的余额。关键是使用 SELECT ... FOR UPDATE 锁定该行。

BEGIN;
SELECT balance FROM accounts WHERE name = 'Alice' FOR UPDATE;

这条命令会读取 Alice 的余额并锁定对应的行。在该事务提交或回滚之前,其他事务无法修改这一行。

在终端 2中开始一个事务,并尝试更新 Alice 的余额。

BEGIN;
UPDATE accounts SET balance = balance + 10 WHERE name = 'Alice';

注意,终端 2中的命令看起来会一直卡住。这是因为它正在等待终端 1释放持有的行锁。

现在,在终端 1中提交事务。

COMMIT;

终端 1提交事务后,终端 2中的 UPDATE 命令就会继续执行。

在终端 2中提交事务。

COMMIT;

现在,在任一终端中查询 accounts 表,确认更改。

SELECT * FROM accounts;

你应该能看到,终端 1 释放行锁后,终端 2 中的事务更新了 Alice 的余额。

PostgreSQL 并发更新模拟

最后,退出两个 psql shell。

\q

这个示例展示了如何使用 SELECT ... FOR UPDATE 模拟锁,防止并发更新相互干扰。如果不使用锁,两个事务可能会读取相同的初始余额,并基于该值分别应用更新,从而导致更新丢失。

总结

在本实验中,你学习了如何管理 PostgreSQL 事务。首先,你使用 psql 连接 PostgreSQL 数据库,并创建了一个包含初始数据的示例 accounts 表。

接着,你通过示例了解了事务的用法:使用 BEGIN 命令开始事务,执行多项数据库操作(更新 Alice 和 Bob 的余额),然后使用 COMMIT 命令提交事务,使更改永久生效。这说明了事务的基本原则:将一系列操作作为一个原子单元处理。你还学习了如何回滚事务和设置隔离级别。最后,你通过并发更新模拟锁,了解如何防止数据损坏。