Python SQLite:`Cursor` 对象深度解析
目录
- 引言:
Cursor对象的重要性 - 创建
Cursor对象- 2.1
Connection.cursor()方法
- 2.1
Cursor对象的核心方法- 3.1
execute(sql, parameters=...): 执行单个 SQL 语句- 3.1.1 参数绑定:安全性与类型处理
- 3.1.2 DDL、DML 和 DQL 操作
- 3.2
executemany(sql, seq_of_parameters): 执行批量 SQL 语句 - 3.3
executescript(sql_script): 执行多条 SQL 脚本 - 3.4 数据检索方法
- 3.4.1
fetchone(): 获取下一行数据 - 3.4.2
fetchmany(size=cursor.arraysize): 获取多行数据 - 3.4.3
fetchall(): 获取所有剩余数据
- 3.4.1
- 3.5 迭代
Cursor对象
- 3.1
Cursor对象的重要属性- 4.1
lastrowid: 获取最后插入的行 ID - 4.2
rowcount: 获取受影响的行数 - 4.3
description: 获取查询结果的元数据 - 4.4
arraysize:fetchmany()的默认批次大小
- 4.1
Connection的row_factory属性- 5.1 使用
sqlite3.Row提升数据可读性
- 5.1 使用
- 错误处理
- 6.1 常见错误类型
- 6.2 错误处理示例
- 最佳实践与注意事项
- 7.1 始终使用参数绑定
- 7.2 及时
commit() - 7.3 谨慎使用
executescript() - 7.4 善用
with语句管理Connection - 7.5 理解
Cursor的生命周期
- 总结
1. 引言:Cursor 对象的重要性
在 Python 的 sqlite3 模块中,Cursor 对象是您与 SQLite 数据库进行交互的核心工具。如果说 Connection 对象是应用程序与数据库之间的“连接通道”,那么 Cursor 对象就是在这个通道上执行 SQL 命令、传递数据、并获取查询结果的“操作员”。
所有对数据库的实际操作,例如创建表、插入数据、更新记录、删除记录以及查询数据,都必须通过 Cursor 对象来完成。一个数据库连接 (Connection) 可以同时拥有多个游标 (Cursor),每个游标都独立地维护其状态和查询结果。
2. 创建 Cursor 对象
Cursor 对象是通过 Connection 对象的 cursor() 方法创建的。
2.1 Connection.cursor() 方法
import sqlite3
# 1. 建立数据库连接
conn = sqlite3.connect('example.db')
# 2. 创建 Cursor 对象
cursor = conn.cursor()
# 现在可以使用 cursor 对象执行 SQL 语句了
# ...
# 3. 关闭连接 (游标会自动关闭)
conn.close()
创建游标时,cursor() 方法通常不接受参数。如果需要在创建时指定自定义的游标行为,可以通过 Connection 对象的 cursor_factory 属性来设置,但这在日常使用中较少用到。
3. Cursor 对象的核心方法
Cursor 对象提供了一系列方法来执行 SQL 语句和检索数据。
3.1 execute(sql, parameters=...): 执行单个 SQL 语句
execute() 方法用于执行单个 SQL 语句。这是最常用的方法。
3.1.1 参数绑定:安全性与类型处理
这是使用 execute() 方法时最重要的实践。您应该始终使用参数绑定来传递值,而不是直接将值拼接进 SQL 字符串。
- 安全性: 有效防止 SQL 注入攻击。
- 类型处理:
sqlite3模块会自动将 Python 对象(如int,float,str,bytes,None,datetime.date,datetime.datetime,bool)适配为 SQLite 兼容的存储类,无需手动进行字符串格式化。 - 占位符: Python
sqlite3支持两种参数占位符:- 问号占位符 (
?): 参数以元组或列表的形式传递。cursor.execute("INSERT INTO users (name, age) VALUES (?, ?)", ("Alice", 30)) - 命名占位符 (
:name): 参数以字典的形式传递。cursor.execute("UPDATE users SET age = :new_age WHERE name = :user_name", {"new_age": 31, "user_name": "Alice"})
- 问号占位符 (
3.1.2 DDL、DML 和 DQL 操作
- DDL (Data Definition Language): 创建、修改或删除数据库结构(表、索引等)。
cursor.execute(''' CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, email TEXT UNIQUE, created_at DATETIME ) ''') - DML (Data Manipulation Language): 插入、更新、删除数据。
# INSERT cursor.execute("INSERT INTO users (name, age, created_at) VALUES (?, ?, ?)", ("Bob", 25, datetime.datetime.now())) # datetime 会被默认适配为 ISO 8601 字符串 conn.commit() # DML 操作需要 commit 才能持久化 # UPDATE cursor.execute("UPDATE users SET age = ? WHERE name = ?", (26, "Bob")) conn.commit() # DELETE cursor.execute("DELETE FROM users WHERE name = ?", ("Bob",)) conn.commit() - DQL (Data Query Language): 查询数据。
cursor.execute("SELECT id, name, age FROM users WHERE age > ?", (20,)) # 结果通过 fetchone(), fetchall(), fetchmany() 或迭代游标获取
3.2 executemany(sql, seq_of_parameters): 执行批量 SQL 语句
executemany() 方法用于执行同一个 SQL 语句多次,每次使用不同的参数集。这比在循环中多次调用 execute() 效率更高。
sql: 要执行的 SQL 语句。seq_of_parameters: 一个序列(例如列表或元组),其中每个元素都是一个参数元组或字典。
import sqlite3
import datetime
import os
db_name = 'bulk_operations.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
price REAL,
stock INTEGER
)
''')
products_to_insert = [
("Laptop", 1200.00, 10),
("Mouse", 25.50, 50),
("Keyboard", 75.00, 20),
("Monitor", 300.00, 5)
]
cursor.executemany("INSERT INTO products (name, price, stock) VALUES (?, ?, ?)", products_to_insert)
conn.commit() # 批量操作后统一提交
print("\n--- executemany 示例 ---")
cursor.execute("SELECT * FROM products")
print("插入的产品:")
for row in cursor.fetchall():
print(row)
3.3 executescript(sql_script): 执行多条 SQL 脚本
executescript() 方法用于执行一个包含多条 SQL 语句的字符串。这些语句之间可以用分号 ; 分隔。通常用于初始化数据库模式或执行复杂的脚本。
注意: executescript() 不支持参数绑定。因此,它不应用于处理用户输入,以防止 SQL 注入。
import sqlite3
import os
db_name = 'script_execution.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
cursor = conn.cursor()
sql_script = """
DROP TABLE IF EXISTS settings;
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT
);
INSERT INTO settings (key, value) VALUES ('app_name', 'MyCoolApp');
INSERT INTO settings (key, value) VALUES ('version', '1.0.0');
"""
cursor.executescript(sql_script)
conn.commit()
print("\n--- executescript 示例 ---")
cursor.execute("SELECT * FROM settings")
print("应用设置:")
for row in cursor.fetchall():
print(row)
3.4 数据检索方法
当执行 SELECT 语句后,数据存储在游标对象中,可以通过以下方法逐行或批量获取。
3.4.1 fetchone(): 获取下一行数据
从查询结果中获取下一行。如果没有更多行,则返回 None。
3.4.2 fetchmany(size=cursor.arraysize): 获取多行数据
从查询结果中获取指定数量的行。size 参数默认为 cursor.arraysize 属性的值(通常为 1)。如果行数少于 size,则返回所有可用行。如果没有更多行,则返回空列表。
3.4.3 fetchall(): 获取所有剩余数据
获取查询结果中所有剩余的行,以列表的形式返回。如果没有更多行,则返回空列表。
import sqlite3
import os
db_name = 'fetch_data.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE employees (
id INTEGER PRIMARY KEY,
name TEXT,
department TEXT
)
''')
employees_data = [
("Alice", "HR"), ("Bob", "Engineering"), ("Charlie", "HR"),
("David", "Sales"), ("Eve", "Engineering"), ("Frank", "Marketing")
]
cursor.executemany("INSERT INTO employees (name, department) VALUES (?, ?)", employees_data)
conn.commit()
print("\n--- 数据检索示例 ---")
# fetchone()
cursor.execute("SELECT * FROM employees WHERE department = 'HR'")
print("fetchone() 结果:", cursor.fetchone()) # 预期: (1, 'Alice', 'HR')
# fetchmany()
print("fetchmany(2) 结果:")
rows_many = cursor.fetchmany(2) # 获取接下来的两行
for row in rows_many:
print(row) # 预期: (3, 'Charlie', 'HR')
# 此时没有第二行了,所以只有一行
# fetchall()
cursor.execute("SELECT * FROM employees WHERE department = 'Engineering'")
print("fetchall() 结果:", cursor.fetchall()) # 预期: [(2, 'Bob', 'Engineering'), (5, 'Eve', 'Engineering')]
# 再次 fetchall() (没有剩余行)
print("再次 fetchall() 结果:", cursor.fetchall()) # 预期: []
3.5 迭代 Cursor 对象
在 Python 中,直接迭代 Cursor 对象是获取查询结果的最简洁和推荐的方式,它会自动逐行获取数据,直到没有更多行。
import sqlite3
import os
db_name = 'iteration.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title TEXT,
author TEXT
)
''')
books_data = [
("The Hitchhiker's Guide to the Galaxy", "Douglas Adams"),
("Pride and Prejudice", "Jane Austen"),
("1984", "George Orwell"),
("To Kill a Mockingbird", "Harper Lee")
]
cursor.executemany("INSERT INTO books (title, author) VALUES (?, ?)", books_data)
conn.commit()
print("\n--- 迭代 Cursor 示例 ---")
cursor.execute("SELECT * FROM books")
for row in cursor: # 直接迭代游标
print(row)
4. Cursor 对象的重要属性
Cursor 对象还提供了一些有用的属性来获取操作结果的元数据。
4.1 lastrowid: 获取最后插入的行 ID
在执行 INSERT 语句后,如果表有一个 INTEGER PRIMARY KEY AUTOINCREMENT 列,lastrowid 属性将返回最后插入的行的 ID。
import sqlite3
import os
db_name = 'lastrowid_demo.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE products (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)")
cursor.execute("INSERT INTO products (name) VALUES ('Widget A')")
product_id_a = cursor.lastrowid
print(f"\n--- lastrowid 示例 ---")
print(f"插入 'Widget A' 的 ID: {product_id_a}") # 预期: 1
cursor.execute("INSERT INTO products (name) VALUES ('Widget B')")
product_id_b = cursor.lastrowid
print(f"插入 'Widget B' 的 ID: {product_id_b}") # 预期: 2
conn.commit()
4.2 rowcount: 获取受影响的行数
rowcount 属性返回 execute() 或 executemany() 方法对数据库执行 INSERT, UPDATE, DELETE 操作时影响的行数。对于 SELECT 语句,rowcount 通常为 -1,表示行数未知(或未被统计)。
import sqlite3
import os
db_name = 'rowcount_demo.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE tasks (id INTEGER PRIMARY KEY, description TEXT, completed BOOLEAN)")
tasks_data = [
(1, "Buy groceries", False), (2, "Do laundry", False),
(3, "Walk dog", True), (4, "Code review", False)
]
cursor.executemany("INSERT INTO tasks VALUES (?, ?, ?)", tasks_data)
print(f"\n--- rowcount 示例 ---")
print(f"插入 {cursor.rowcount} 条任务。") # 预期: 4
conn.commit()
cursor.execute("UPDATE tasks SET completed = ? WHERE completed = ?", (True, False))
print(f"更新了 {cursor.rowcount} 条未完成任务。") # 预期: 3
conn.commit()
cursor.execute("DELETE FROM tasks WHERE id = 3")
print(f"删除了 {cursor.rowcount} 条任务。") # 预期: 1
conn.commit()
cursor.execute("SELECT * FROM tasks")
print(f"SELECT 语句的 rowcount: {cursor.rowcount}") # 预期: -1
4.3 description: 获取查询结果的元数据
当执行 SELECT 语句后,description 属性会返回一个元组的列表,每个元组代表结果集中的一列,包含以下信息:(name, type_code, display_size, internal_size, precision, scale, null_ok)。在 sqlite3 中,除了 name,其他字段通常都是 None。
import sqlite3
import os
db_name = 'description_demo.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL)")
cursor.execute("INSERT INTO products VALUES (1, 'Book', 15.99)")
conn.commit()
cursor.execute("SELECT id, name, price FROM products")
print(f"\n--- description 示例 ---")
if cursor.description:
print("查询结果列元数据:")
for column_info in cursor.description:
print(f" Column Name: {column_info[0]}") # 预期: id, name, price
else:
print("没有可用的列元数据 (可能未执行 SELECT 语句)。")
4.4 arraysize: fetchmany() 的默认批次大小
arraysize 属性用于设置 fetchmany() 方法在不指定 size 参数时的默认行数。默认为 1。
import sqlite3
import os
db_name = 'arraysize_demo.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE items (id INTEGER PRIMARY KEY, value TEXT)")
items_data = [(i, f"Item {i}") for i in range(1, 11)]
cursor.executemany("INSERT INTO items VALUES (?, ?)", items_data)
conn.commit()
cursor.arraysize = 3 # 设置默认批次大小为 3
cursor.execute("SELECT * FROM items")
print(f"\n--- arraysize 示例 ---")
print(f"第一次 fetchmany() (使用 arraysize={cursor.arraysize}): {cursor.fetchmany()}") # 预期: 前3行
print(f"第二次 fetchmany(): {cursor.fetchmany()}") # 预期: 接下来的3行
print(f"第三次 fetchmany(): {cursor.fetchmany()}") # 预期: 接下来的3行
print(f"第四次 fetchmany(): {cursor.fetchmany()}") # 预期: 剩余的1行
5. Connection 的 row_factory 属性
默认情况下,Cursor 对象在获取数据时返回的是元组 (tuple)。虽然这对于简单的查询足够,但当结果集列数较多时,通过索引访问数据(row[0], row[1]) 会降低代码可读性。Connection 对象的 row_factory 属性允许您改变返回行的类型。
5.1 使用 sqlite3.Row 提升数据可读性
sqlite3.Row 是一个特殊的 row_factory,它返回一个类似元组的对象,但同时支持通过列名进行字典式访问。
import sqlite3
import os
db_name = 'row_factory_demo.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
# 设置 row_factory 为 sqlite3.Row
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, first_name TEXT, last_name TEXT, age INTEGER)")
cursor.execute("INSERT INTO users VALUES (1, 'John', 'Doe', 30)")
cursor.execute("INSERT INTO users VALUES (2, 'Jane', 'Smith', 25)")
conn.commit()
print(f"\n--- row_factory 示例 ---")
cursor.execute("SELECT id, first_name, last_name, age FROM users WHERE id = 1")
user = cursor.fetchone()
# 通过索引访问 (仍然可以)
print(f"通过索引访问: {user[1]} {user[2]}, Age: {user[3]}")
# 通过列名访问 (更具可读性)
print(f"通过列名访问: {user['first_name']} {user['last_name']}, Age: {user['age']}")
# 结果对象本身是一个 sqlite3.Row 对象
print(f"结果对象的类型: {type(user)}") # 预期: <class 'sqlite3.Row'>
# 也可以像字典一样获取所有键
print(f"所有列名: {user.keys()}")
6. 错误处理
在使用 Cursor 对象进行数据库操作时,错误是不可避免的。合理地处理这些错误可以提高应用程序的健壮性。
6.1 常见错误类型
sqlite3.OperationalError: 数据库操作失败,例如文件被锁定、数据库文件损坏、权限问题、SQL 语法错误等。sqlite3.ProgrammingError: 编程错误,例如尝试在一个已关闭的游标上操作、在错误的线程中访问连接(如果check_same_thread=True)。sqlite3.IntegrityError: 违反数据库完整性约束,例如插入重复的UNIQUE值、违反NOT NULL约束。
6.2 错误处理示例
import sqlite3
import os
db_name = 'error_handling.db'
if os.path.exists(db_name): os.remove(db_name)
with sqlite3.connect(db_name) as conn:
cursor = conn.cursor()
try:
cursor.execute("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL)")
conn.commit()
# 尝试插入有效数据
cursor.execute("INSERT INTO products (id, name) VALUES (?, ?)", (1, "Laptop"))
conn.commit()
print("\n--- 错误处理示例 ---")
print("成功插入 Laptop。")
# 尝试插入重复的 UNIQUE 值 (IntegrityError)
cursor.execute("INSERT INTO products (id, name) VALUES (?, ?)", (2, "Laptop"))
conn.commit() # 这里会尝试提交,但错误在 execute 阶段就会被检测到并抛出
print("不应该执行到这里。")
except sqlite3.IntegrityError as e:
print(f"捕获到 IntegrityError: {e}")
conn.rollback() # 回滚未成功的事务
print("事务已回滚。")
except sqlite3.OperationalError as e:
print(f"捕获到 OperationalError: {e}")
conn.rollback()
print("事务已回滚。")
except sqlite3.Error as e:
print(f"捕获到其他 SQLite 错误: {e}")
conn.rollback()
print("事务已回滚。")
finally:
# 即使发生错误,连接在 with 块结束时也会自动关闭
pass
# 尝试执行一个语法错误的 SQL (OperationalError)
try:
cursor.execute("SELECT * FROM non_existent_table WHERE id = 1")
except sqlite3.OperationalError as e:
print(f"捕获到 OperationalError (SQL 语法/表不存在): {e}")
7. 最佳实践与注意事项
7.1 始终使用参数绑定
这是防止 SQL 注入、确保数据类型正确适配的最佳实践。永远不要使用 f-string 或字符串拼接来构建包含用户输入的 SQL 语句。
7.2 及时 commit()
对于任何修改数据库的 DML 操作 (INSERT, UPDATE, DELETE),您必须调用 conn.commit() 才能将更改持久化到数据库文件。SELECT 查询不需要 commit()。建议在逻辑事务的末尾进行提交,而不是每次操作后都提交,以提高性能并保持数据原子性。
7.3 谨慎使用 executescript()
虽然 executescript() 方便执行多条 SQL 语句,但它不支持参数绑定。因此,仅将其用于安全的、不需要用户输入的数据库初始化或模式管理脚本。
7.4 善用 with 语句管理 Connection
正如在“连接到数据库”文档中强调的,使用 with sqlite3.connect(...) as conn: 模式可以确保数据库连接在操作完成后或发生异常时自动关闭,并且事务也会自动提交或回滚。游标对象会在其所属的连接关闭时自动失效和被回收。
7.5 理解 Cursor 的生命周期
一个游标在被创建后,可以在其所属的 Connection 活跃期间多次执行 SQL 语句。当 Connection 关闭时,所有相关的 Cursor 也会随之关闭。通常不需要显式地关闭 Cursor 对象,但避免长时间持有不用的游标是良好的习惯。
7.6 考虑 row_factory 提升可读性
对于经常需要通过列名访问查询结果的场景,设置 conn.row_factory = sqlite3.Row 可以显著提高代码的可读性和可维护性。
7.7 BOOLEAN 和 datetime 的处理
请记住,SQLite 没有原生的 BOOLEAN 类型,通常用 INTEGER 的 0 和 1 表示。Python 的 bool 会自动适配。对于 datetime 对象,默认会适配为 ISO 8601 字符串。如果希望在读取时自动转换回 Python datetime 对象,请确保在 sqlite3.connect() 中使用 detect_types=sqlite3.PARSE_DECLTYPES,并确保列声明为 DATE, DATETIME 或 TIMESTAMP。
8. 总结
已为您详细介绍了 Python sqlite3 模块中的 Cursor 对象。
核心要点回顾:
Cursor对象是执行所有数据库操作 (DDL,DML,DQL) 的核心接口。execute()用于执行单个 SQL 语句,必须使用参数绑定来防止 SQL 注入。executemany()用于高效批量执行相同的 SQL 语句。executescript()用于执行多条 SQL 语句脚本,但不支持参数绑定。- 数据检索通过
fetchone(),fetchmany(),fetchall()或直接迭代Cursor来完成。 lastrowid获取最后插入行的 ID,rowcount获取受影响行数,description提供查询结果列元数据。- 通过设置
conn.row_factory = sqlite3.Row可以使查询结果通过列名访问,大大提高代码可读性。 - 完善的错误处理机制对于健壮的应用程序至关重要。
掌握 Cursor 对象的这些方法和属性,将使您能够有效、安全、高效地与 SQLite 数据库进行交互。
清理测试文件 (为确保后续测试环境干净)
import os
files_to_clean = [
'example.db', 'bulk_operations.db', 'script_execution.db',
'fetch_data.db', 'lastrowid_demo.db', 'rowcount_demo.db',
'description_demo.db', 'arraysize_demo.db', 'row_factory_demo.db',
'error_handling.db'
]
for f in files_to_clean:
if os.path.exists(f):
os.remove(f)
print("\n所有测试数据库文件已清理。")
更多推荐

所有评论(0)