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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 03:30:46