目录

  1. 引言:为何及何时修改表结构?
    • 1.1 数据库模式变更需求
    • 1.2 SQLite ALTER TABLE 的特点与限制
  2. Python 与 SQLite 交互基础
    • 2.1 建立连接与游标
    • 2.2 执行 SQL 语句与事务管理
  3. SQLite 原生 ALTER TABLE 语句详解
    • 3.1 ALTER TABLE ADD COLUMN:添加新列 (最常用)
      • 语法与规则
      • Python 实现
      • 注意事项 (默认值、NOT NULL 约束)
    • 3.2 ALTER TABLE RENAME TO:重命名表
      • 语法
      • Python 实现
    • 3.3 ALTER TABLE RENAME COLUMN TO:重命名列 (SQLite 3.25.0+ 特性)
      • 语法
      • Python 实现
      • 版本要求与兼容性
  4. SQLite ALTER TABLE 的局限性及其通用解决方案
    • 4.1 常见局限:删除列、修改列类型、添加复杂约束等
    • 4.2 通用重构方法 (Create-Copy-Drop-Rename 模式)
      • 核心步骤详解
      • Python 代码实现 (示例:删除列、修改列类型)
  5. 最佳实践与注意事项
    • 5.1 使用 with 语句管理连接
    • 5.2 事务处理 (COMMIT, ROLLBACK)
    • 5.3 错误处理
    • 5.4 务必在操作前备份数据库
    • 5.5 性能考量 (对于大型表)
    • 5.6 表名和列名的大小写敏感性
  6. 综合代码示例
  7. 总结

1. 引言:为何及何时修改表结构?

在应用程序的生命周期中,数据库的表结构(Schema)很少是一成不变的。随着功能迭代、需求变化或性能优化,我们经常需要对现有表进行修改。

1.1 数据库模式变更需求

  • 新增功能:需要存储新的数据类型,例如给用户表添加一个 last_login_time 列。
  • 数据模型调整:某个列的类型需要从 TEXT 变为 INTEGER,或者某个表需要拆分。
  • 命名规范统一:将 user_name 列重命名为 username
  • 优化或废弃:删除不再需要的列,或者为了性能调整索引。

1.2 SQLite ALTER TABLE 的特点与限制

相较于 PostgreSQL、MySQL 等功能全面的关系型数据库,SQLite 的 ALTER TABLE 语句功能相对有限。了解这些限制对于在 Python 中正确操作 SQLite 至关重要:

  • 支持的操作
    • ADD COLUMN (添加列)
    • RENAME TABLE (重命名表)
    • RENAME COLUMN (重仅在 SQLite 3.25.0 及更高版本中支持)
  • 不支持的操作 (直接)
    • DROP COLUMN (删除列) - 直到 SQLite 3.35.0 才支持,但在此之前以及考虑到兼容性,通常需要复杂的工作流。
    • ALTER COLUMN (修改列类型、NOT NULL 约束等)
    • ADD PRIMARY KEYDROP PRIMARY KEY (直接修改主键)
    • 添加或删除外键约束 (通常需要在创建表时定义)

由于这些限制,对于许多复杂的表结构修改,我们需要采用一种“创建新表-复制数据-删除旧表-重命名新表”的通用策略。

2. Python 与 SQLite 交互基础

在 Python 中修改 SQLite 表结构,首先需要掌握基本的数据库连接和操作。

2.1 建立连接与游标

使用 sqlite3.connect() 建立与数据库的连接,并通过 conn.cursor() 获取游标对象来执行 SQL 语句。

import sqlite3
import os

DB_FILE = "my_app.db"

def connect_db():
    """建立数据库连接"""
    try:
        conn = sqlite3.connect(DB_FILE)
        return conn
    except sqlite3.Error as e:
        print(f"连接数据库失败: {e}")
        return None

def create_initial_table():
    """创建初始的 'users' 表用于演示"""
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE) # 清理旧的测试文件

    with connect_db() as conn:
        cursor = conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL
            );
        ''')
        cursor.execute("INSERT INTO users (name) VALUES ('Alice'), ('Bob');")
        conn.commit()
    print(f"数据库 '{DB_FILE}' 已创建,并初始化了 'users' 表。")

def fetch_table_schema(table_name):
    """获取并打印表的结构 (为了验证修改)"""
    with connect_db() as conn:
        cursor = conn.cursor()
        cursor.execute(f"PRAGMA table_info({table_name});")
        print(f"\nSchema for '{table_name}':")
        for col in cursor.fetchall():
            print(f"  Column: {col[1]}, Type: {col[2]}, NotNull: {col[3]}, Default: {col[4]}")
        cursor.execute(f"SELECT * FROM {table_name};")
        print(f"Data in '{table_name}':")
        for row in cursor.fetchall():
            print(f"  {row}")

2.2 执行 SQL 语句与事务管理

所有 ALTER TABLE 操作都需要通过 cursor.execute() 执行。修改数据库结构是写操作,因此在执行成功后需要调用 conn.commit() 来保存更改。如果发生错误,可以通过 conn.rollback() 来撤销未提交的更改。

3. SQLite 原生 ALTER TABLE 语句详解

3.1 ALTER TABLE ADD COLUMN:添加新列

这是最常见且最简单的 ALTER TABLE 操作。

  • 语法与规则:

    ALTER TABLE table_name ADD COLUMN column_name data_type [column_constraint];
    
    • 新添加的列会追加到表的末尾。
    • 如果新列定义为 NOT NULL,则必须为其指定一个 DEFAULT 值,否则会报错(因为现有行无法填充 NULL)。
    • 如果没有指定 DEFAULT 值,新列对于所有现有行将默认为 NULL
  • Python 实现:

    def add_column_to_users(db_file):
        """演示如何向 'users' 表添加新列 'email'"""
        try:
            with sqlite3.connect(db_file) as conn:
                cursor = conn.cursor()
                cursor.execute("ALTER TABLE users ADD COLUMN email TEXT;")
                conn.commit()
            print("成功向 'users' 表添加了 'email' 列。")
        except sqlite3.Error as e:
            print(f"添加列失败: {e}")
    
    def add_column_with_default(db_file):
        """演示如何向 'users' 表添加新列 'age' 并带默认值和 NOT NULL 约束"""
        try:
            with sqlite3.connect(db_file) as conn:
                cursor = conn.cursor()
                # 必须指定 DEFAULT 值,因为是 NOT NULL
                cursor.execute("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 18;")
                conn.commit()
            print("成功向 'users' 表添加了 'age' 列 (INTEGER NOT NULL DEFAULT 18)。")
        except sqlite3.Error as e:
            print(f"添加列失败: {e}")
    
  • 注意事项

    • 如果新列是 NOT NULL 的,必须提供 DEFAULT,因为现有的数据行没有该列的值。
    • 在 SQLite 中,一旦一个列被声明为 NOT NULL,它就不能再被改为 NULL,反之亦然(直接 ALTER COLUMN 不支持)。

3.2 ALTER TABLE RENAME TO:重命名表

  • 语法:

    ALTER TABLE old_table_name RENAME TO new_table_name;
    
  • Python 实现:

    def rename_table(db_file, old_name, new_name):
        """演示如何重命名表"""
        try:
            with sqlite3.connect(db_file) as conn:
                cursor = conn.cursor()
                cursor.execute(f"ALTER TABLE {old_name} RENAME TO {new_name};")
                conn.commit()
            print(f"成功将表 '{old_name}' 重命名为 '{new_name}'。")
        except sqlite3.Error as e:
            print(f"重命名表失败: {e}")
    

3.3 ALTER TABLE RENAME COLUMN TO:重命名列

  • 语法:

    ALTER TABLE table_name RENAME COLUMN old_column_name TO new_column_name;
    
  • Python 实现:

    def rename_column(db_file, table_name, old_col_name, new_col_name):
        """演示如何重命名列 (需要 SQLite 3.25.0 及以上版本)"""
        try:
            with sqlite3.connect(db_file) as conn:
                cursor = conn.cursor()
                # 检查 SQLite 版本是否支持 RENAME COLUMN
                cursor.execute("PRAGMA user_version;") # 或者检查 sqlite_version
                sqlite_version = sqlite3.sqlite_version
                if sqlite3.sqlite_version_info < (3, 25, 0):
                    print(f"当前 SQLite 版本 {sqlite_version} 不支持 RENAME COLUMN。需要 3.25.0+。")
                    return
    
                cursor.execute(f"ALTER TABLE {table_name} RENAME COLUMN {old_col_name} TO {new_col_name};")
                conn.commit()
            print(f"成功将 '{table_name}' 表中的 '{old_col_name}' 列重命名为 '{new_col_name}'。")
        except sqlite3.Error as e:
            print(f"重命名列失败: {e}")
    
  • 版本要求与兼容性

    • 此功能是在 SQLite 3.25.0 版本中引入的。如果您的 SQLite 版本低于此,上述代码将失败。您可以通过 sqlite3.sqlite_version_infoPRAGMA schema_version; 来检查当前版本。
    • 在旧版本 SQLite 中,重命名列需要使用“Create-Copy-Drop-Rename”通用重构方法。

4. SQLite ALTER TABLE 的局限性及其通用解决方案

由于 SQLite 对 ALTER TABLE 的限制,对于以下操作,我们通常需要使用一种更复杂的通用重构模式。

4.1 常见局限:删除列、修改列类型、添加复杂约束等

  • 删除列 (DROP COLUMN)
    • SQLite 3.35.0 及更高版本原生支持 ALTER TABLE ... DROP COLUMN
    • 在此之前,这是一个主要痛点,必须使用通用重构方法。
  • 修改列类型:例如,将 TEXT 类型改为 INTEGER
  • 修改 NOT NULL 约束:例如,将可为空的列改为 NOT NULL 或反之。
  • 添加复杂的 CHECK 约束、UNIQUE 约束或外键约束:虽然 ADD COLUMN 可以带约束,但对于现有列或更复杂的场景,直接修改会受限。

4.2 通用重构方法 (Create-Copy-Drop-Rename 模式)

这种模式是解决 SQLite ALTER TABLE 限制的通用且可靠的方法。

  • 核心步骤详解:

    1. Start Transaction (开始事务):确保所有步骤都是原子性的,要么全部成功,要么全部回滚。
    2. Create New Table (创建新表):根据期望的新结构,创建一个临时表。这个新表将包含你想要保留的列、新的列类型、新的约束,以及任何删除的列的排除。
    3. Copy Data (复制数据):将旧表中的数据复制到新表中。在这个过程中,你可以对数据进行转换(例如,修改列类型),或者忽略已删除的列。
    4. Drop Old Table (删除旧表):删除原始的旧表。
    5. Rename New Table (重命名新表):将临时创建的新表重命名为旧表的名称。
    6. Recreate Indexes/Triggers/Views (重新创建索引/触发器/视图):在重命名后,任何依赖于旧表的索引、触发器或视图将不再有效。你需要重新创建它们。
    7. Commit Transaction (提交事务):如果所有步骤都成功,提交事务。
  • Python 代码实现 (示例:删除列 ‘email’ 和修改列 ‘name’ 为 VARCHAR(50)):

    def refactor_table_schema(db_file, old_table_name, new_table_name_temp, target_columns_and_types, data_copy_map):
        """
        通用重构方法:Create-Copy-Drop-Rename。
        适用于删除列、修改列类型等 SQLite 不直接支持的操作。
    
        :param db_file: 数据库文件路径
        :param old_table_name: 要修改的原始表名
        :param new_table_name_temp: 临时新表的名称
        :param target_columns_and_types: 字符串列表,定义新表的列 (例如: ["id INTEGER PRIMARY KEY", "username VARCHAR(50) NOT NULL"])
        :param data_copy_map: 字典,旧列名到新列名的映射,用于数据复制
                               例如: {"id": "id", "name": "username"}
        """
        try:
            with sqlite3.connect(db_file) as conn:
                cursor = conn.cursor()
                conn.execute("BEGIN;") # 开始事务
    
                # 1. 创建新表
                new_cols_str = ", ".join(target_columns_and_types)
                create_new_table_sql = f"CREATE TABLE {new_table_name_temp} ({new_cols_str});"
                cursor.execute(create_new_table_sql)
                print(f"临时表 '{new_table_name_temp}' 已创建。")
    
                # 2. 从旧表复制数据到新表
                old_cols = ", ".join(data_copy_map.keys())
                new_cols = ", ".join(data_copy_map.values())
                copy_data_sql = f"INSERT INTO {new_table_name_temp} ({new_cols}) SELECT {old_cols} FROM {old_table_name};"
                cursor.execute(copy_data_sql)
                print(f"数据已从 '{old_table_name}' 复制到 '{new_table_name_temp}'。")
    
                # 3. 删除旧表
                cursor.execute(f"DROP TABLE {old_table_name};")
                print(f"旧表 '{old_table_name}' 已删除。")
    
                # 4. 重命名新表为旧表名
                cursor.execute(f"ALTER TABLE {new_table_name_temp} RENAME TO {old_table_name};")
                print(f"临时表 '{new_table_name_temp}' 已重命名为 '{old_table_name}'。")
    
                # 5. TODO: 重新创建依赖对象 (索引, 触发器, 视图等)
                # 这部分需要额外的逻辑来查询 sqlite_master 表,获取旧表的依赖对象,并重新创建。
                # 简单起见,此处省略,但在实际应用中非常重要。
    
                conn.commit() # 提交事务
                print(f"表 '{old_table_name}' 结构重构成功。")
        except sqlite3.Error as e:
            conn.rollback() # 发生错误时回滚
            print(f"表结构重构失败并已回滚: {e}")
            raise # 重新抛出异常以便调用者处理
    
    def delete_column_and_alter_type_example(db_file):
        """删除 'email' 列,并将 'name' 列修改为 'username' (VARCHAR(50))"""
        print("\n--- 演示删除列和修改列类型 ---")
        old_table = "users"
        temp_table = "users_new_temp"
        
        # 目标新表的列定义
        target_cols = [
            "id INTEGER PRIMARY KEY",
            "username VARCHAR(50) NOT NULL" # name 列被改为 username,类型也变了
            # 'email' 列被移除
        ]
        
        # 数据复制映射:旧列名 -> 新列名
        data_map = {
            "id": "id",
            "name": "username" # 旧的 'name' 列数据复制到新的 'username' 列
        }
    
        try:
            refactor_table_schema(db_file, old_table, temp_table, target_cols, data_map)
        except Exception:
            print("请检查错误信息,可能因为之前的操作导致数据库状态不一致。")
    

5. 最佳实践与注意事项

5.1 使用 with 语句管理连接

始终使用 with sqlite3.connect(...) as conn: 模式。这确保了数据库连接在代码块结束后无论是否发生异常都会被正确关闭,防止资源泄漏。

5.2 事务处理 (COMMIT, ROLLBACK)

对于任何修改数据库结构或数据的操作,都应该使用事务。

  • ALTER TABLE 或重构操作开始前,显式地使用 conn.execute("BEGIN;")conn.isolation_level = None(自动提交模式除外)来开始一个事务。
  • 操作成功后,调用 conn.commit() 来保存更改。
  • 如果过程中出现任何错误,捕获异常并调用 conn.rollback() 来撤销所有未提交的更改,使数据库恢复到操作前的状态,保持数据完整性。

5.3 错误处理

使用 try...except sqlite3.Error 来捕获与 SQLite 相关的错误,并提供有意义的错误信息。这对于调试和用户体验至关重要。

5.4 务必在操作前备份数据库

这是最重要的忠告。任何对数据库模式的修改都可能导致数据丢失或损坏,尤其是在使用通用重构方法时。在执行任何 ALTER TABLE 操作之前,请务必创建数据库文件的完整备份。

import shutil

def backup_database(db_file, backup_path):
    """创建数据库文件的备份"""
    try:
        shutil.copyfile(db_file, backup_path)
        print(f"数据库 '{db_file}' 已备份到 '{backup_path}'。")
    except Exception as e:
        print(f"备份数据库失败: {e}")

5.5 性能考量 (对于大型表)

通用重构方法涉及到创建新表和复制所有数据。对于包含数百万甚至数十亿行数据的大型表,这个操作可能会非常耗时且占用大量磁盘空间。

  • 在生产环境中执行此类操作时,应选择流量较低的时间段。
  • 考虑分批复制数据,或者利用数据库的特定工具(如 sqlite3 命令行工具的 .dump)进行处理。

5.6 表名和列名的大小写敏感性

SQLite 默认情况下对表名和列名是大小写不敏感的,除非在 CREATE TABLE 语句中使用了双引号 " 将名称括起来。在 ALTER TABLE 语句中,也应遵循相同的命名约定。例如,ALTER TABLE "Users" RENAME TO "NewUsers" 会保持大小写敏感性。

6. 综合代码示例

import sqlite3
import os
import shutil
import datetime

DB_FILE = "my_app.db"
BACKUP_DIR = "db_backups"

def init_db():
    """初始化数据库并创建初始表"""
    if os.path.exists(DB_FILE):
        os.remove(DB_FILE)
    os.makedirs(BACKUP_DIR, exist_ok=True) # 确保备份目录存在

    with sqlite3.connect(DB_FILE) as conn:
        cursor = conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP
            );
        ''')
        cursor.execute("INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Charlie');")
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS products (
                prod_id INTEGER PRIMARY KEY,
                item_name TEXT NOT NULL,
                price REAL DEFAULT 0.0
            );
        ''')
        cursor.execute("INSERT INTO products (item_name, price) VALUES ('Laptop', 1200.0), ('Mouse', 25.0);")
        conn.commit()
    print(f"Initialized database '{DB_FILE}' with 'users' and 'products' tables.")

def get_table_schema(table_name):
    """获取并打印表的结构"""
    try:
        with sqlite3.connect(DB_FILE) as conn:
            cursor = conn.cursor()
            cursor.execute(f"PRAGMA table_info('{table_name}');")
            print(f"\n--- Schema for '{table_name}' ---")
            if not cursor.fetchall():
                print(f"Table '{table_name}' does not exist.")
                return False
            cursor.execute(f"PRAGMA table_info('{table_name}');") # Re-execute to print
            for col in cursor.fetchall():
                print(f"  Name: {col[1]}, Type: {col[2]}, NotNull: {bool(col[3])}, Default: {col[4]}")
            return True
    except sqlite3.Error as e:
        print(f"Error getting schema for '{table_name}': {e}")
        return False

def get_table_data(table_name):
    """获取并打印表的所有数据"""
    try:
        with sqlite3.connect(DB_FILE) as conn:
            cursor = conn.cursor()
            cursor.execute(f"SELECT * FROM '{table_name}';")
            print(f"--- Data in '{table_name}' ---")
            rows = cursor.fetchall()
            if not rows:
                print("  No data.")
                return
            for row in rows:
                print(f"  {row}")
    except sqlite3.Error as e:
        print(f"Error getting data for '{table_name}': {e}")

def create_backup():
    """创建数据库备份文件"""
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_file = os.path.join(BACKUP_DIR, f"{os.path.basename(DB_FILE)}_{timestamp}.bak")
    try:
        shutil.copyfile(DB_FILE, backup_file)
        print(f"Database backed up to: {backup_file}")
        return backup_file
    except Exception as e:
        print(f"Failed to create backup: {e}")
        return None

# --- 原生 ALTER TABLE 操作 ---
def add_email_column():
    print("\n--- ADD COLUMN: email (TEXT NULL) ---")
    create_backup()
    try:
        with sqlite3.connect(DB_FILE) as conn:
            cursor = conn.cursor()
            cursor.execute("ALTER TABLE users ADD COLUMN email TEXT;")
            conn.commit()
        print("Column 'email' added to 'users' table.")
    except sqlite3.Error as e:
        print(f"Failed to add column: {e}")
    get_table_schema('users')
    get_table_data('users')

def add_status_column_with_default():
    print("\n--- ADD COLUMN: status (TEXT NOT NULL DEFAULT 'active') ---")
    create_backup()
    try:
        with sqlite3.connect(DB_FILE) as conn:
            cursor = conn.cursor()
            cursor.execute("ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'active';")
            conn.commit()
        print("Column 'status' (TEXT NOT NULL DEFAULT 'active') added to 'users' table.")
    except sqlite3.Error as e:
        print(f"Failed to add column: {e}")
    get_table_schema('users')
    get_table_data('users')

def rename_users_table():
    print("\n--- RENAME TABLE: users -> app_users ---")
    create_backup()
    try:
        with sqlite3.connect(DB_FILE) as conn:
            cursor = conn.cursor()
            cursor.execute("ALTER TABLE users RENAME TO app_users;")
            conn.commit()
        print("Table 'users' renamed to 'app_users'.")
    except sqlite3.Error as e:
        print(f"Failed to rename table: {e}")
    get_table_schema('app_users') # Check new table name
    get_table_data('app_users')
    get_table_schema('users') # Should not exist

def rename_prod_id_column():
    print("\n--- RENAME COLUMN: products.prod_id -> product_id ---")
    create_backup()
    # Check SQLite version for RENAME COLUMN support
    if sqlite3.sqlite_version_info < (3, 25, 0):
        print(f"Skipping RENAME COLUMN. Current SQLite version {sqlite3.sqlite_version} is less than 3.25.0.")
        return

    try:
        with sqlite3.connect(DB_FILE) as conn:
            cursor = conn.cursor()
            cursor.execute("ALTER TABLE products RENAME COLUMN prod_id TO product_id;")
            conn.commit()
        print("Column 'prod_id' in 'products' table renamed to 'product_id'.")
    except sqlite3.Error as e:
        print(f"Failed to rename column: {e}")
    get_table_schema('products')
    get_table_data('products')

# --- 通用重构方法 (Create-Copy-Drop-Rename) ---
def complex_table_refactor():
    print("\n--- Complex Table Refactor: app_users -> users ---")
    print("  (Rename 'name' to 'username' (VARCHAR(50) NOT NULL), remove 'email' & 'status')")
    create_backup()

    old_table_name = "app_users"
    temp_table_name = "app_users_temp_new"

    # Define the new schema for the 'users' table
    # Note: VARCHAR(50) is just for show, SQLite treats it as TEXT
    new_schema_cols = [
        "id INTEGER PRIMARY KEY",
        "username VARCHAR(50) NOT NULL",
        "created_at TEXT DEFAULT CURRENT_TIMESTAMP"
    ]

    # Map old columns to new columns for data transfer
    # 'email' and 'status' are intentionally excluded
    column_mapping = {
        "id": "id",
        "name": "username", # old 'name' maps to new 'username'
        "created_at": "created_at"
    }

    try:
        with sqlite3.connect(DB_FILE) as conn:
            cursor = conn.cursor()
            conn.execute("BEGIN;") # Start transaction for atomic operation

            # 1. Create a new temporary table with the desired schema
            create_temp_table_sql = f"CREATE TABLE {temp_table_name} ({', '.join(new_schema_cols)});"
            cursor.execute(create_temp_table_sql)
            print(f"  Temporary table '{temp_table_name}' created.")

            # 2. Copy data from the old table to the new temporary table
            old_cols_for_select = ", ".join(column_mapping.keys())
            new_cols_for_insert = ", ".join(column_mapping.values())
            copy_data_sql = f"INSERT INTO {temp_table_name} ({new_cols_for_insert}) SELECT {old_cols_for_select} FROM {old_table_name};"
            cursor.execute(copy_data_sql)
            print(f"  Data copied from '{old_table_name}' to '{temp_table_name}'.")

            # 3. Drop the old table
            cursor.execute(f"DROP TABLE {old_table_name};")
            print(f"  Old table '{old_table_name}' dropped.")

            # 4. Rename the temporary table to the original table name
            cursor.execute(f"ALTER TABLE {temp_table_name} RENAME TO {old_table_name};")
            print(f"  Temporary table '{temp_table_name}' renamed to '{old_table_name}'.")

            # TODO: Recreate any dependent indexes, triggers, or views here.
            # (For this example, we assume no external dependencies for simplicity)

            conn.commit() # Commit the transaction
            print(f"Table '{old_table_name}' (now named 'users') schema refactored successfully.")
    except sqlite3.Error as e:
        conn.rollback() # Rollback on error
        print(f"  Table refactoring failed and rolled back: {e}")
        raise # Re-raise to show the error
    except Exception as e:
        print(f"  An unexpected error occurred during refactoring: {e}")
        raise

    get_table_schema('users')
    get_table_data('users')


def main():
    init_db()
    get_table_schema('users')
    get_table_data('users')
    get_table_schema('products')
    get_table_data('products')

    add_email_column()
    add_status_column_with_default()

    rename_users_table() # Renames 'users' to 'app_users'

    rename_prod_id_column() # Renames products.prod_id to products.product_id

    try:
        complex_table_refactor() # Renames 'app_users' back to 'users' and alters its schema
    except Exception:
        print("Complex refactor step encountered an error. Check logs above.")

    print("\n--- Final Database State ---")
    get_table_schema('users')
    get_table_data('users')
    get_table_schema('products')
    get_table_data('products')

    print("\nAll operations complete. Check 'db_backups' directory for backups.")

if __name__ == "__main__":
    main()

7. 总结

为您详尽解析了在 Python 中使用 sqlite3 模块修改 SQLite 表结构的方法。

核心要点回顾:

  • SQLite ALTER TABLE 的局限性: 它仅原生支持 ADD COLUMNRENAME TABLE 和(较新版本)RENAME COLUMN
  • 通用重构方法 (Create-Copy-Drop-Rename): 对于删除列、修改列类型等复杂操作,这是标准且可靠的解决方案。它涉及到创建一个新表,将旧数据复制到新表,删除旧表,然后重命名新表。
  • 关键实践:
    • 始终使用 with 语句管理数据库连接。
    • 通过 BEGIN;COMMIT;ROLLBACK; 来确保操作的原子性和数据完整性。
    • 在执行任何模式修改操作之前,务必进行数据库备份。这是防止数据丢失的最后一道防线。
    • 注意 SQLite 版本对 RENAME COLUMN 的支持。

通过理解这些原则和方法,您将能够自信且安全地在 Python 应用程序中管理 SQLite 数据库的表结构变化。

更多推荐