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

存储过程调用慢于INSERT、executemany无性能提升的原因探究

问题描述

数据表与存储过程定义

CREATE TABLE `inspect_call` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `task_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  `cc_number` varchar(63) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `created_at` bigint(20) unsigned NOT NULL DEFAULT '0',
  `updated_at` bigint(20) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `task_id` (`task_id`)
) ENGINE=InnoDB AUTO_INCREMENT=234031 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 

CREATE PROCEDURE inspect_proc(IN task bigint,IN number varchar(63))
INSERT INTO inspect_call(task_id,cc_number) values (task, number)

初始测试困惑

原本预期调用存储过程比直接执行INSERT快很多,但实际测试结果相反:插入10000条数据时,单条INSERT循环耗时约4分钟,调用存储过程循环耗时约15分钟,多次测试结果一致。MySQL服务器配置普通,但无法理解存储过程更慢的原因。测试代码如下(使用mysql-connector-python 8.0.31):

command = ("INSERT INTO inspect_call (task_id,cc_number)"
           "VALUES (%s, %s)")
for i in range(rows): 
    cursor.execute(command, (task_id,f"{cc}{i}"))
    # cursor.callproc("inspect_proc", (task_id,f"{cc}{i}"))
cnx.commit()

注:已知可通过设置innodb_flush_log_at_trx_commit = 2提升插入速度,但暂不打算调整该参数。


更新1:executemany批量插入无性能提升

根据建议尝试executemany批量插入,但性能和单条循环INSERT基本一致:

cursor = cnx.cursor(buffered=True)
for i in range(int(rows/1000)):
    data = []
    for j in range(1000):
        data.append((task_id,f"{cc}{i*1000+j}"))
    cursor.executemany(command,data)
cnx.commit()

# 与以下代码性能无差异
cursor = cnx.cursor()
for i in range(rows):
    cursor.execute(command, (task_id,f"{cc}{i}"))

多次测试(尝试过每次批量插入100条),结果两者性能基本一致,这是为什么?


更新2:网络问题解决后executemany仍无提升

最终发现之前插入慢的主因是从笔记本通过外部主机名访问数据库,将脚本上传至服务器内网访问后,速度大幅提升:插入10000条耗时约3-4秒,插入100000条耗时约36秒。但executemany仍未带来性能提升,这是为什么?


解答

1. 存储过程比直接INSERT慢的原因

你的存储过程只是简单封装了单条INSERT语句,调用时依然是单次网络请求执行单条插入,反而额外增加了存储过程的调用开销(比如参数传递、存储过程的解析执行逻辑),所以比直接执行单条INSERT更慢。存储过程的性能优势通常体现在复杂逻辑的批量处理(减少多次网络交互+数据库端批量运算),而非这种简单的单条插入封装。

2. executemany未带来性能提升的原因

mysql-connector-python的executemany默认实现并非真正的批量插入(即生成INSERT INTO ... VALUES (...), (...)这类单条聚合SQL),而是在客户端循环执行单条INSERT,和你手动写for循环调用cursor.execute的逻辑几乎一致,因此性能没有差异。

要实现真正的批量插入,有两种可行方案:

  • 手动构造批量INSERT语句:
    batch_size = 1000
    # 构造对应批量大小的VALUES占位符
    command = f"INSERT INTO inspect_call (task_id,cc_number) VALUES {', '.join(['(%s, %s)']*batch_size)}"
    data = []
    for i in range(rows):
        data.extend([task_id, f"{cc}{i}"])
        # 达到批量大小就执行插入
        if len(data) >= batch_size * 2:
            cursor.execute(command, data)
            data = []
    # 处理剩余不足批量的部分
    if data:
        remaining_count = len(data) // 2
        command = f"INSERT INTO inspect_call (task_id,cc_number) VALUES {', '.join(['(%s, %s)']*remaining_count)}"
        cursor.execute(command, data)
    cnx.commit()
    
  • 启用连接器的批量插入优化:
    创建连接时指定use_pure=False(使用C扩展实现的客户端),此时executemany会自动生成批量INSERT语句:
    cnx = mysql.connector.connect(
        host='你的数据库地址',
        user='用户名',
        password='密码',
        database='数据库名',
        use_pure=False  # 启用C扩展客户端,开启executemany批量优化
    )
    # 后续使用executemany即可获得性能提升
    

3. 网络对插入性能的影响

跨公网访问时,单条INSERT的网络往返延迟(RTT)被多次累加,10000次请求的总延迟直接导致耗时极高;内网访问时网络延迟极低,此时客户端的执行逻辑是否真正实现批量,就成为了性能瓶颈——默认的executemany依然是多次单条请求,只是在客户端内部循环,内网低延迟下看起来和单条循环性能接近,但换成真正的批量插入,性能还能进一步提升。

内容的提问来源于stack exchange,提问作者Qiulang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 20:05:32