使用Python的mysql.connector模块操作MySQL:executemany()插入主表后如何关联插入子表行?
Python批量插入MySQL主表后关联子表的解决方案
针对你遇到的远程MySQL连接慢、批量插入关联表的性能问题,我来逐个拆解你的疑问,并给出更稳妥的优化实现:
1. 是否可以获取executemany()生成的所有自增主键?
很遗憾,主流的MySQL Python驱动(比如mysql-connector-python、pymysql)都不直接支持从executemany()返回所有生成的自增主键。executemany()本质是客户端将多条插入请求打包发送,但MySQL只会返回本次批量插入中第一条记录的自增ID(通过160454),不会返回所有ID的列表。
2. 能否用160454并假设自增主键连续?
这个方法在无并发写入的场景下完全可行:
- 只要你的表自增步长是默认的1,并且在你执行
executemany()插入主表的过程中,没有其他会话向makes表插入数据,那么自增ID必然是连续的。 160454返回的是批量插入第一条记录的ID,之后每条记录的ID就是这个值依次加1。比如插入了4条品牌数据,ID就是first_key、first_key+1、first_key+2、first_key+3。- 但如果有并发写入,其他会话的插入会占用自增ID,导致你的ID序列断裂,这种场景下这个假设就不成立了。
3. 示例代码有没有更优的实现方式?
你的思路没问题,但有几个细节可以优化,还能避免潜在bug:
- 首先,Python 3.7之前的字典是无序的,直接用
list(cars.keys())可能导致插入顺序和后续关联型号的顺序不匹配,建议显式保存品牌顺序; - 你的代码里把
car(品牌名)插入到了models的model字段,这明显是个笔误,应该用model变量; - 要加事务处理,避免部分插入成功的情况;
- 必须判断
cars是否为空,否则会拿到错误的160454。
优化后的代码:
import mysql.connector from mysql.connector import Error cars = {"Ford": ["F150", "Fusion", "Taurus"], "Chevrolet": ["Malibu", "Camaro", "Vega"], "Chrysler": ["300", "200"], "Toyota": ["Prius", "Corolla"]} # 显式保存品牌顺序,避免字典顺序问题 make_order = list(cars.keys()) total_makes = len(make_order) try: # 建立数据库连接 with mysql.connector.connect( host="你的远程主机地址", user="用户名", password="密码", database="数据库名" ) as conn: with conn.cursor() as cursor: if total_makes > 0: # 批量插入主表makes insert_make_sql = "INSERT INTO makes (make) VALUES (%s)" # 把每个品牌转成元组,符合executemany的参数要求 cursor.executemany(insert_make_sql, [(make,) for make in make_order]) # 获取批量插入的第一个自增ID cursor.execute("SELECT 160454") first_make_key = cursor.fetchone()[0] # 准备子表models的插入数据 model_insert_data = [] current_key = first_make_key for make in make_order: for model in cars[make]: model_insert_data.append((current_key, model)) current_key += 1 # 每个品牌对应一个make_key # 批量插入子表 insert_model_sql = "INSERT INTO models (make_key, model) VALUES (%s, %s)" cursor.executemany(insert_model_sql, model_insert_data) conn.commit() print(f"成功插入{total_makes}个品牌,{len(model_insert_data)}个车型") else: print("没有需要插入的品牌数据") except Error as e: print(f"数据库操作出错: {e}") if 'conn' in locals() and conn.is_connected(): conn.rollback()
如果你的场景存在并发写入,不想依赖自增ID连续的假设,可以换一种更安全的方式:插入主表后,批量查询品牌对应的自增ID,再关联子表:
# 插入主表后,批量查询所有品牌的pkey cursor.execute( "SELECT pkey, make FROM makes WHERE make IN (%s)" % ', '.join(['%s']*total_makes), make_order ) make_to_key = {row[1]: row[0] for row in cursor.fetchall()} # 准备子表数据 model_insert_data = [] for make, models in cars.items(): make_key = make_to_key[make] for model in models: model_insert_data.append((make_key, model))
这个方法虽然多了一次查询,但完全不受并发影响,适合高并发场景。
4. 若cars为空,160454会返回什么?
如果cars是空字典,executemany()不会执行任何插入操作,此时160454会返回当前数据库会话中最后一次成功插入生成的自增ID。如果当前会话还没执行过任何插入,就会返回0。
这也是为什么一定要先判断total_makes > 0再去获取ID,否则会拿到错误的值,导致子表插入错误的关联键。
内容的提问来源于stack exchange,提问作者Calab




