理解 SQLite 的锁模型、它的事务,以及 SQL 标准所描述的隔离异常。
在 Kodokon 中打开本课SQLite 在整个数据库文件这一级别加锁,而不是在行这一级别。在默认模式(回滚日志)下,读者共享一个 SHARED 锁,但每次只能有一个写者获得 EXCLUSIVE 锁。结果就是:写操作被串行化了。让我们创建一张表来配合事务操作。
CREATE TABLE accounts (
id INTEGER PRIMARY KEY,
balance INTEGER NOT NULL
);
INSERT INTO accounts (id, balance)
VALUES (1, 100), (2, 50);
-- Read: deferred transaction (default)
BEGIN;
SELECT balance FROM accounts WHERE id = 1;
COMMIT;锁分阶段演进:UNLOCKED,然后是 SHARED(读)、RESERVED(有写意图)、PENDING,最后是 EXCLUSIVE(写)。如果一个事务已经持有写锁,另一个事务就会收到 SQLITE_BUSY 错误。与其立刻失败,不如用 busy_timeout 设置一个等待时长,并启用 WAL 模式,让读者和写者能够并行推进。
-- A single writer, concurrent readers
PRAGMA journal_mode = WAL;
-- Wait up to 5 s if the database is busy
PRAGMA busy_timeout = 5000;SQL 标准描述了三种异常:脏读(读取到未提交的数据)、不可重复读(重新读取同一行却看到不同的值),以及幻读(重放一个范围查询,看到有新行出现)。由于 SQLite 把写者串行化,它实际上表现为 SERIALIZABLE:这些异常不会出现(在使用了 read_uncommitted 的共享缓存模式之外)。在其他数据库中,你要显式地选择隔离级别。
-- Atomic transfer: immediate write lock
BEGIN IMMEDIATE;
UPDATE accounts
SET balance = balance - 10
WHERE id = 1;
UPDATE accounts
SET balance = balance + 10
WHERE id = 2;
COMMIT;BEGIN 和 BEGIN IMMEDIATE 之间有什么区别?