You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQLAlchemy+MariaDB复合主键批量Upsert实现问题求助

SQLAlchemy 搭配 MariaDB 复合主键批量 Upsert 问题解决

问题根源

你之前用and_组合单个主键字段的in查询,逻辑上是让每个主键字段各自匹配对应的值列表,而不是匹配主键字段的组合,导致数据库无法找到对应记录,自然不会执行更新逻辑。

两种可行解决方案

方案1:先查询过滤再分批次插入/更新

核心是用tuple_把复合主键字段打包,配合in_查询主键组合的集合,实现精准匹配:

1. 定义复合主键模型

from sqlalchemy import Column, Integer, String, tuple_, case, insert
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine

Base = declarative_base()

class CompositeModel(Base):
    __tablename__ = 'composite_table'
    # 复合主键:id + type_id
    id = Column(Integer, primary_key=True)
    type_id = Column(Integer, primary_key=True)
    name = Column(String(50))
    value = Column(Integer)

# 初始化数据库连接
engine = create_engine('mysql+mysqlconnector://用户名:密码@localhost/数据库名')
Session = sessionmaker(bind=engine)
session = Session()

2. 批量Upsert实现函数

def bulk_upsert_composite(session, model, data_list):
    # 提取模型的复合主键字段
    pk_cols = [col for col in model.__table__.columns if col.primary_key]
    # 提取待处理数据的主键组合(元组形式)
    pk_tuples = [tuple(d[col.name] for col in pk_cols) for d in data_list]
    
    # 精准查询已存在的主键组合
    existing_pks = set(session.query(*pk_cols).filter(
        tuple_(*pk_cols).in_(pk_tuples)
    ).all())
    
    # 拆分新增/更新数据集
    to_insert = []
    to_update = []
    for d in data_list:
        current_pk = tuple(d[col.name] for col in pk_cols)
        if current_pk not in existing_pks:
            to_insert.append(d)
        else:
            to_update.append(d)
    
    # 批量插入新数据
    if to_insert:
        session.bulk_insert_mappings(model, to_insert)
        print(f"插入 {len(to_insert)} 条新记录")
    
    # 批量更新现有数据
    if to_update:
        update_stmt = model.__table__.update()
        # 为每个非主键字段构造匹配更新逻辑
        non_pk_cols = [col for col in model.__table__.columns if not col.primary_key]
        for col in non_pk_cols:
            when_clauses = []
            for d in to_update:
                when_clauses.append(
                    (tuple_(*pk_cols) == tuple(d[pk_col.name] for pk_col in pk_cols), d[col.name])
                )
            update_stmt = update_stmt.values({col.name: case(*when_clauses, else_=col)})
        session.execute(update_stmt)
        print(f"更新 {len(to_update)} 条记录")
    
    session.commit()

3. 使用示例

test_data = [
    {"id": 1, "type_id": 1, "name": "A", "value": 10},
    {"id": 1, "type_id": 2, "name": "B", "value": 20},
    {"id": 2, "type_id": 1, "name": "C", "value": 30},
    # 这条已存在,会被更新
    {"id": 1, "type_id": 1, "name": "A_Updated", "value": 15}
]

bulk_upsert_composite(session, CompositeModel, test_data)

方案2:直接用MariaDB的ON DUPLICATE KEY UPDATE(更高效)

MariaDB原生支持基于主键(包括复合主键)的重复判断,SQLAlchemy可以直接调用该语法,无需提前查询过滤,性能更优:

def bulk_upsert_on_duplicate(session, model, data_list):
    # 构造插入语句
    insert_stmt = insert(model.__table__).values(data_list)
    # 定义重复时的更新字段(所有非主键字段)
    update_fields = {
        col.name: insert_stmt.inserted[col.name]
        for col in model.__table__.columns if not col.primary_key
    }
    # 拼接ON DUPLICATE KEY UPDATE逻辑
    upsert_stmt = insert_stmt.on_duplicate_key_update(**update_fields)
    
    session.execute(upsert_stmt)
    session.commit()
    # 打印影响行数(插入+更新的总条数)
    print(f"批量Upsert完成,共影响 {session.execute('SELECT ROW_COUNT()').scalar()} 条记录")

关键提示

  • 方案1适合需要在代码层对数据做额外处理(比如过滤、修改)的场景;
  • 方案2直接委托数据库处理重复判断,代码更简洁,大数据量下性能更好,优先推荐;
  • 确保MariaDB表的复合主键定义正确,否则ON DUPLICATE KEY UPDATE无法触发更新逻辑。

内容的提问来源于stack exchange,提问作者Shan-x

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 04:50:41