介绍
在这个实验中,我们将探索 SQLite 事务处理,重点是通过使用事务来维护数据完整性。我们将学习如何开始和提交事务,确保将多个相关操作视为一个单一的、原子性的单元。
本实验将指导你完成使用 BEGIN TRANSACTION 启动事务、对数据库进行更改,然后使用 COMMIT 语句永久保存这些更改的过程。你还将学习如何使用 ROLLBACK 撤消更改,以及使用 SAVEPOINT 进行更精细的控制。这种实践经验将巩固你对事务如何在 SQLite 中保证原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)(ACID 属性)的理解。
创建数据库和表
在第一步中,我们将创建一个 SQLite 数据库和一个用于存储用户数据的表。这将为后续步骤中探索事务处理奠定基础。
首先,在 LabEx 虚拟机(VM)中打开你的终端。你的默认路径是 /home/labex/project。
现在,让我们创建一个名为 mydatabase.db 的 SQLite 数据库。运行以下命令来创建数据库文件并打开 SQLite 命令行工具:
sqlite3 mydatabase.db
你将看到一个提示,表明你现在位于 SQLite shell 中:
SQLite version 3.x.x
Enter ".help" for usage hints.
sqlite>
接下来,创建一个名为 users 的表来存储基本用户信息。该表将包含三列:id(唯一标识符)、name 和 balance。在 sqlite> 提示符下输入以下 SQL 命令,然后按 Enter 键:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
balance REAL
);
此命令设置 users 表,其中:
id是一个整数,用作每个用户的主键(primary key)。name是一个文本字段,表示用户的姓名。balance是一个实数,表示用户的账户余额。
现在,将一些初始数据插入到 users 表中:
INSERT INTO users (name, balance) VALUES ('Alice', 100.0);
INSERT INTO users (name, balance) VALUES ('Bob', 200.0);
这些命令添加了两个用户,Alice 和 Bob,他们的初始余额分别为 100.0 和 200.0。
为了确认数据已正确添加,请运行以下命令以查看表中的所有记录:
SELECT * FROM users;
预期输出:
1|Alice|100.0
2|Bob|200.0
此输出显示每个记录的 id、name 和 balance。SELECT * 命令检索指定表中的所有列。
开始并提交事务
在这一步中,你将学习如何开始和提交事务。事务对于在执行多个相关操作时确保数据完整性至关重要。
要开始一个事务,请使用 BEGIN TRANSACTION 命令:
BEGIN TRANSACTION;
此命令告诉 SQLite 开始跟踪更改,但在你显式提交事务之前,不会将这些更改永久应用到数据库。
现在,让我们从 Alice 转账 50.0 给 Bob:
UPDATE users SET balance = balance - 50.0 WHERE name = 'Alice';
UPDATE users SET balance = balance + 50.0 WHERE name = 'Bob';
这些命令在事务中更新 Alice 和 Bob 的余额。Alice 的余额减少 50.0,而 Bob 的余额增加 50.0。
要保存更改,请使用 COMMIT 命令:
COMMIT;
此命令提交事务,使更改永久生效。
通过再次查询表来验证更改:
SELECT * FROM users;
预期输出:
1|Alice|50.0
2|Bob|250.0
此输出确认事务已成功提交,并且 Alice 和 Bob 的余额已相应更新。
回滚事务
在这一步中,你将学习如何使用 ROLLBACK 命令来撤销在事务期间所做的更改。这对于处理错误和确保数据完整性至关重要。
首先,开始一个新的事务:
BEGIN TRANSACTION;
现在,让我们尝试从 Alice 转账 100.0 给 Bob,但这次,我们将模拟一个错误情况:
UPDATE users SET balance = balance - 100.0 WHERE name = 'Alice';
UPDATE users SET balance = balance + 100.0 WHERE name = 'Bob';
由于 Alice 只有 50.0,因此第一个更新将导致负余额。虽然 SQLite 默认情况下不强制执行约束(constraints),但我们将在该操作之后模拟发生错误。
要撤消这些更改,请使用 ROLLBACK 命令:
ROLLBACK;
此命令回滚事务,撤消自 BEGIN TRANSACTION 语句以来所做的任何更改。
通过查询 users 表来验证更改是否已回滚:
SELECT * FROM users;
预期输出:
1|Alice|50.0
2|Bob|250.0
此输出确认事务已成功回滚,并且 Alice 和 Bob 的余额保持不变。
实现保存点 (Savepoints)
在这一步中,你将学习如何在事务中使用保存点(savepoints)。保存点允许你在事务中创建中间点,你可以回滚(rollback)到这些点,而无需回滚整个事务。
首先,开始一个新的事务:
BEGIN TRANSACTION;
创建一个名为 savepoint1 的保存点:
SAVEPOINT savepoint1;
现在,让我们从 Alice 转账 20.0 给 Bob:
UPDATE users SET balance = balance - 20.0 WHERE name = 'Alice';
UPDATE users SET balance = balance + 20.0 WHERE name = 'Bob';
创建另一个名为 savepoint2 的保存点:
SAVEPOINT savepoint2;
让我们添加一个名为 'Charlie' 的新用户,初始余额为 300.0:
INSERT INTO users (name, balance) VALUES ('Charlie', 300.0);
现在,假设我们认为添加 Charlie 是一个错误。我们可以回滚到 savepoint1,这将撤消 INSERT 语句以及 Alice 和 Bob 之间 20.0 的转账:
ROLLBACK TO SAVEPOINT savepoint1;
验证回滚到 savepoint1 后的更改:
SELECT * FROM users;
预期输出:
1|Alice|50.0
2|Bob|250.0
你应该看到 Charlie 不在表中,并且 Alice 和 Bob 的余额恢复到 savepoint1 之前的值。
现在,让我们从 Alice 转账 10.0 给 Bob:
UPDATE users SET balance = balance - 10.0 WHERE name = 'Alice';
UPDATE users SET balance = balance + 10.0 WHERE name = 'Bob';
最后,提交事务:
COMMIT;
添加约束以防止余额为负
在本步骤中,你将向 users 表添加约束,以防止余额为负数。这将有助于确保数据完整性,防止无效交易。
由于 SQLite 不支持使用 ALTER TABLE ADD CONSTRAINT 向现有表添加约束,因此我们需要重新创建该表,并包含该约束。首先,启动一个事务,以确保所有操作都是原子性的:
BEGIN TRANSACTION;
创建一个具有相同结构但包含 CHECK 约束的新表:
CREATE TABLE users_new (
id INTEGER PRIMARY KEY,
name TEXT,
balance REAL CHECK (balance >= 0)
);
将所有数据从旧表复制到新表:
INSERT INTO users_new SELECT * FROM users;
删除旧表:
DROP TABLE users;
将新表重命名为原始名称:
ALTER TABLE users_new RENAME TO users;
提交事务,使更改永久生效:
COMMIT;
现在,users 表具有一个 CHECK 约束,确保 balance 列始终大于或等于 0。
让我们尝试从 Alice 转账 1000 元给 Bob,这将违反该约束:
BEGIN TRANSACTION;
UPDATE users SET balance = balance - 1000 WHERE name = 'Alice';
这次,UPDATE 语句 将 抛出错误,因为它违反了 CHECK 约束。你将看到一条类似于这样的错误消息:Error: CHECK 约束失败: balance >= 0。
回滚事务:
ROLLBACK;
验证更改已回滚:
SELECT * FROM users;
预期输出:
1|Alice|40.0
2|Bob|260.0
这证实 ROLLBACK 成功地将数据库恢复到失败事务之前的原始状态。
总结
在这个实验(lab)中,你已经学习了 SQLite 中事务处理的基础知识。你已经了解了如何开始和提交事务、回滚更改、实现保存点(savepoints)以进行更精细的控制,以及添加约束(constraints)以确保数据完整性。这些技能对于构建健壮且可靠的数据库应用程序至关重要。


