使用sqlx向MySQL插入记录时在特定场景下速度骤降
解决sqlx事务内批量插入1000条数据过慢的问题
问题场景
使用依赖:
sqlx = { version = "0.6.2", features = ["runtime-tokio-native-tls", "mysql"] }
在release模式下,针对关闭AUTO_COMMIT的本地MySQL 8.0.31服务器执行以下代码:
let mut tx = pool.begin().await?; for i in 0..1_000 { let q = &format!("INSERT INTO tbl_abc(some_col) VALUES ({i})"); sqlx::query(q).execute(&mut tx).await?; } tx.commit().await?;
出现以下现象:
- 插入1000条记录耗时40秒以上(单条约40ms)
- 插入50-100条时速度正常(单条约0.12ms)
- 调整连接池大小无明显改善
问题根源
核心原因是单条语句循环执行的网络交互开销累积:即便在同一个事务内,每条INSERT语句都要经历「客户端发请求→服务器处理→返回响应」的完整流程。小批量时总开销不明显,1000条时往返延迟的总和被放大,导致总耗时剧增。
另外,用format!拼接SQL存在SQL注入风险,且sqlx无法预编译动态生成的语句,每次执行都需要服务器重新解析SQL,额外增加了处理耗时。
优化方案
1. 使用MySQL批量插入语法(快速见效)
利用MySQL的INSERT ... VALUES (...), (...), ...语法,将多条记录合并为单条SQL执行,大幅减少网络往返次数:
let mut tx = pool.begin().await?; // 构造批量插入SQL let values = (0..1_000) .map(|i| format!("({})", i)) .collect::<Vec<_>>() .join(","); let q = format!("INSERT INTO tbl_abc(some_col) VALUES {values}"); sqlx::query(&q).execute(&mut tx).await?; tx.commit().await?;
如果插入数据包含用户输入(需防范SQL注入),改用参数化批量插入:
let mut tx = pool.begin().await?; // 生成参数占位符 let placeholders = (0..1_000) .map(|_| "(?)") .collect::<Vec<_>>() .join(","); let q = format!("INSERT INTO tbl_abc(some_col) VALUES {placeholders}"); // 收集所有参数并执行 let params: Vec<i32> = (0..1_000).collect(); sqlx::query(&q) .bind_all(params) .execute(&mut tx) .await?; tx.commit().await?;
2. 使用sqlx QueryBuilder(安全便捷的参数化批量插入)
sqlx提供QueryBuilder工具,简化参数化批量插入的代码,无需手动拼接占位符:
use sqlx::mysql::MySqlQueryBuilder; let mut tx = pool.begin().await?; let mut builder = MySqlQueryBuilder::new("INSERT INTO tbl_abc(some_col) VALUES "); let mut separated = builder.separated(","); for i in 0..1_000 { separated.push_bind(i); } builder.build().execute(&mut tx).await?; tx.commit().await?;
3. 调整MySQL配置(辅助优化)
若批量插入后仍有性能瓶颈,可检查并调整以下MySQL配置:
- 增大
innodb_buffer_pool_size,让更多数据在内存中处理 - 调整
innodb_log_file_size,减少日志刷盘频率 - 根据业务需求设置
innodb_flush_log_at_trx_commit:1(事务安全)或2(性能优先,适合非核心数据)
内容的提问来源于stack exchange,提问作者at54321
相关产品推荐
相关产品推荐

