如何用Rust快速向MySQL插入百万条数据?
Rust 批量插入MySQL数据的性能优化方案
问题背景
我正在学习Rust,发现Rust处理数据速度很快,但向MySQL插入大规模数据时,性能远不如Python——Python1分钟能完成的插入,Rust用mysql库居然要15分钟以上。
我最初的实现代码如下:
use mysql::*; #[derive(Debug, PartialEq, Eq)] struct ListPhone { phone_no: String, } fn insert_mysql(data: Vec<ListPhone>) -> () { let url = "mysql://user:pass@xxx.xxx.xxx.xxx:3306/learn"; let pool = mysql::Pool::new(url); let mut conn = pool.expect("error pool conn").get_conn(); let res = conn.expect("error running").exec_batch( r"INSERT INTO listPhone (phone_no) VALUES (:phone_no)", data.into_iter().map(|p| { params! { "phone_no" => p.phone_no, } }), ); println!("done") }
问题排查
通过日志对比发现,Python会生成单条批量插入语句:
insert into listPhone (phone_no) values (1),(2),(3)... 所有数据
而Rust的exec_batch实际是生成多条单值插入语句逐行执行:
"insert into listPhone (phone_no) values (1)",
"insert into listPhone (phone_no) values (2)",
"insert into listPhone (phone_no) values (3)",
... 其余数据
这就是性能差距的核心原因。我尝试手动拼接SQL语句的方式实现了批量插入,效率比Python还快,但写法不够优雅:
fn insert_mysql_batch_experiment(data: Vec<ListPhone>) -> () { let mut vec = Vec::new(); let url = env::var("DATABASE_URL").expect("load env failed"); let pool = mysql::Pool::new(&*url).expect("error pool conn"); let mut conn = pool.get_conn().expect("error conn"); for x in data.iter() { vec.push(x.phone_no.clone()); } let hasil = vec.join("'),('"); let final_str = [ "INSERT INTO listPhone (phone_no) VALUES ", "('", &hasil, "')", ] .join(""); { let query_result = conn.query_iter(final_str); query_result.expect("error inserting data"); } }
优化方案
1. 利用mysql库的批量参数绑定(推荐)
mysql库其实支持单条语句的批量参数绑定,不需要手动拼接SQL。可以构造包含多个参数组的语句,让MySQL一次性执行:
use mysql::*; #[derive(Debug, PartialEq, Eq)] struct ListPhone { phone_no: String, } fn insert_mysql_optimized(data: Vec<ListPhone>) -> Result<()> { let url = "mysql://user:pass@xxx.xxx.xxx.xxx:3306/learn"; let pool = Pool::new(url)?; let mut conn = pool.get_conn()?; // 构造批量插入的SQL模板 let placeholders = data.iter() .map(|_| "(?)") .collect::<Vec<_>>() .join(","); let sql = format!("INSERT INTO listPhone (phone_no) VALUES {}", placeholders); // 提取所有phone_no作为参数 let params: Vec<_> = data.into_iter() .map(|p| p.phone_no) .collect(); // 执行批量插入 conn.exec(&sql, params)?; println!("done"); Ok(()) }
这种方式既避免了手动拼接SQL的安全风险(比如SQL注入),又能生成单条批量插入语句,性能和手动拼接相当。
2. 使用更高效的数据库驱动库
如果mysql库的批量处理依然不能满足需求,可以尝试以下专门优化过性能的Rust数据库库:
sqlx:支持异步操作,批量插入API更直观,且有更好的性能优化,支持编译时SQL检查diesel:成熟的ORM框架,提供批量插入的便捷方法,适合复杂业务场景
以sqlx为例,批量插入的实现如下:
use sqlx::{MySql, Pool}; use sqlx::mysql::MySqlPoolOptions; #[derive(Debug, PartialEq, Eq)] struct ListPhone { phone_no: String, } async fn insert_mysql_sqlx(data: Vec<ListPhone>) -> Result<()> { let pool = MySqlPoolOptions::new() .max_connections(5) .connect("mysql://user:pass@xxx.xxx.xxx.xxx:3306/learn") .await?; // 批量插入 sqlx::query!( "INSERT INTO listPhone (phone_no) VALUES (?)", data.iter().map(|p| &p.phone_no) ) .execute(&pool) .await?; println!("done"); Ok(()) }
3. 额外性能优化建议
- 关闭自动提交:在插入前执行
conn.query("SET autocommit = 0"),插入完成后手动commit,减少事务开销 - 调整MySQL配置:比如增大
max_allowed_packet(避免批量语句过大报错)、优化innodb_buffer_pool_size等 - 分批次插入:如果数据量极大(千万级以上),可以将数据分成多个批次(比如每批次10000条),避免单条语句过大导致的性能问题
内容的提问来源于stack exchange,提问作者giri
相关产品推荐
相关产品推荐

