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 教程。