HOWTO · Python

Python 中的 fetchall()

本教程演示 Python 的游标对象方法 fetchall() 以及如何在应用程序中使用它从 Python 中的数据库中检索数据。

本页内容

在 Python 中查询数据库时,游标对象提供了多种读取结果行的方式。fetchall() 是其中最直接的一种:一次调用即可返回当前结果集中所有剩余行。本教程将介绍 fetchall() 的返回值、如何在运行查询后使用它、如何处理返回的行,以及何时应选择其他获取方式。

示例使用 Python 内置的 sqlite3 模块,因此无需安装任何东西即可运行。相同的 fetchall() 语义也适用于其他 DB-API 驱动,例如 mysql-connector 或 psycopg。

fetchall() 的返回值

fetchall() 是 Python 数据库 API(PEP 249)所定义的游标对象方法。在 execute() 执行查询后,可在该游标上调用它。它会获取该查询结果集中所有剩余行,并以元组列表的形式返回;每个元组包含一行的列值,顺序与查询选取的列一致。

实际使用时,有两点需要注意:

  • 这次调用会消耗结果集。fetchall() 读完所有行后,在通过 execute() 执行新查询之前,再次调用它或调用 fetchone() 都不会取得更多内容。
  • 如果查询没有匹配到任何行,fetchall() 会返回空列表([]),而不是 None,因此可以安全地遍历结果。

运行查询并获取结果行

典型流程是:连接数据库、创建游标、执行 SELECT 查询,然后在同一个游标上调用 fetchall()。下面的示例会在内存中的 SQLite 数据库里创建一个小型 users 表,插入三行数据,查询这些数据,并打印 fetchall() 的返回值。

代码块中的代码、示例字符串及其输出保持英文,以与规范示例的字节内容一致。

本节中的代码以及显示的标签或输出仍为英文。

import sqlite3

connection = sqlite3.connect(':memory:')
cursor = connection.cursor()

cursor.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
cursor.executemany(
    'INSERT INTO users (name) VALUES (?)',
    [('Alice',), ('Bob',), ('Charlie',)]
)

cursor.execute('SELECT * FROM users ORDER BY id')
results = cursor.fetchall()
print(results)

connection.close()

输出:

[(1, 'Alice'), (2, 'Bob'), (3, 'Charlie')]

results 中的每个元素都是一个元组,例如 (1, 'Alice');其中各值的顺序与查询选取的列顺序相同。此时再次调用 fetchall() 会返回 [],因为第一次调用已经取完所有行。

处理返回的行

由于 fetchall() 返回普通列表,你可以像处理其他列表一样遍历它、按索引访问元素或对它切片。将每行元组解包到有名称的变量中,可以让循环更易读,也能清楚表明每个值对应哪一列。

下一个示例会查询两列,并在打印时解包每一行。请注意,元组中各值的位置始终遵循 SELECT 语句中的列顺序;元组本身不包含列名。

代码块中的代码、示例字符串及其输出保持英文,以与规范示例的字节内容一致。

本节中的代码以及显示的标签或输出仍为英文。

import sqlite3

connection = sqlite3.connect(':memory:')
cursor = connection.cursor()

cursor.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)')
cursor.executemany(
    'INSERT INTO users (name, age) VALUES (?, ?)',
    [('Alice', 30), ('Bob', 25), ('Charlie', 35)]
)

cursor.execute('SELECT name, age FROM users ORDER BY id')
rows = cursor.fetchall()
for name, age in rows:
    print(f'{name} is {age} years old')

connection.close()

输出:

Alice is 30 years old
Bob is 25 years old
Charlie is 35 years old

如果需要通过列名而不是位置访问数据,可以在创建游标之前设置连接的 row_factory(例如 sqlite3.Row),或检查 cursor.description 属性。无论采用哪种方式,获取方法的工作方式都相同。

根据结果大小选择获取方式

fetchall() 会将整个结果集一次性读入内存,并保存为一个列表。当结果规模较小、内存足以轻松容纳时,这种方式很方便;但如果查询返回数百万行,驱动一次性传输全部数据可能耗尽内存,或使程序长时间停顿。

如果结果可能很大,可以改为逐步获取:

  • fetchone() 返回下一行;没有剩余行时返回 None。直接遍历游标也会逐行产生结果。
  • fetchmany(size) 每次最多返回 size 行;最后一批可能少于这个数量。这样可以分批处理数据,而不必将整个结果集都载入列表。

下面的示例使用 fetchmany() 每批处理三行,因此每次只需在内存中保留一批数据。

代码块中的代码、示例字符串及其输出保持英文,以与规范示例的字节内容一致。

本节中的代码以及显示的标签或输出仍为英文。

import sqlite3

connection = sqlite3.connect(':memory:')
cursor = connection.cursor()

cursor.execute('CREATE TABLE events (id INTEGER PRIMARY KEY, label TEXT)')
cursor.executemany(
    'INSERT INTO events (label) VALUES (?)',
    [(f'event {i}',) for i in range(1, 8)]
)

cursor.execute('SELECT id, label FROM events ORDER BY id')
while True:
    batch = cursor.fetchmany(3)
    if not batch:
        break
    print(batch)

connection.close()

输出:

[(1, 'event 1'), (2, 'event 2'), (3, 'event 3')]
[(4, 'event 4'), (5, 'event 5'), (6, 'event 6')]
[(7, 'event 7')]

对已经通过 fetchall() 获取的列表切片并不能节省内存,因为完整结果早已载入。对于真正庞大的结果,也可以通过更具筛选性的 WHERE 子句或 LIMIT 缩小查询范围。

处理空结果和数据库错误

查询没有匹配到任何行并不是错误:fetchall() 只会返回 []。遍历结果的代码可以自然地处理这种情况;但如果要按索引访问结果或直接解包,应先检查列表是否为空。

数据库操作也可能失败:表名拼写错误、连接中断或约束冲突,都可能使 execute() 或获取结果的调用抛出 sqlite3.Error 等错误。可以用 try/except 包裹这些操作,并在 finally 中关闭连接,以便妥善报告错误并释放资源。这里,sqlite3 连接上的 with 块不能代替关闭连接:它只会提交或回滚当前事务,不会关闭连接,因此仍需显式调用 close()(也可以用 contextlib.closing() 包装连接,以确保关闭)。

下面的示例展示了这两种情况:没有匹配行的 WHERE 子句会得到空列表;查询不存在的表时,错误会被捕获并报告。

代码块中的代码、示例字符串及其输出保持英文,以与规范示例的字节内容一致。

本节中的代码以及显示的标签或输出仍为英文。

import sqlite3

connection = sqlite3.connect(':memory:')
try:
    cursor = connection.cursor()

    cursor.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)')
    cursor.executemany(
        'INSERT INTO users (name) VALUES (?)',
        [('Alice',), ('Bob',)]
    )

    cursor.execute('SELECT * FROM users WHERE id = ?', (99,))
    print(cursor.fetchall())

    cursor.execute('SELECT * FROM missing_table')
    cursor.fetchall()
except sqlite3.Error as e:
    print(f'Database error: {e}')
finally:
    connection.close()

输出:

[]
Database error: no such table: missing_table

except 子句应限定为驱动对应的错误类型(此处为 sqlite3.Error),而不要捕获所有异常,这样无关的程序错误仍会显现。

总之,fetchall() 是读取查询全部行的简便方式:它返回元组列表,没有匹配行时返回空列表,并会消耗结果集。对于小到中等规模的结果,可以使用它;如果数据量可能造成问题,则考虑使用 fetchone() 或 fetchmany()。同时,应为数据库操作添加错误处理,让失败保持可见并确保连接关闭。