星云点击:星空遥控器
120.47M · 2026-02-04
在回答这个问题前,我们先看一段代码。
import sqlite3
import os
DB = "test.db"
def setup_db():
if os.path.exists(DB):
os.remove(DB)
conn = sqlite3.connect(DB)
# conn.execute("PRAGMA journal_mode=WAL;") # 显式开启 WAL
conn.execute("CREATE TABLE data (id INTEGER);")
conn.executemany("INSERT INTO data VALUES (?)", [(i,) for i in range(3)])
conn.commit()
conn.close()
def long_writer():
conn = sqlite3.connect(DB, timeout=10)
cur = conn.cursor()
conn.execute("BEGIN EXCLUSIVE") # 显式开启独占事务,确保持有写锁
cur.execute("INSERT INTO data VALUES (?)", (999,))
reader()
conn.commit()
conn.close()
def reader():
conn = sqlite3.connect(DB, timeout=10)
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM data")
conn.close()
print("=" * 50)
setup_db()
long_writer()
print("n 测试完成")
这段代码的操作内容很简单,就是:
sqlite 数据库可能你会觉得正常不会有人这么玩的,谁会在一个数据库连接中还建立第二个连接呢? 个人理解,正常开发确实不会这样玩;但是在高度模块化开发的过程中,则完全有可能。甚至于读操作可能是开发者A开发,写操作可能是开发者B开发,因而存在上述场景。
而上述调用,使用 sqlite 数据库,你会看到下述报错:
==================================================
Traceback (most recent call last):
File "D:CodeProjectotherPythonProjectsqlite 2_writer_demo.py", line 33, in <module>
long_writer()
File "D:CodeProjectotherPythonProjectsqlite 2_writer_demo.py", line 21, in long_writer
reader()
File "D:CodeProjectotherPythonProjectsqlite 2_writer_demo.py", line 28, in reader
cur.execute("SELECT COUNT(*) FROM data")
sqlite3.OperationalError: database is locked
用过类似 MySQL、PostgreSQL 之类的数据库的同学可能会有疑问:为啥会报错呢?
究其原因:sqlite 使用的是粗粒度的文件锁,而不是 MySQL 之类的行锁/表锁,而正常模式下不支持并发操作的,存在并发操作时,会报错database is locked
像上述代码中,一个写连接已经占用一个线程了,如果再建立一个连接,那么相当于建立两个连接,也就是并发操作,自然就锁表了。
既然不支持多连接,那就合并方法呗,把代码中的 reader() 方法内容直接拷贝到 long_writer()。
确实如此,上述方式不失为一种方法。当然还有另一种方式,也就是今天要介绍的 Wal 模式。
具体操作呢,就是将上述代码中注释的那一行取消注释即可。
conn.execute("PRAGMA journal_mode=WAL;") # 显式开启 WAL
就这么简单,然后运行不报错,也写入成功。
看到这里,你肯定会有疑问:前面不是说 sqlite 只支持文件锁嘛,而 sqlite 的数据库文件就一个,是怎么做到单个连接操作这个文件的时候,另外还能建立连接并发操作这个文件而不报错加锁问题呢?
开启 Wal 模式,简单理解:其实相当于读锁和写锁分离,写入过程中加锁,是不会影响读取的。具体实现上:
懂了吧,也就是说只要开启 Wal 模式,对于读多写少的场景,再也不会因为少量写操作导致单次请求,读操作被阻塞,响应过慢了。
Wal 模式是不是完全解决了 sqlite 的并发问题呢?
并发操作无外乎读读操作、读写操作、写写操作,你可以在上述代码中模仿写方法里面再嵌套写方法、读方法嵌套读方法实践试试~ 这里就不解答了,给你留个实践的机会呢!
上述代码中,为啥要加 conn.execute("BEGIN EXCLUSIVE") 呢?
实际上,不加的话,你会发现:非 Wal 模式也不会报错了。这是因为 sqlite 默认自动提交会立即释放锁,也就是写操作完成自动提交的瞬间会释放锁,从而不会阻塞读。但是阻塞本身在非 Wal 模式是存在的,只有在耗时写操作或高频读操作时,这种阻塞才会很明显。BEGIN EXCLUSIVE 会显式开启独占事务,以便模拟这种感知。
好啦,今天的分享就到这里了,感谢阅读,我是唐叔,欢迎三连哦~