Django项目中同步插入MySQL比异步更快的原因及优化方案咨询
问题背景
将项目重构为异步实现(基于aiomysql+asyncio),测试后发现同步代码(pymysql)的MySQL插入速度反而更快。两者核心差异如下:
- 数据库驱动:同步使用pymysql 1.1.0,异步使用aiomysql 0.2.0+asyncio
- 均采用pandas完成数据计算,业务逻辑完全一致
- 测试环境为同一电脑、同一网络
- 版本差异:同步代码用Python 3.6+Django 3.2.25,异步代码用Python 3.8+Django 4.2.11
- 异步采用Django异步视图,插入操作通过自定义连接池调用
executemany,单次插入894条数据
慢的原因分析
全局锁导致串行化执行
自定义DatabasePool为每个数据库添加了全局asyncio.Lock,所有数据库操作都必须先获取锁才能执行。这直接让异步操作退化为串行执行,不仅失去了异步并发的优势,还额外增加了协程调度的开销,导致比同步代码更慢。aiomysql版本过于陈旧
使用的aiomysql 0.2.0是非常早期的版本,旧版本在executemany的实现上可能存在性能缺陷,比如未对批量插入做针对性优化,或内部异步调度逻辑存在冗余开销。异步框架的固有开销
异步代码本身存在事件循环调度、协程切换的额外消耗。当仅执行单个批量插入任务时,这些开销会直接体现在总耗时中,而同步代码无此负担。版本差异的隐性影响
Python 3.8与3.6、Django 4.2与3.2之间的底层实现差异,可能引入了额外的处理逻辑,间接拖慢了插入速度。
优化方案
移除全局数据库锁
aiomysql连接池本身已实现协程安全,pool.acquire()方法可被多个协程安全调用,无需额外加锁。删除_locks相关代码,恢复异步并发能力:async def execute_sql(self, db_name, sql, args=None, executemany=False): pool = self._pools.get(db_name) if not pool: raise ValueError(f"No pool found for database: {db_name}") async with pool.acquire() as conn: async with conn.cursor() as cur: try: if executemany: await cur.executemany(sql, args or ()) else: await cur.execute(sql, args or ()) if sql.strip().upper().startswith("SELECT"): result = await cur.fetchall() else: result = cur.rowcount except Exception as e: raise return result升级aiomysql版本
升级到最新稳定版(如0.2.7),新版本修复了大量性能问题,尤其是针对批量操作的效率优化。执行升级命令:pip install --upgrade aiomysql优化批量插入SQL写法
MySQL支持单条INSERT语句包含多个VALUES组,这种方式比executemany效率更高。可以手动拼接SQL:def generate_batch_insert_sql(table, columns, data): # 生成多个VALUES占位符组 placeholders = ", ".join(["(" + ", ".join(["%s"]*len(columns)) + ")" for _ in data]) sql = f"INSERT INTO {table} ({', '.join(columns)}) VALUES {placeholders}" # 扁平化数据列表适配单条SQL参数 flat_data = [item for sublist in data for item in sublist] return sql, flat_data # 使用示例 columns = ["id", "name", "age", ...] sql, flat_data = generate_batch_insert_sql("example", columns, list_fen_final) await exec_sql_async("example_database", sql, flat_data)注意:需确保MySQL的
max_allowed_packet参数足够大,避免因SQL过长报错。调整连接池参数
根据实际并发量优化连接池配置:- 适当提高
maxsize(如设为50),但不超过MySQL配置的max_connections值 - 将
minsize设置为日常并发所需的最小连接数,减少连接频繁创建销毁的开销
- 适当提高
针对性降低异步开销
- 若存在多个数据库操作任务,采用异步并行执行(如同时插入多表、分批次并行插入),充分发挥异步优势
- 对于单一大批量插入场景,可在异步视图中通过
run_in_executor调用同步pymysql执行,规避异步调度开销
内容的提问来源于stack exchange,提问作者Rain

