本文目录导读:

我来详细介绍Python操作数据库DDL变更的几种常见方式:
使用原生数据库驱动
MySQL (使用mysql-connector-python)
import mysql.connector
from mysql.connector import errorcode
def execute_ddl():
config = {
'user': 'your_user',
'password': 'your_password',
'host': 'localhost',
'database': 'your_db',
'raise_on_warnings': True
}
try:
conn = mysql.connector.connect(**config)
cursor = conn.cursor()
# DDL 操作示例
ddl_statements = [
# 创建表
"""CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)""",
# 添加列
"ALTER TABLE users ADD COLUMN phone VARCHAR(20) AFTER email",
# 修改列
"ALTER TABLE users MODIFY COLUMN phone VARCHAR(30)",
# 添加索引
"CREATE INDEX idx_username ON users(username)",
# 添加外键
"""ALTER TABLE orders
ADD CONSTRAINT fk_user_id
FOREIGN KEY (user_id) REFERENCES users(id)"""
]
for statement in ddl_statements:
try:
cursor.execute(statement)
print(f"执行成功: {statement[:50]}...")
except mysql.connector.Error as err:
print(f"执行失败: {err}")
conn.rollback()
conn.commit()
except mysql.connector.Error as err:
if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
print("用户名或密码错误")
elif err.errno == errorcode.ER_BAD_DB_ERROR:
print("数据库不存在")
else:
print(f"连接错误: {err}")
finally:
if 'conn' in locals() and conn.is_connected():
cursor.close()
conn.close()
PostgreSQL (使用psycopg2)
import psycopg2
from psycopg2 import sql
def postgresql_ddl():
conn = psycopg2.connect(
host="localhost",
database="your_db",
user="your_user",
password="your_password"
)
try:
cur = conn.cursor()
# 使用参数化DDL
table_name = "employees"
column_name = "salary"
column_type = "DECIMAL(10,2)"
# 创建表
create_table = sql.SQL("""
CREATE TABLE IF NOT EXISTS {} (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
{} {},
hire_date DATE
)
""").format(
sql.Identifier(table_name),
sql.Identifier(column_name),
sql.SQL(column_type)
)
cur.execute(create_table)
# 检查表是否存在
cur.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = %s
)
""", (table_name,))
exists = cur.fetchone()[0]
print(f"表 {table_name} 是否存在: {exists}")
conn.commit()
except Exception as e:
print(f"DDL执行失败: {e}")
conn.rollback()
finally:
cur.close()
conn.close()
使用ORM框架 (SQLAlchemy)
基本DDL操作
from sqlalchemy import create_engine, text, MetaData, Table, Column, Integer, String
from sqlalchemy.exc import SQLAlchemyError
def sqlalchemy_ddl():
# 创建数据库引擎
engine = create_engine('mysql+pymysql://user:password@localhost/dbname')
try:
with engine.connect() as conn:
# 直接执行DDL
conn.execute(text("""
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price DECIMAL(10,2),
INDEX idx_name (name)
)
"""))
# 使用DDL对象
from sqlalchemy.schema import CreateTable, AddColumn
# 添加列
conn.execute(text("ALTER TABLE products ADD COLUMN category VARCHAR(100)"))
# 修改列
conn.execute(text("ALTER TABLE products MODIFY COLUMN price DECIMAL(12,2)"))
conn.commit()
print("DDL操作完成")
except SQLAlchemyError as e:
print(f"数据库错误: {e}")
使用Alembic管理DDL变更
# alembic/versions/xxxx_create_tables.py
"""create tables
Revision ID: 1234567890
Revises:
Create Date: 2024-01-01 10:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '1234567890'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# 创建表
op.create_table(
'accounts',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=100), nullable=True),
sa.Column('balance', sa.Float(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
# 添加列
op.add_column('accounts', sa.Column('email', sa.String(length=200)))
# 创建索引
op.create_index('idx_account_name', 'accounts', ['name'])
def downgrade():
# 回滚操作
op.drop_index('idx_account_name', table_name='accounts')
op.drop_column('accounts', 'email')
op.drop_table('accounts')
使用数据库迁移工具
yoyo-migrations 示例
# migrations/0001_create_users.py
from yoyo import step
step(
"""
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""",
"""
DROP TABLE IF EXISTS users
"""
)
step(
"""
ALTER TABLE users ADD COLUMN phone VARCHAR(20)
""",
"""
ALTER TABLE users DROP COLUMN phone
"""
)
通用DDL执行器
import logging
from typing import List, Callable
from contextlib import contextmanager
class DDLExecutor:
def __init__(self, connection_config: dict):
self.config = connection_config
self.setup_logging()
def setup_logging(self):
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger(__name__)
@contextmanager
def get_connection(self):
"""获取数据库连接"""
conn = mysql.connector.connect(**self.config)
try:
yield conn
finally:
conn.close()
def execute_ddl(self, statements: List[str],
transaction: bool = True,
on_error: str = 'stop'):
"""
执行DDL语句列表
Args:
statements: DDL语句列表
transaction: 是否使用事务
on_error: 'stop' 或 'continue'
"""
with self.get_connection() as conn:
cursor = conn.cursor()
try:
for i, statement in enumerate(statements, 1):
try:
self.logger.info(f"执行DDL [{i}]: {statement[:100]}")
cursor.execute(statement)
if not transaction:
conn.commit()
except Exception as e:
self.logger.error(f"DDL [{i}] 失败: {e}")
if on_error == 'stop':
if transaction:
conn.rollback()
raise
elif on_error == 'continue':
continue
if transaction:
conn.commit()
self.logger.info("所有DDL执行完成")
finally:
cursor.close()
def validate_ddl(self, statement: str) -> bool:
"""验证DDL语句语法"""
import sqlparse
try:
parsed = sqlparse.parse(statement)
return len(parsed) > 0
except:
return False
# 使用示例
executor = DDLExecutor({
'host': 'localhost',
'database': 'test_db',
'user': 'root',
'password': 'password'
})
# 批量执行DDL
ddls = [
"CREATE TABLE IF NOT EXISTS table1 (id INT PRIMARY KEY)",
"ALTER TABLE table1 ADD COLUMN name VARCHAR(50)",
"CREATE INDEX idx_name ON table1(name)"
]
try:
executor.execute_ddl(ddls, transaction=True, on_error='stop')
except Exception as e:
print(f"DDL执行过程出错: {e}")
最佳实践建议
安全执行DDL的封装
import time
from functools import wraps
def retry_on_lock(max_retries=3, delay=1):
"""DDL锁等待重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if "lock" in str(e).lower() and attempt < max_retries - 1:
time.sleep(delay * (attempt + 1))
continue
raise
return func(*args, **kwargs)
return wrapper
return decorator
class SafeDDLExecutor:
def __init__(self, connection):
self.conn = connection
self.backup_statements = []
@retry_on_lock(max_retries=3)
def execute_alter_table(self, table_name: str, alter_clause: str):
"""安全执行ALTER TABLE"""
# 先检查表是否存在
cursor = self.conn.cursor()
cursor.execute(f"SHOW TABLES LIKE '{table_name}'")
if not cursor.fetchone():
raise ValueError(f"表 {table_name} 不存在")
# 备份当前结构
cursor.execute(f"SHOW CREATE TABLE {table_name}")
create_stmt = cursor.fetchone()[1]
self.backup_statements.append(create_stmt)
# 执行ALTER
alter_stmt = f"ALTER TABLE {table_name} {alter_clause}"
cursor.execute(alter_stmt)
self.conn.commit()
print(f"成功执行: {alter_stmt}")
def rollback_last(self):
"""回滚最后一次DDL变更"""
if self.backup_statements:
last_backup = self.backup_statements.pop()
# 使用备份的CREATE语句重新创建表
cursor = self.conn.cursor()
cursor.execute(last_backup)
self.conn.commit()
# 使用示例
conn = mysql.connector.connect(**config)
executor = SafeDDLExecutor(conn)
try:
executor.execute_alter_table("users", "ADD COLUMN age INT DEFAULT 0")
except Exception as e:
print(f"DDL失败: {e}")
executor.rollback_last()
print("已回滚变更")
- 简单项目:直接使用原生驱动执行DDL即可
- 复杂项目:推荐使用SQLAlchemy + Alembic管理
- 生产环境:必须实现错误处理、事务管理和回滚机制
- 大型变更:考虑使用在线DDL工具(如pt-online-schema-change)
选择哪种方式取决于你的项目规模、团队习惯和数据库类型,建议在生产环境操作前先在测试环境验证。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播