头图

Python + Sqlalchemy 对数据库的批量插入或更新(Upsert)

本篇承接上一篇《Mysql 数据库的批量插入或更新(Upsert)》的内容,来看看在 Python 中,怎么实现大量数据的 upsert(记录存在就更新,不存在则插入)。

由于不同数据库对这种 upsert 的实现机制不同,Sqlalchemy 也就不再试图做一致性的封装了,而是提供了各自的方言 API,具体到 Mysql,就是给 insert statement ,增加了 on_duplicate_key_update 方法。

基本用法

假设表数据模型如下:

class TableA(db.Model):
    __tablename__ = 'table_a'
    __table_args__ = (db.UniqueConstraint('a', 'b', name='table_a_a_b_unique'))

    id = db.Column(db.Integer, primary_key=True)
    a = db.Column(db.Integer)
    b = db.Column(db.Integer)
    c = db.Column(db.Integer)

其中 id 是自增主键,a, b 组成了唯一索引。那么对应的 upsert 语句如下:
from sqlalchemy.dialects.mysql import insert

insert(TableA).values(a=1, b=2, c=3).on_duplicate_key_update(c=3)

复用数值

跟 SQL 语句类似,我们可以不用每次都重复填写 insert 和 update 的数值:

update_keys = ['c']
insert_stmt = insert(table_cls).values(a=1, b=2, c=3)
update_columns = {x.name: x for x in insert_stmt.inserted if x.name in update_keys}
upsert_stmt = insert_stmt.on_duplicate_key_update(**update_columns)
db.session.execute(upsert_stmt)

注意,最后一句 on_duplicate_key_update 的参数是需要展开的,不接受 dict 作为参数

批量处理

同样,insert 语句是支持传一组数据作为参数的:

records = [{
    'a':1,
    'b':2,
    'c':3
},{
    'a':10,
    'b':20,
    'c':4
},{
    'a':20,
    'b':30,
    'c':5
}]

update_keys = ['c']
insert_stmt = insert(table_cls).values(records)
update_columns = {x.name: x for x in insert_stmt.inserted if x.name in update_keys}
upsert_stmt = insert_stmt.on_duplicate_key_update(**update_columns)
db.session.execute(upsert_stmt)

就可以实现整体的 upsert。

封装

观察上面的代码,实际上 upsert 的部分是业务无关的,那么就可以封装一个更方便调用的通用函数了:

from sqlalchemy.dialects.mysql import insert

def upsert(table_cls, records, except_cols_on_update=[]):
    update_keys = [key for key in records[0].keys() if
                   key not in except_cols_on_update]
    insert_stmt = insert(table_cls).values(chunk)
    update_columns = {x.name: x for x in insert_stmt.inserted if x.name in update_keys}
    upsert_stmt = insert_stmt.on_duplicate_key_update(**update_columns)
    db.session.execute(upsert_stmt)

分批次生成

以上的封装,还可以做一些改进:为避免records 数据集过大,可以分批执行 sql 语句,并通过参数决定是否要提交:

from sqlalchemy.dialects.mysql import insert

def upsert(table_cls, records, chunk_size=10000, commit_on_chunk=True, except_cols_on_update=[]):
    update_keys = [key for key in records[0].keys() if
                   key not in except_cols_on_update]
    for i in range(0, len(records), chunk_size):
        chunk = records[i:i + chunk_size]
        insert_stmt = insert(table_cls).values(chunk)
        update_columns = {x.name: x for x in insert_stmt.inserted if x.name in update_keys}
        upsert_stmt = insert_stmt.on_duplicate_key_update(**update_columns)
        db.session.execute(upsert_stmt)
        if commit_on_chunk:
            db.session.commit()

调用方式如下 :

upsert(TableA, records,
                       chunk_size=50000,
                       commit_on_chunk=True,
                       except_cols_on_update=['id', 'a', 'b'])

这时 records 可以数量很大,比如1千万条,调用后每 5 万条生成一条 sql 语句,并且执行后就commit(如果参数 commit_on_chunk = False,那么函数内就一直不提交,可以结束后自行统一提交),update 语句中,避免更新 'id', 'a', 'b' 这三个字段。

我的语雀原文链接


hawk
关注创业公司的技术与团队
289 声望
20 粉丝
0 条评论
推荐阅读
为什么 AI 时代,人人都需要学一点编程
经常有人调侃说,ChatGPT 来了,你们这些码农和程序员都要失业了,这当然首先是在吸引眼球,有流量才有钱么。这些做自媒体的up主是不会认真想想,AI 到底对编程造成了怎样的冲击,对码农们的影响到底是是什么?真...

songofhawk阅读 571

封面图
花了几个月时间把 MySQL 重新巩固了一遍,梳理了一篇几万字 “超硬核” 的保姆式学习教程!(持续更新中~)
MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System:关系数据库管理系统)应用软件之一。

民工哥14阅读 2k

封面图
硬卷完了!MongoDB 打怪升级进阶成神之路( 2023 最新版 )!
前面我们学习:MySQL 打怪升级进阶成神之路、Redis 打怪升级进阶成神之路,然后我们还在继续 NoSQL 的卷王之路。从第一篇文章开始,我们逐步详细介绍了 MogoDB 基础概念、安装和最基本的CURD操作、索引和聚合、工...

民工哥7阅读 651

封面图
基于Sanic的微服务基础架构
使用python做web开发面临的一个最大的问题就是性能,在解决C10K问题上显的有点吃力。有些异步框架Tornado、Twisted、Gevent 等就是为了解决性能问题。这些框架在性能上有些提升,但是也出现了各种古怪的问题难以...

jysong6阅读 4k评论 3

又一款眼前一亮的Linux终端工具!
今天给大家介绍一款最近发现的功能十分强大,颜值非常高的一款终端工具。这个神器我是在其他公众号文章上看到的,但他们都没把它的强大之处介绍明白,所以我自己体验一波后,再向大家分享自己的体验。

良许6阅读 1.8k

初学后端,如何做好表结构设计?
这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。

王中阳Go4阅读 1.8k评论 2

封面图
又一款内存数据库横空出世,比 Redis 更强,性能直接飙升一倍!杀疯了
KeyDB是Redis的高性能分支,专注于多线程,内存效率和高吞吐量。除了多线程之外,KeyDB还具有仅在Redis Enterprise中可用的功能,例如Active Replication,FLASH存储支持以及一些根本不可用的功能,例如直接备份...

民工哥4阅读 1.7k评论 2

封面图
289 声望
20 粉丝
宣传栏