提示

这些技巧基于邮件列表中的帖子、问题和电子邮件。建议您也阅读所有文档。

SQLite则不同

虽然SQLite提供了与许多其他数据库一样的SQL数据库,但它在许多方面也是独一无二的。阅读有关独特功能的信息,请访问 SQLite websitequirks

小技巧

Best practice 是推荐的。

交易记录

事务是作为一个整体应用于数据库文件的更改。它们要么完全发生,要么根本不发生。SQLite会记录事务期间所做的所有更改,在最后提交时,这些更改将永久保存在数据库中。如果您不提交,或者只是退出,那么其他/新连接将不会看到更改,并且SQLite会自动处理整理正在进行的工作。

提交事务可能非常耗时。SQLite使用强大的多步骤过程,必须处理任何时候都可能发生的错误,并要求操作系统确保数据存储在存储上,并在电源重启后仍能存活。这将 limit the rate at which you can do transactions

如果不执行任何操作,则每条语句都是单个事务::

# this will be 3 separate transactions
db.execute("INSERT ...")
db.execute("INSERT ...")
db.execute("INSERT ...")

您可以使用Begin/Commit来设置事务边界::

# this will be one transaction
db.execute("BEGIN")
db.execute("INSERT ...")
db.execute("INSERT ...")
db.execute("INSERT ...")
db.execute("COMMIT")

然而,这是额外的工作,而且还需要错误处理。例如,如果第二次插入失败,那么您可能希望回滚未完成的事务,以便同一连接上的其他工作看不到部分数据。

如果您使用 with Connection 则事务将自动启动,并在成功时提交或在发生异常时回滚:

# this will be one transaction with automatic commit and rollback
with db:
    db.execute("INSERT ...")
    db.execute("INSERT ...")
    db.execute("INSERT ...")

确实有 technical detailsSQLite site

游标

SQLite只在您请求时计算每个结果行。例如,如果您的查询返回1000万行,SQLite将不会预先计算全部1000万行。取而代之的是,将按照您的要求计算下一行。

上的游标 Connection 并不是彼此孤立的。在一个游标上执行的任何操作对同一连接上的所有其他游标都立即可见。如果您启动交易,这仍然适用。连接是相互隔离的。

Connection.execute()Connection.executemany() 自动从以下位置获取光标 Connection.cursor() 它们都很便宜。最好的做法是不要重复使用它们,而是每次都得到一个新的。如果不这样做,代码重构和嵌套循环可能会无意中使用相同的游标对象,该对象不会崩溃,但会导致难以诊断程序中的行为。

阅读更多关于 Cursors

装订

发出查询时,请始终使用绑定。 String interpolation 可能看起来更方便,但你会遇到困难。您可能会觉得您可以完全控制所有访问的数据,但如果您的代码非常有用,那么您会发现它的使用越来越广泛。计算机在解析SQL方面总是比你做得更好,而坏人有多年的查找和使用经验 SQL injection attacks 以一种你从未想过可能的方式。

这个 documentation 提供了如何使用各种形式的绑定的许多示例。

诊断

SQLite和APSW都提供了详细的诊断信息。错误将通过 exception

APSW确保您拥有 detailed information 在堆栈跟踪中以及APSW/SQLite正在操作的数据中。

SQLite有一个 warning/error logging facility 。你可以打电话给 apsw.ext.log_sqlite() 它安装了一个处理程序,用于将SQLite消息转发到 logging module .`

要自己动手::

def handler(errcode, message):
    errstr=apsw.mapping_result_codes[errcode & 255]
    print (f"SQLITE_LOG: { message } ({ errcode }) { errstr } "
           + apsw.mapping_extended_result_codes.get(errcode, ""))

apsw.config(apsw.SQLITE_CONFIG_LOG, handler)

这是我使用时打印的内容的示例 /dev/null 中的数据库名称。 Connection 然后试着创建一张桌子。

SQLITE_LOG: cannot open file at line 28729 of [7dd4968f23] (14) SQLITE_CANTOPEN
SQLITE_LOG: os_unix.c:28729: (2) open(/dev/null-journal) - No such file or directory (14) SQLITE_CANTOPEN
SQLITE_LOG: statement aborts at 38: [create table foo(x,y);] unable to open database file (14) SQLITE_CANTOPEN

管理和更新您的架构

如果您的程序将SQLite用于 data 然后,您将需要管理和更新您的架构。要做到这一点,最困难的方法是测试表及其列的存在,并以编程方式进行维护。最简单的方法是使用 pragma user_version 如下例所示::

def ensure_schema(db):
  # a new database starts at user_version 0
  if db.pragma("user_version") == 0:
    with db:
      db.execute("""
        CREATE TABLE IF NOT EXISTS foo(x,y,z);
        CREATE TABLE IF NOT EXISTS bar(x,y,z);
        PRAGMA user_version = 1;""")

  if db.pragma("user_version") == 1:
    with db:
      db.execute("""
      CREATE TABLE IF NOT EXISTS baz(x,y,z);
      CREATE INDEX ....
      PRAGMA user_version = 2;""")

  if db.pragma("user_version") == 2:
    with db:
      db.execute("""
      ALTER TABLE .....
      PRAGMA user_version = 3;""")

此方法将如您所期望的那样自动升级架构。您还可以使用 pragma application_id 将数据库标记为由您的应用程序创建。

解析SQL

有时,您想知道特定的SQL语句做了什么。使用 apsw.ext.query_info() 它会提供你所需要的所有细节。

自定义连接

apsw.connection_hooks 是每个事件的回调列表 Connection 被创造出来了。它们被轮流调用,新连接是唯一的参数。

例如,如果要添加一个 executescript 方法连接到类似于 Connection.execute() 但忽略所有返回的行::

def executescript(self, sql, bindings=None):
  for _ in self.execute(sql, bindings):
    pass

def my_hook(connection):
  connection.executescript = executescript

apsw.connection_hooks.append(my_hook)

自定义游标

您可以定制游标的行为。一个例子是想要一个 rowcount 或对返回的行进行批处理。(这些在SQLite中没有任何意义,但其目的可能是使代码源代码与其他数据库驱动程序兼容)。

Connection.cursor_factory 到任何可调用对象,它将以连接作为唯一参数进行调用,并返回用作游标的对象。

例如,我们不是将行作为元组返回,而是可以使用 row tracer 使用 Cursor.getdescription() **

def dict_row(cursor, row):
  return {k[0]: row[i] for i, k in enumerate(cursor.getdescription())}

def my_factory(connection):
  cursor = apsw.Cursor(connection)
  cursor.rowtrace = dict_row
  return cursor

connection.cursor_factory = my_factory

忙碌的处理

SQLite使用锁来协调通过多个连接(在同一进程内或在不同进程中)对数据库的访问。总的目标是使锁尽可能宽松(允许并发),并在使用更多限制性锁时尽可能短地保持锁。请参阅 SQLite documentation 了解更多详细信息。

默认情况下,您将获得一个 BusyError 如果无法获取锁,则。您可以设置一个 timeout 它将继续重试,否则将出现 callback 在那里你可以决定要做什么。

数据库架构

在启动新数据库时,很难决定要拥有哪些表和字段以及如何链接它们。用于设计SQL模式的技术称为 normalization 。如果您不规范化您的模式,该页面还会显示常见的陷阱。

提前写入日志记录

推出SQLite 3.7 write ahead logging 这有几个好处,但作为页面文档也有一些缺点。默认情况下,WAL模式处于关闭状态。除了为每个数据库手动启用它之外,还可以使用以下命令为所有打开的数据库启用它 connection_hooks **

def setwal(db):
    db.pragma("journal_mode", "wal")
    # custom auto checkpoint interval (use zero to disable)
    db.wal_autocheckpoint(10)

apsw.connection_hooks.append(setwal)

请注意,如果无法设置WAL模式(例如,数据库在内存中或临时),则设置WAL模式的尝试将被忽略。杂注将返回生效的模式。调用如下函数也是无害的 Connection.wal_autocheckpoint() 在非WAL模式的连接上。

如果您编写自己的VFS,那么从支持WAL的现有VFS继承将使您的VFS也支持额外的WAL方法。(您的VFS将直接指向基本方法--不会通过Python进行间接调用。)

共享缓存模式

SQLite支持 shared cache mode 其中到同一数据库的多个连接可以共享一个高速缓存,而不是拥有它们自己的高速缓存。SQLite建议您 you do not use this mode

如果您确实使用它,请注意 busy handling 是非常不同的,而且与Python程序通常所做的相比,您不太可能节省任何内存或I/O。