在异步环境下使用 aiomysql 执行 CREATE TABLE IF NOT EXISTS 语句失败原因

在异步环境下使用 aiomysql 执行 CREATE TABLE IF NOT EXISTS 语句失败,通常与以下几个因素有关:

1. 事务隔离级别与隐式提交

MySQL 在 DDL 语句(如 CREATE TABLE)执行时会隐式提交当前事务。而 aiomysql 作为异步驱动,默认会开启事务上下文。这种机制可能导致以下问题:

  • 事务嵌套冲突:DDL 语句强制提交事务,破坏了异步操作的事务上下文
  • 锁等待超时:高并发下,表结构操作可能被其他事务阻塞

2. 错误处理机制不完善

异步环境中,SQL 执行错误可能被异步任务捕获,但未正确传播到上层调用。例如:

async def create_table():
    async with pool.acquire() as conn:
        async with conn.cursor() as cursor:
            try:
                await cursor.execute(create_table_sql)
                await conn.commit()  # 显式提交,但可能已被隐式提交破坏
            except Exception as e:
                print(f"Error: {e}")  # 错误被捕获但未处理

3. 连接池配置问题

aiomysql 的连接池默认配置可能不适合 DDL 操作,例如:

# 创建连接池时未指定合适的参数
pool = await aiomysql.create_pool(
    host='localhost',
    port=3306,
    user='user',
    password='password',
    db='test',
    autocommit=False  # 默认关闭自动提交,可能导致问题
)

解决方案

方案一:使用 autocommit 模式
async def create_table_safe():
    async with pool.acquire() as conn:
        async with conn.cursor() as cursor:
            # 临时开启自动提交模式
            await conn.autocommit(True)
            try:
                await cursor.execute(create_table_sql)
            except Exception as e:
                print(f"Table creation failed: {e}")
            finally:
                # 恢复原设置(如果需要)
                await conn.autocommit(False)
方案二:显式处理事务边界
async def ensure_table_exists():
    async with pool.acquire() as conn:
        async with conn.cursor() as cursor:
            # 检查表格是否已存在
            await cursor.execute("SHOW TABLES LIKE 'car_info'")
            if not await cursor.fetchone():
                try:
                    # 开始事务
                    await conn.begin()
                    await cursor.execute(create_table_sql)
                    await conn.commit()
                except Exception as e:
                    await conn.rollback()
                    raise RuntimeError(f"Table creation failed: {e}")
方案三:使用数据库元数据验证
async def create_table_safe():
    async with pool.acquire() as conn:
        async with conn.cursor() as cursor:
            try:
                await cursor.execute(create_table_sql)
            except aiomysql.Error as e:
                # 忽略表已存在的错误
                if e.args[0] != 1050:  # ER_TABLE_EXISTS_ERROR
                    raise

关键排查步骤

  1. 检查 MySQL 错误日志:查看是否有表创建失败的具体原因
  2. 验证连接池配置:确保 autocommit 设置符合操作需求
  3. 捕获详细错误信息:在异常处理中打印完整的错误堆栈
  4. 使用 SHOW CREATE TABLE 验证:检查实际表结构是否与预期一致

通过以上方法,应该能够解决 aiomysql 异步环境下 IF NOT EXISTS 失效的问题。

修复一下 这个 mysql 语句 -- 创建 lottery_system_janada 数据库,若该数据库不存在,则进行创建操作 CREATE DATABASE IF NOT EXISTS lottery_system_janada; -- 切换到 lottery_system_janada 数据库,后续操作将在此数据库中进行 USE lottery_system_janada; -- 创建 Terminals 表,用于存储终端代理的相关信息 CREATE TABLE IF NOT EXISTS Terminals ( -- 终端自增主键,唯一标识每个终端记录 terminal_id INT AUTO_INCREMENT PRIMARY KEY, -- 终端名称,不能为空,用于标识终端的名称 terminal_name VARCHAR(255) NOT NULL, -- 终端所在位置,可用于记录终端的物理位置信息 location VARCHAR(255), -- 终端状态,使用枚举类型,只能为 'active' 或 'inactive',默认状态为 'active' status ENUM('active', 'inactive') DEFAULT 'active' ); -- 创建 Users 表,用于存储系统用户的基本信息 CREATE TABLE IF NOT EXISTS Users ( -- 用户自增主键,唯一标识每个用户记录 user_id INT AUTO_INCREMENT PRIMARY KEY, -- 用户名,不能为空且必须唯一,用于用户登录和标识 username VARCHAR(255) NOT NULL UNIQUE, -- 用户密码,不能为空,用于用户登录验证 password VARCHAR(255) NOT NULL, -- 用户邮箱,必须唯一,可用于找回密码等操作 email VARCHAR(255) UNIQUE, -- 用户注册日期,默认值为当前时间,记录用户注册的时间 registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 创建 Bets 表,用于记录用户的投注信息 CREATE TABLE IF NOT EXISTS Bets ( -- 投注记录自增主键,唯一标识每条投注记录 bet_id INT AUTO_INCREMENT PRIMARY KEY, -- 关联 Users 表的用户 ID,用于标识该投注是哪个用户进行的 user_id INT, -- 关联 Terminals 表的终端 ID,用于标识该投注是通过哪个终端进行的 terminal_id INT, -- 投注金额,使用 DECIMAL 类型,精度为 10 位,保留 2 位小数,不能为空 bet_amount DECIMAL(10, 2) NOT NULL, -- 投注时间,默认值为当前时间,记录投注发生的时间 bet_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 外键约束,关联 Users 表的 user_id,确保引用的用户 ID 存在于 Users 表中 FOREIGN KEY (user_id) REFERENCES Users(user_id), -- 外键约束,关联 Terminals 表的 terminal_id,确保引用的终端 ID 存在于 Terminals 表中 FOREIGN KEY (terminal_id) REFERENCES Terminals(terminal_id) ); -- 创建 Draws 表,用于记录抽奖活动的相关信息 CREATE TABLE IF NOT EXISTS Draws ( -- 抽奖记录自增主键,唯一标识每条抽奖记录 draw_id INT AUTO_INCREMENT PRIMARY KEY, -- 抽奖日期,不能为空,记录抽奖活动发生的时间 draw_date TIMESTAMP NOT NULL, -- 中奖号码,使用 VARCHAR 类型,用于记录本次抽奖的中奖号码 winning_numbers VARCHAR(255) ); -- 创建 Profits 表,用于记录终端的盈利信息 CREATE TABLE IF NOT EXISTS Profits ( -- 盈利记录自增主键,唯一标识每条盈利记录 profit_id INT AUTO_INCREMENT PRIMARY KEY, -- 关联 Terminals 表的终端 ID,用于标识该盈利是哪个终端产生的 terminal_id INT, -- 关联 Draws 表的抽奖 ID,用于标识该盈利与哪次抽奖活动相关 draw_id INT, -- 盈利金额,使用 DECIMAL 类型,精度为 10 位,保留 2 位小数,不能为空 profit_amount DECIMAL(10, 2) NOT NULL, -- 盈利日期,默认值为当前时间,记录盈利发生的时间 profit_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 外键约束,关联 Terminals 表的 terminal_id,确保引用的终端 ID 存在于 Terminals 表中 FOREIGN KEY (terminal_id) REFERENCES Terminals(terminal_id), -- 外键约束,关联 Draws 表的 draw_id,确保引用的抽奖 ID 存在于 Draws 表中 FOREIGN KEY (draw_id) REFERENCES Draws(draw_id) ); -- 创建 TerminalOdds 表,用于存储终端的赔率信息 CREATE TABLE IF NOT EXISTS TerminalOdds ( -- 赔率记录自增主键,唯一标识每条赔率记录 odds_id INT AUTO_INCREMENT PRIMARY KEY, -- 关联 Terminals 表的终端 ID,用于标识该赔率信息是哪个终端的 terminal_id INT, -- 赔率类型,不能为空,用于区分不同类型的赔率 odds_type VARCHAR(255) NOT NULL, -- 赔率值,使用 DECIMAL 类型,精度为 5 位,保留 2 位小数,不能为空 odds_value DECIMAL(5, 2) NOT NULL, -- 赔率生效日期,默认值为当前时间,记录该赔率开始生效的时间 effective_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 外键约束,关联 Terminals 表的 terminal_id,确保引用的终端 ID 存在于 Terminals 表中 FOREIGN KEY (terminal_id) REFERENCES Terminals(terminal_id) ); -- 创建 TradeRecords 表,用于记录用户的交易信息 CREATE TABLE IF NOT EXISTS TradeRecords ( -- 交易记录自增主键,唯一标识每条交易记录 record_id INT AUTO_INCREMENT PRIMARY KEY, -- 关联 Users 表的用户 ID,用于标识该交易是哪个用户进行的 user_id INT, -- 关联 Terminals 表的终端 ID,用于标识该交易是通过哪个终端进行的 terminal_id INT, -- 交易类型,使用枚举类型,只能为 'deposit'(充值)、'withdrawal'(提现)、'bet'(投注),不能为空 trade_type ENUM('deposit', 'withdrawal', 'bet') NOT NULL, -- 交易金额,使用 DECIMAL 类型,精度为 10 位,保留 2 位小数,不能为空 amount DECIMAL(10, 2) NOT NULL, -- 交易时间,默认值为当前时间,记录交易发生的时间 trade_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 外键约束,关联 Users 表的 user_id,确保引用的用户 ID 存在于 Users 表中 FOREIGN KEY (user_id) REFERENCES Users(user_id), -- 外键约束,关联 Terminals 表的 terminal_id,确保引用的终端 ID 存在于 Terminals 表中 FOREIGN KEY (terminal_id) REFERENCES Terminals(terminal_id) );
11-10
以下是我原本使用node+ts+sqlite3的连接sqlite数据库的代码,现在从sqlite3改为了使用better-sqlite3,是应该修改为同步代码,请你修改连接数据库创建索引等处的api。 const sqlite3 = require('better-sqlite3') import { open, Database as AsyncDatabase } from 'sqlite'; import path from 'path'; // import { Logger } from '@/utils/Logger'; class Database { private db: AsyncDatabase | null; // private logger = new Logger('Database'); todo??? constructor() { this.db = null; } async connect(dbPath?: string): Promise<void> { try { console.log(111222) const finalPath = dbPath || path.join(__dirname, '../../database/vct.db'); // this.db = await open({ // filename: finalPath, // driver: sqlite3.Database // }); this.db = sqlite3('../../database/vct.db') console.log(222); // 启用WAL模式提高并发性能 await this.db.exec('PRAGMA journal_mode = WAL;'); await this.db.exec('PRAGMA synchronous = NORMAL;'); await this.db.exec('PRAGMA cache_size = 1000;'); await this.db.exec('PRAGMA foreign_keys = OK;'); await this.initTables(); // this.logger.info('数据库连接成功'); } catch (error) { console.error(error) // this.logger.error('数据库连接失败', error); throw error; } } async initTables(): Promise<void> { if (!this.db) throw new Error('数据库未连接'); // 创建设备表 await this.db.exec(` CREATE TABLE IF NOT EXISTS devices ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT UNIQUE NOT NULL, name TEXT NOT NULL, ip_address TEXT NOT NULL, port INTEGER NOT NULL DEFAULT 80, mac_address TEXT, model TEXT, firmware_version TEXT, username TEXT NOT NULL, password TEXT NOT NULL, token TEXT, token_expires_at DATETIME, status TEXT DEFAULT 'offline' CHECK(status IN ('online', 'offline', 'error', 'connecting')), is_initialized BOOLEAN DEFAULT 0, config_data TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // 创建发现设备缓存表 await this.db.exec(` CREATE TABLE IF NOT EXISTS discovered_devices ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT NOT NULL, ip_address TEXT NOT NULL, port INTEGER NOT NULL DEFAULT 80, mac_address TEXT, model TEXT, firmware_version TEXT, last_seen DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(device_id, ip_address) ) `); // 创建索引 await this.db.exec('CREATE INDEX IF NOT EXISTS idx_devices_device_id ON devices(device_id)'); await this.db.exec('CREATE INDEX IF NOT EXISTS idx_devices_ip_address ON devices(ip_address)'); await this.db.exec('CREATE INDEX IF NOT EXISTS idx_discovered_device_id ON discovered_devices(device_id)'); await this.db.exec('CREATE INDEX IF NOT EXISTS idx_discovered_ip_address ON discovered_devices(ip_address)'); } getDB(): AsyncDatabase { if (!this.db) { throw new Error('数据库未连接'); } return this.db; } async close(): Promise<void> { if (this.db) { await this.db.close(); this.db = null; // this.logger.info('数据库连接已关闭'); } } } module.exports = Database;
09-05
代码6:import asyncio import logging import aiomysql from server.src.mvc.database import Database from common.models import User class ServerModel: """服务器模型""" def __init__(self, controller): self.controller = controller self.db = Database() async def init_database(self): """对数据库进行初始化""" try: await self.db.init_database() except RuntimeError as e: # 处理初始化失败场景 self.controller.update_status(e) async def authenticate_user(self, username, password): """验证用户""" try: query = "SELECT user_id, username, role FROM users WHERE username = %s AND password = %s" result = await self.db.execute_query(query, (username, password)) if not result: return None user_data = result[0] return User( user_id=user_data["user_id"], username=user_data["username"], role=user_data["role"] ) except (RuntimeError, aiomysql.Error) as e: logging.error(f"认证异常: {e}") # 遵循返回None的设计约定 return None 代码7:import asyncio import aiomysql import logging class Database: """异步数据库操作""" def __init__(self, host="localhost", port=3306, user="root", password="123456", db="erp"): """异步配置数据库操作""" # 地址 self.host = host # 端口 self.port = port # 登录账号 self.user = user # 登录密码 self.password = password # 数据库名称 self.db = db self.pool = None # 添加连接锁防止竞态条件 self.connection_lock = asyncio.Lock() async def _ensure_connection(self): """连接数据库""" async with self.connection_lock: if self.pool is None: try: #先尝试连接到MySQL 服务器,不指定数据 temp_pool = await aiomysql.create_pool( host=self.host, port=self.port, user=self.user, password=self.password, autocommit=True, minsize=1, maxsize=20 ) async with temp_pool.acquire() as conn: async with conn.cursor() as cursor: # 检查数据库是否存在 await cursor.execute(f"SHOW DATABASES LIKE '{self.db}'") result = await cursor.fetchone() if not result: # 如果数据库不存在,则创建数据库 await cursor.execute(f"CREATE DATABASE {self.db}") logging.info(f"数据库 {self.db} 创建成功") # 关闭临时地址池 temp_pool.close() await temp_pool.wait_closed() # 创建执行数据库的连接池 self.pool = await aiomysql.create_pool( host=self.host, port=self.port, user=self.user, password=self.password, db=self.db, autocommit=True, minsize=1, maxsize=20, # 根据负载调整连接池大小 connect_timeout=10, # 连接超时时间(秒) charset='utf8mb4', # 字符集设置 pool_recycle=3600 # 连接回收时间(秒) ) logging.info("数据库连接池创建成功") except (aiomysql.Error, OSError) as e: logging.critical(f"数据库连接失败: {e}") raise ConnectionError(f"Database connection failed: {e}") async def disconnect(self): """关闭数据库连接""" if self.pool: # 关闭连接池 self.pool.close() # 等待所有连接释放 await self.pool.wait_closed() self.pool = None logging.info("数据库连接已关闭") async def init_database(self): """初始化数据库""" try: """保证DDL操作""" await self._ensure_connection() async with self.pool.acquire() as conn: async with conn.cursor() as cursor: # 批量执行创建语句 ddl_commands = [ # 创建创建用户表,IF NOT EXISTS users可以使该表不存在时才会创建 """CREATE TABLE IF NOT EXISTS users ( user_id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, role ENUM('admin', 'user') NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)""", # 创建产品表 """CREATE TABLE IF NOT EXISTS products ( product_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, price DECIMAL(10, 2) NOT NULL, stock INT NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)""", # 创建订单表 """CREATE TABLE IF NOT EXISTS orders ( order_id INT AUTO_INCREMENT PRIMARY KEY, customer VARCHAR(100) NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, status ENUM('pending', 'completed', 'cancelled') NOT NULL DEFAULT 'pending', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (product_id) REFERENCES products(product_id))""", # 创建角色表 """CREATE TABLE IF NOT EXISTS roles ( role_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, description VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )""", # 创建权限表 """CREATE TABLE IF NOT EXISTS permissions ( permission_id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, description VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)""", # 创建角色-权限关联表 """CREATE TABLE IF NOT EXISTS role_permissions ( role_id INT NOT NULL, permission_id INT NOT NULL, PRIMARY KEY (role_id, permission_id), FOREIGN KEY (role_id) REFERENCES roles(role_id), FOREIGN KEY (permission_id) REFERENCES permissions(permission_id) )"""] for cmd in ddl_commands: await cursor.execute(cmd) # 执行数据初始化 await conn.begin() try: # 初始化登录数据 await cursor.execute( """INSERT INTO users (username, password, role) VALUES ('admin', 'admin123', 'admin'), ('user', 'user123', 'user') ON DUPLICATE KEY UPDATE username=username""") # 添加测试角色和权限 await cursor.execute( """INSERT INTO roles (name, description) VALUES ('admin', '系统管理员'), ('user', '普通用户') ON DUPLICATE KEY UPDATE name=name""") # 创建许可表 await cursor.execute(""" INSERT INTO permissions (name, description) VALUES ('view_products', '查看产品'), ('add_product', '添加产品'), ('edit_product', '编辑产品'), ('delete_product', '删除产品'), ('view_orders', '查看订单'), ('create_order', '创建订单'), ('edit_order', '编辑订单'), ('delete_order', '删除订单'), ('manage_users', '管理用户'), ('view_logs', '查看日志') ON DUPLICATE KEY UPDATE name=name """) # 建立角色-权限关联 await cursor.execute(""" INSERT INTO role_permissions (role_id, permission_id) VALUES (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (1, 10), -- 管理员权限 (2, 1), (2, 5), (2, 6) -- 用户权限 ON DUPLICATE KEY UPDATE role_id=role_id """) await conn.commit() logging.info("数据库初始化完成") except Exception as e: await conn.rollback() logging.error(f"数据初始化失败: {e}") raise except Exception as e: logging.exception("数据库初始化致命错误") await self.disconnect() raise RuntimeError(f"Init database failed: {e}") from e async def execute_update(self, query, params=None): """执行更新""" try: await self._ensure_connection() async with self.pool.acquire() as conn: async with conn.cursor() as cursor: await cursor.execute(query, params or ()) return cursor.rowcount except (aiomysql.Error, RuntimeError) as e: logging.error(f"执行更新失败: {e}\nQuery: {query}") raise async def execute_query(self, query, params=None): """执行查询""" try: await self._ensure_connection() async with self.pool.acquire() as conn: async with conn.cursor(aiomysql.DictCursor) as cursor: await cursor.execute(query, params or ()) return await cursor.fetchall() except (aiomysql.Error, RuntimeError) as e: logging.error(f"执行查询失败: {e}\nQuery: {query}") raise
07-12
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值