Python sqlite3.Cursor.executescript 方法
上次修改时间:2025 年 4 月 15 日
本综合指南探讨了 Python 的 sqlite3.Cursor.executescript 方法,该方法一次执行多个 SQL 语句。我们将介绍基本用法、事务行为、错误处理和实际示例。
基本定义
executescript 方法执行一个由分号分隔的 SQL 语句脚本。它是 sqlite3.Cursor 类的一部分,提供了一种在单个调用中运行多个语句的方法。
主要特性:它在一个事务中执行所有语句,不支持参数替换,并且适用于架构初始化或批量操作。如果成功,该方法会自动提交。
基本的 executescript 示例
这是使用 executescript 创建表和插入数据的最简单用法。
import sqlite3
with sqlite3.connect('example.db') as conn:
cursor = conn.cursor()
script = """
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE
);
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY,
user_id INTEGER,
amount REAL,
FOREIGN KEY(user_id) REFERENCES users(id)
);
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
"""
cursor.executescript(script)
此示例演示了如何在一次调用中执行多个 SQL 语句。该脚本创建两个相关表并插入初始数据。with 语句确保适当的资源清理。
请注意,与需要显式提交的 execute 不同,executescript 在成功完成后会自动提交事务。
使用 executescript 的事务行为
executescript 在单个事务中运行所有语句。如果任何语句失败,则整个事务将回滚。
import sqlite3
try:
with sqlite3.connect('test.db') as conn:
cursor = conn.cursor()
script = """
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
price REAL CHECK(price > 0)
);
INSERT INTO products (name, price) VALUES ('Laptop', 999.99);
INSERT INTO products (name, price) VALUES ('Mouse', -19.99); -- This will fail
INSERT INTO products (name, price) VALUES ('Keyboard', 49.99);
"""
cursor.executescript(script)
except sqlite3.Error as e:
print("Transaction failed:", e)
# Verify no data was inserted
with sqlite3.connect('test.db') as conn:
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM products")
print("Rows in products:", cursor.fetchone()[0]) # Output: 0
此示例演示了原子事务行为。第二个 INSERT 违反了 CHECK 约束,导致整个脚本失败。没有更改被持久化到数据库。
原子性保证对于在执行多个相关操作时维护数据库一致性至关重要。
模式初始化脚本
executescript 特别适用于数据库模式初始化。这是一个完整的模式设置示例。
import sqlite3
def initialize_database(db_path):
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
schema_script = """
PRAGMA foreign_keys = ON;
CREATE TABLE departments (
dept_id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
budget REAL DEFAULT 0.0
);
CREATE TABLE employees (
emp_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
salary REAL CHECK(salary >= 0),
dept_id INTEGER,
hire_date TEXT DEFAULT CURRENT_DATE,
FOREIGN KEY(dept_id) REFERENCES departments(dept_id)
);
CREATE INDEX idx_employees_dept ON employees(dept_id);
CREATE TRIGGER update_dept_budget
AFTER INSERT ON employees
BEGIN
UPDATE departments
SET budget = budget + NEW.salary
WHERE dept_id = NEW.dept_id;
END;
INSERT INTO departments (name, budget) VALUES ('Engineering', 500000);
INSERT INTO departments (name, budget) VALUES ('Marketing', 300000);
"""
cursor.executescript(schema_script)
initialize_database('company.db')
此示例创建了一个完整的数据库模式,其中包含表、索引、触发器和初始数据。PRAGMA 语句启用外键约束。
使用 executescript 进行模式初始化可确保所有模式元素都是原子创建的,并保持适当的依赖关系顺序。
使用 executescript 进行错误处理
执行脚本时,适当的错误处理至关重要。此示例演示如何捕获和处理错误。
import sqlite3
def run_safe_script(db_path, script):
try:
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
cursor.executescript(script)
except sqlite3.Error as e:
print(f"Script execution failed: {e}")
return False
return True
script = """
CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY,
message TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO logs (message) VALUES ('Script started');
BAD SQL STATEMENT; -- This will cause an error
INSERT INTO logs (message) VALUES ('Script completed');
"""
success = run_safe_script('logs.db', script)
print("Script success:", success)
# Verify partial execution
with sqlite3.connect('logs.db') as conn:
cursor = conn.cursor()
cursor.execute("SELECT message FROM logs")
print("Log entries:", cursor.fetchall()) # Output: []
此示例演示了当脚本中发生错误时,不会将任何更改应用于数据库。表创建和第一个插入将被回滚。
sqlite3.Error 异常捕获所有与 SQLite 相关的错误,包括语法错误和约束冲突。
从文件加载 SQL 脚本
对于较大的脚本,将它们存储在单独的文件中是很实用的。此示例演示如何从文件加载和执行脚本。
import sqlite3
def execute_sql_file(db_path, file_path):
try:
with open(file_path, 'r') as file:
sql_script = file.read()
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
cursor.executescript(sql_script)
except FileNotFoundError:
print(f"Error: SQL file not found at {file_path}")
except sqlite3.Error as e:
print(f"Database error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
# Assume we have a file named 'schema.sql' with SQL statements
execute_sql_file('inventory.db', 'schema.sql')
这种方法将 SQL 代码与 Python 代码分离,使维护更加容易。无需修改 Python 应用程序即可编辑脚本文件。
对于生产用途,请考虑添加对 SQL 文件内容的验证,并实施适当的日志记录,而不仅仅是打印错误。
将 executescript 与 execute 结合使用
您可以将 executescript 与常规 execute 调用结合使用,以实现更复杂的场景。
import sqlite3
def setup_application_database():
with sqlite3.connect('app.db') as conn:
cursor = conn.cursor()
# Execute schema creation script
cursor.executescript("""
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value TEXT
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT UNIQUE,
password_hash TEXT
);
""")
# Insert configuration using execute with parameters
config_data = [
('max_connections', '100'),
('timeout', '30'),
('theme', 'dark')
]
cursor.executemany(
"INSERT INTO config (key, value) VALUES (?, ?)",
config_data
)
# Create indexes separately
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_users_username
ON users(username)
""")
setup_application_database()
此示例显示了一种混合方法,其中模式设置使用 executescript,而数据插入使用参数化的 executemany 来保证安全。
这种组合允许利用每种方法的优势:脚本用于模式,参数化查询用于数据。这种方法可以防止用户数据出现 SQL 注入。
性能注意事项
对于批量数据操作,executescript 可能比多个 execute 调用更有效,但存在权衡。
import sqlite3
import time
def test_performance():
# Test executescript
start = time.time()
with sqlite3.connect(':memory:') as conn:
cursor = conn.cursor()
script = "CREATE TABLE test1 (id INTEGER, data TEXT);\n"
script += "\n".join(f"INSERT INTO test1 VALUES ({i}, 'data{i}');"
for i in range(1000))
cursor.executescript(script)
script_time = time.time() - start
# Test multiple executes
start = time.time()
with sqlite3.connect(':memory:') as conn:
cursor = conn.cursor()
cursor.execute("CREATE TABLE test2 (id INTEGER, data TEXT)")
for i in range(1000):
cursor.execute(f"INSERT INTO test2 VALUES (?, ?)", (i, f'data{i}'))
conn.commit()
execute_time = time.time() - start
print(f"executescript: {script_time:.3f} seconds")
print(f"multiple execute: {execute_time:.3f} seconds")
test_performance()
此性能测试比较了使用 executescript 插入 1000 行数据与单独的 execute 调用。结果会有所不同,但通常表明 executescript 对于批量操作更快。
但是,executescript 不支持参数化查询,因此对于用户提供的数据,尽管速度较慢,但使用带参数的单独 execute 调用更安全。
最佳实践
- 用于模式初始化: 非常适合创建表、索引、触发器
- 避免用于用户输入: 不支持参数化查询
- 正确处理错误: 脚本以原子方式失败 - 相应地计划
- 考虑文件存储: 大型脚本最好放在单独的文件中
- 结合使用方法: 在适当的时候与 execute/executemany 一起使用
资料来源
作者
列出所有 Python 教程。