批量插入应避免单条语句多次网络往返,须用多值INSERT、表值参数或原生数组传参,在数据库端一次性展开写入,而非存储过程中循环执行INSERT。

存储过程里怎么批量插入而不触发 N 次网络往返
单条 INSERT 语句执行一次,客户端就发一次请求——哪怕你循环 100 次,就是 100 次 round-trip。数据库端可能只花 2ms,但网络延迟(尤其跨机房)动辄 10–50ms,瓶颈根本不在 SQL 本身。
真正有效的做法是把多行数据打包进一条语句,或者用存储过程内部循环处理。关键不是“写个存储过程”,而是让数据别出数据库边界。
- 用
INSERT INTO ... VALUES (...), (...), (...)一次性插多行(注意 MySQL 有max_allowed_packet限制,PostgreSQL 无硬上限但要注意事务大小) - 在存储过程中接收数组或 JSON 字符串(如 PostgreSQL 的
jsonb、SQL Server 的STRING_SPLIT或表值参数),再用UNNEST/CROSS APPLY展开后批量写入 - 避免在存储过程里写
FOR i IN 1..n LOOP INSERT ...这种循环——它只是把 100 次网络往返,换成 100 次内部执行,没减少 I/O,还加重了锁竞争
SQL Server 表值参数(TVP)传大批量数据时卡住怎么办
表值参数本意是高效传结构化数据,但实际用起来常卡在客户端序列化或服务端内存分配上,尤其当传几万行、每行字段又多的时候。
问题往往不出在语法,而在两处:客户端构造 TVP 的方式,和服务端是否启用了 SET ARITHABORT ON(某些 ORM 或连接池默认关掉它,导致执行计划缓存失效,反复编译)。
- 客户端侧:用
DataTable构造 TVP 时,别一行行Rows.Add(),改用Load()配合SqlDataReader或数组批量填充,快 3–5 倍 - 服务端侧:确保连接字符串包含
Packet Size=8192(默认够用),且存储过程中第一行加SET ARITHABORT ON - 别把 TVP 当通用容器:字段类型必须和用户定义表类型(UDT)严格一致,比如 UDT 定义的是
INT,传NULL或smallint都可能隐式转换失败,报错信息是The table-valued parameter "@data" must be declared with a user-defined table type.
MySQL 存储过程里用游标遍历大结果集为什么越来越慢
游标本质是逐行提取 + 逐行处理,底层会维持临时结果集快照,数据量一过万,内存占用飙升,还会阻塞源表的 DML 操作。更麻烦的是,MySQL 游标不支持动态 SQL 绑定,没法提前预编译,每次 fetch 都带解析开销。
除非业务逻辑真需要“逐行判断后决定下一行怎么查”,否则游标是最后的选择。
- 优先用
JOIN+UPDATE ... SELECT替代“查出来再更新”:比如要给一批用户加积分,直接UPDATE users u JOIN temp_batch b ON u.id = b.user_id SET u.score = u.score + b.points - 真要过程式逻辑,把数据导出到临时表(
CREATE TEMPORARY TABLE ... SELECT ...),再对临时表做多次聚合/更新,比游标快一个数量级 - 如果非用游标不可,务必在声明前加
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE,不然遇到空结果会直接报错中断,而不是安静退出
PostgreSQL 中用 jsonb 传参进存储过程,解析后性能反而下降
jsonb 灵活,但解析成本真实存在。一个含 5000 条记录的 JSON 数组,用 jsonb_array_elements() 展开,比原生数组慢 2–4 倍;若再嵌套对象、还要取多层字段,CPU 就明显吃紧。
这不是 JSON 本身的问题,而是 PostgreSQL 对 JSON 路径解析未做向量化优化,每次调用都是独立解析。
- 能用原生数组就别用 JSON:比如传整数 ID 列表,用
INT[]类型参数,配合UNNEST($1),比jsonb_array_elements($1::jsonb)快得多 - 如果前端只能发 JSON,至少在存储过程开头用
jsonb_populate_recordset(NULL::my_table_type, $1)一次性转成记录集,别在循环里反复调用->>取字段 - 注意
jsonb字段名大小写敏感,而 PostgreSQL 列名默认小写——data->>'UserId'和data->>'userid'是不同路径,容易漏数据
批量操作的核心从来不是“怎么写存储过程”,而是“数据在哪次被组织、在哪次被拆解”。网络损耗藏在看不见的序列化、参数绑定、执行计划选择里,而不是那行 CALL proc(...) 上。多看执行计划里的 Planning Time 和 Execution Time 分布,比猛写循环实在得多。











