为什么 SQLite 似乎没有锁定我的 table / 行?

Why doesn't SQLite appear to lock my table / row?

我正在修补内存中的 sqlite 数据库,试图解决并发问题。这是一个使用 SQLAlchemy

的例子
from sqlalchemy import create_engine, text

# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=True)

# Do stuff
with engine.connect() as conn1, engine.connect() as conn2:
    conn1.execute(text("CREATE TABLE leagues (id INTEGER PRIMARY KEY, name VARCHAR(255) NOT NULL);"))
    conn1.execute(text("INSERT INTO leagues (name) VALUES ('A')"))

    conn1.execute(text("UPDATE leagues SET name = name || '1'"))  # append 1 to the name
    conn2.execute(text("UPDATE leagues SET name = name || '2'"))  # append 2 to the name
    conn1.execute(text("UPDATE leagues SET name = name || '1'"))  # append 1 to the name

    conn1.commit()
    conn2.commit()

    result = conn2.execute(text("SELECT name FROM leagues"))
    print(result.all())

输出

2021-10-27 16:19:25,472 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-10-27 16:19:25,473 INFO sqlalchemy.engine.Engine CREATE TABLE leagues (id INTEGER PRIMARY KEY, name VARCHAR(255) NOT NULL);
2021-10-27 16:19:25,473 INFO sqlalchemy.engine.Engine [generated in 0.00058s] ()
2021-10-27 16:19:25,474 INFO sqlalchemy.engine.Engine INSERT INTO leagues (name) VALUES ('A')
2021-10-27 16:19:25,474 INFO sqlalchemy.engine.Engine [generated in 0.00026s] ()
2021-10-27 16:19:25,474 INFO sqlalchemy.engine.Engine UPDATE leagues SET name = name || '1'
2021-10-27 16:19:25,474 INFO sqlalchemy.engine.Engine [generated in 0.00008s] ()
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine UPDATE leagues SET name = name || '2'
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine [generated in 0.00013s] ()
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine UPDATE leagues SET name = name || '1'
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine [cached since 0.0005372s ago] ()
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine COMMIT
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine COMMIT
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine SELECT name FROM leagues
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine [generated in 0.00011s] ()
[('A121',)]
2021-10-27 16:19:25,475 INFO sqlalchemy.engine.Engine ROLLBACK

这里,我使用两个连接向同一条记录写入数据。我希望输出是“A112”,因为我认为第一个连接在提交之前会锁定记录,但令我惊讶的是输出是“A121”。

SQLite 实际上没有锁定 table 还是我误解了发生了什么? (我会用另一个数据库如 PostgreSQL 得到相同的结果吗?)

如果您有多个连接,我实际上希望代码会出错,因为您正在创建死锁。您 运行 陷入了 SQLAlchemy 的实施怪癖。

来自documentation

class sqlalchemy.pool.SingletonThreadPool(creator, pool_size=5, **kw)

A Pool that maintains one connection per thread.

[...]

SingletonThreadPool is used by the SQLite dialect automatically when a memory-based database is used.

换句话说,当您打开一个 :memory: 数据库时,SQLAlchemy 不会使用 SQLite 的共享缓存模式来在不同的连接之间共享内存数据库,而是创建一个连接并通过它汇集所有连接对象.

通过将 poolclass=SingletonThreadPool 传递给 create_engine,您可以在文件连接上看到相同的行为,或者您可以在使用多个连接到 SQLAlchemy 之外的同一数据库的内存数据库中看到错误:

import sqlite3

db1 = sqlite3.connect("file::memory:?cache=shared", uri=True)
db2 = sqlite3.connect("file::memory:?cache=shared", uri=True)

conn1 = db1.cursor()
conn2 = db2.cursor()

conn1.execute("CREATE TABLE leagues (id INTEGER PRIMARY KEY, name VARCHAR(255) NOT NULL);")
conn1.execute("INSERT INTO leagues (name) VALUES ('A')")

conn1.execute("UPDATE leagues SET name = name || '1'")
# The next line will error out, since the db is locked by conn1
conn2.execute("UPDATE leagues SET name = name || '2'")