ZetCode

Python sqlite3.Cursor.executescript 方法

上次修改时间:2025 年 4 月 15 日

本综合指南探讨了 Python 的 sqlite3.Cursor.executescript 方法,该方法一次执行多个 SQL 语句。我们将介绍基本用法、事务行为、错误处理和实际示例。

基本定义

executescript 方法执行一个由分号分隔的 SQL 语句脚本。它是 sqlite3.Cursor 类的一部分,提供了一种在单个调用中运行多个语句的方法。

主要特性:它在一个事务中执行所有语句,不支持参数替换,并且适用于架构初始化或批量操作。如果成功,该方法会自动提交。

基本的 executescript 示例

这是使用 executescript 创建表和插入数据的最简单用法。

basic_executescript.py
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 在单个事务中运行所有语句。如果任何语句失败,则整个事务将回滚。

transaction_behavior.py
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 特别适用于数据库模式初始化。这是一个完整的模式设置示例。

schema_init.py
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 进行错误处理

执行脚本时,适当的错误处理至关重要。此示例演示如何捕获和处理错误。

error_handling.py
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 脚本

对于较大的脚本,将它们存储在单独的文件中是很实用的。此示例演示如何从文件加载和执行脚本。

load_from_file.py
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 调用结合使用,以实现更复杂的场景。

combined_usage.py
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 调用更有效,但存在权衡。

performance_test.py
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 调用更安全。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程