ZetCode

Python sqlite3.Connection.executescript 方法

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

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

基本定义

executescript 方法在单个调用中执行 SQL 语句的脚本。 它是 sqlite3.Connection 类的一部分,对批量操作非常有用。

主要特点:它在一个事务中执行所有语句,接受带有 SQL 命令的字符串参数,并且不返回任何结果。 它非常适合模式设置或迁移。

基本 executescript 示例

这是一个简单的例子,演示了如何使用 executescript 来创建表并在一个操作中插入数据。

basic_executescript.py
import sqlite3

with sqlite3.connect('test.db') as conn:
    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');
    """
    conn.executescript(script)

此示例创建一个单个脚本创建两个相关的表并插入示例数据。 with 语句确保正确的连接清理。

所有语句都作为单个事务的一部分执行。 如果任何语句失败,则整个事务将自动回滚。

事务行为

此示例演示了当脚本中发生错误时 executescript 的事务行为。

transaction_behavior.py
import sqlite3

with sqlite3.connect('transactions.db') as conn:
    try:
        script = """
            CREATE TABLE IF NOT EXISTS accounts (
                id INTEGER PRIMARY KEY,
                name TEXT,
                balance REAL
            );
            
            INSERT INTO accounts (name, balance) VALUES ('Alice', 1000);
            INSERT INTO accounts (name, balance) VALUES ('Bob', 500);
            
            -- This will cause an error (table doesn't exist)
            INSERT INTO transactions (account_id, amount) VALUES (1, -100);
        """
        conn.executescript(script)
    except sqlite3.Error as e:
        print("Error executing script:", e)
        
    # Verify no partial changes were committed
    cursor = conn.cursor()
    cursor.execute("SELECT COUNT(*) FROM accounts")
    print("Accounts count:", cursor.fetchone()[0])  # Outputs 0

该脚本包含一个错误(插入到不存在的表中)。 整个事务回滚,数据库保持不变。

这种原子行为可确保在执行多个相关语句时的数据一致性。

模式迁移脚本

executescript 非常适合数据库模式迁移。 此示例显示了版本升级脚本。

schema_migration.py
import sqlite3

with sqlite3.connect('app.db') as conn:
    migration_script = """
        -- Add new columns to existing table
        ALTER TABLE users ADD COLUMN last_login TEXT;
        ALTER TABLE users ADD COLUMN status TEXT DEFAULT 'active';
        
        -- Create new tables for features
        CREATE TABLE IF NOT EXISTS user_preferences (
            user_id INTEGER PRIMARY KEY,
            theme TEXT DEFAULT 'light',
            notifications INTEGER DEFAULT 1,
            FOREIGN KEY(user_id) REFERENCES users(id)
        );
        
        -- Backfill existing data
        UPDATE users SET status = 'active' WHERE status IS NULL;
    """
    conn.executescript(migration_script)

此脚本以原子方式执行多个模式更改。 它添加列,创建一个新表并回填数据。

使用 executescript 可确保所有更改一起成功或失败,从而防止部分迁移。

从外部文件加载 SQL

此示例显示了如何从外部文件加载 SQL 并使用 executescript 执行它。

external_script.py
import sqlite3

def load_and_execute_sql(db_file, sql_file):
    with sqlite3.connect(db_file) as conn:
        with open(sql_file, 'r') as f:
            sql_script = f.read()
        conn.executescript(sql_script)

# Execute schema.sql file against test.db
load_and_execute_sql('test.db', 'schema.sql')

该函数从文件读取 SQL 并针对数据库执行它。 此模式对于部署脚本很有用。

应为生产用途添加错误处理,以处理文件读取和 SQL 执行错误。

与参数化查询结合使用

虽然 executescript 不直接支持参数,但您可以将其与常规执行结合使用以进行混合操作。

mixed_operations.py
import sqlite3

with sqlite3.connect('inventory.db') as conn:
    # Schema setup with executescript
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY,
            name TEXT,
            price REAL,
            stock INTEGER
        );
        
        CREATE TABLE IF NOT EXISTS categories (
            id INTEGER PRIMARY KEY,
            name TEXT
        );
    """)
    
    # Parameterized inserts with execute
    cursor = conn.cursor()
    products = [
        ('Laptop', 999.99, 10),
        ('Phone', 699.99, 25),
        ('Tablet', 399.99, 15)
    ]
    cursor.executemany("INSERT INTO products (name, price, stock) VALUES (?, ?, ?)", products)

此示例使用 executescript 进行模式创建,使用常规 execute 进行参数化插入。

该组合提供了脚本执行的便利性和参数化查询的安全性。

性能注意事项

此示例演示了 executescript 如何提高批量操作的性能。

bulk_operations.py
import sqlite3
import time

def time_operations():
    with sqlite3.connect('perf_test.db') as conn:
        conn.execute("DROP TABLE IF EXISTS test_data")
        conn.execute("CREATE TABLE test_data (id INTEGER, value TEXT)")
        
        # Method 1: Individual executes
        start = time.time()
        cursor = conn.cursor()
        for i in range(1000):
            cursor.execute(f"INSERT INTO test_data VALUES ({i}, 'test{i}')")
        conn.commit()
        print(f"Individual executes: {time.time() - start:.3f} seconds")
        
        # Method 2: executescript
        conn.execute("DROP TABLE test_data")
        conn.execute("CREATE TABLE test_data (id INTEGER, value TEXT)")
        
        start = time.time()
        script = "\n".join(f"INSERT INTO test_data VALUES ({i}, 'test{i}');" 
                          for i in range(1000))
        conn.executescript(script)
        print(f"executescript: {time.time() - start:.3f} seconds")

time_operations()

该示例比较了单个插入与单个 executescript 调用。 对于批量操作,脚本方法通常更快。

但是,对于非常大的脚本,应考虑内存使用情况和 SQLite 的 SQL 长度限制。

复杂数据库初始化

此示例显示了一个完整的数据库初始化脚本,其中包含视图、索引和示例数据。

database_init.py
import sqlite3

def initialize_database(db_file):
    with sqlite3.connect(db_file) as conn:
        init_script = """
            PRAGMA foreign_keys = ON;
            PRAGMA journal_mode = WAL;
            
            -- Main tables
            CREATE TABLE IF NOT EXISTS customers (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL,
                email TEXT UNIQUE,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP
            );
            
            CREATE TABLE IF NOT EXISTS orders (
                id INTEGER PRIMARY KEY,
                customer_id INTEGER NOT NULL,
                amount REAL NOT NULL,
                status TEXT DEFAULT 'pending',
                created_at TEXT DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY(customer_id) REFERENCES customers(id)
            );
            
            -- Indexes
            CREATE INDEX IF NOT EXISTS idx_orders_customer ON orders(customer_id);
            CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);
            
            -- Views
            CREATE VIEW IF NOT EXISTS customer_totals AS
                SELECT c.id, c.name, SUM(o.amount) as total_spent
                FROM customers c
                LEFT JOIN orders o ON c.id = o.customer_id
                GROUP BY c.id;
                
            -- Sample data
            INSERT INTO customers (name, email) VALUES 
                ('John Doe', 'john@example.com'),
                ('Jane Smith', 'jane@example.com');
                
            INSERT INTO orders (customer_id, amount) VALUES
                (1, 99.99),
                (1, 49.99),
                (2, 149.99);
        """
        conn.executescript(init_script)

initialize_database('ecommerce.db')

此综合初始化脚本使用表、索引、视图和示例数据设置完整的数据库模式。

使用 executescript 可确保原子地创建所有组件,并且 PRAGMA 语句在模式创建之前配置数据库行为。

最佳实践

资料来源

作者

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

列出所有 Python 教程