领先的免费Web技术教程,涵盖HTML到ASP.NET

网站首页 > 知识剖析 正文

Rust的数据库框架:SQLx连接MySQL实践

nixiaole 2025-07-08 22:01:56 知识剖析 5 ℃

sqlx 是一个用于 Rust 编程语言的数据库操作库,支持异步操作并且提供了类型安全的查询。

sqlx最大的特点就是可以灵活地组SQL语句,大部分数据库操作都只需要掌握sqlx::query这个函数,就能使用。

使用sqlx操作数据库有两种方式:sqlx::query函数调用和sqlx::query!宏。如:

基本使用

依赖配置:

# 数据库框架
sqlx = { version="0.7",features = ["mysql", "runtime-tokio-rustls", "chrono"] }

# 异步框架
tokio = { version = "1.45", features = ["full"] }

# 环境变量操作库
dotenv = "0.15"

sqlx是异步操作库,需要一个异步运行时,这里使用tokio。为了使用sqlx的query!宏,需要配置“DATABASE_URL”环境变量,这里使用 dotenv来读取环境变量。

我们在开发中,最常用的SQL操作就是增、删、查、改。除此之外,还可能在软件开始时对数据库进行初始化(建库、建表、建索引等)。

连接数据库:

    dotenv::dotenv().ok();
    let database_url = std::env::var("DATABASE_URL").expect("必须设置环境变量:DATABASE_URL");

    let pool: Pool<MySql> = MySqlPoolOptions::new()
        .max_connections(5)
        .connect(&database_url)
        .await?;

如果要使用query!宏,必须配置 'DATABASE_URL'环境变量,如:

set DATABASE_URL="mysql://root:root@localhost/db_chess"

还有一个需要注意的就是sqlx是的操作是异步的,所以需要在async函数中进行。

执行SQL文件:

执行SQL文件不常用。但为了提升软件的安装体验,常常在软件初始化时(安装后第一次启动)把数据库也初始化了。执行SQL文件非常简单:

sqlx::query_file!("db/database.sql").execute(&pool).await?;

针对比较小的文件,可以以上的方式,但如果SQL文件非常大(包含迁移数据),那以上的方法就行不通。

通常情况下,执行超大的SQL文件(1G以上)需要进行“流式处理”。简单地说,就是读取SQL文件,通过分号(;)判断SQL语言,一条一条地执行。

当然,更好的方式是使用 sqlx migrate 工具进行来执行超大的SQL文件。sqlx提升了命令行工具和代码集成两种方式,以下是代码集成方式示例:

    let migrator = sqlx::migrate::Migrator::new(Path::new("./migrations")).await.unwrap();
    migrator.run(&pool).await.unwrap(); // 执行SQL。

代码中的"./migrations"是保存有SQL文件的目录。

CURD操作:

使用Rust进行SQL操作时,可以使用query函数和query!宏,两种方式比较相似:

// query!宏方式
sqlx::query!("insert into tb_user(username, password) values(?, ?)","小明","123")
      .execute(&pool).await?;
    
// query函数方式
sqlx::query( "insert into tb_user(username, password) values(?, ?)")
        .bind("小明").bind("123").execute(&pool).await?;

两种方式的区别是:query!宏可以在编译时检查SQL语句,而query函数则不会检测。当然,如果使用了RustRover,正确配置数据源后,在编辑器中是可以看出错误的。

对于有返回信息query,还可以直接将数据映射成结构体对象(如:User对象)或返回单个数据(查询指定用户ID对应的用户名等):

// 插入数据时,获取新插入行的行的ID
// 使用宏查询,需要设置环境变量:DATABASE_URL
let res = sqlx::query!("insert into tb_user(username, password) values(?, ?)","小明","123")
      .execute(&pool).await?;
let id = res.last_insert_id(); // 获取插入数据列的ID

// 用户结构定义,如果需要从数据库反序列化,需要 加上sqlx::FromRow
#[derive(Debug, sqlx::FromRow)]
struct User {
    id: i64,
    username: String,
    password: String,
}
// 查询指定用户信息,返回查到的第一条数据
    let user = sqlx::query_as::<_, User>("select * from tb_user where id = ?")
            .bind(3) .fetch_optional(&pool).await;
    match user {
        Ok(user) => {println!("{:?}", user)}
        Err(err) => {println!("{}", err);}
    }
// 还有一个fetch_one函数,但如果没有查到数据,
// 将会返回错误: "no rows returned by a query that expected to return at least one row"

// 可以使用fetch_all函数查询多条数据,返回Vec数组;
// 如果没有查询到数据,返回空的Vec数组
    let users = sqlx::query_as::<_, User>("select * from tb_user")
            .fetch_all(&pool).await?;
    println!("用户:{:?}", users);

事务操作:

有的时候,我们需要保证一组数据操作“完整”地执行成功,比如:注册用户时要保证插入用户数据成功、设置用户基础角色成功。

事务的作用就是保证一组SQL(多条SQL)操作能够全部执行成功;要么有任何一条SQL执行失败时,撤销之前执行成功的SQL操作,保证并不会对数据库造成影响。

上述第64行故意写了一个错误的SQL,执行该SQL时会失败。在这之前,前面的插入用户数据其实已经成功。

如果没有事务,这就会造成插入数据成功而关联用户角色失败的情况。总体来说,这个操作是失败的,因为结果返回了Error,但数据库中却多了一条数据。这显然不是我们想要的。

但是,加上事务之后,如果在我们调用提交(commit)前发生错误,前面操作成功的语句都会回滚(撤销)。

结语:简单、直接、够用

用sqlx操作数据库就是这么回事:

  1. 写SQL不绕弯 - 直接写原生SQL,query/query!两个核心方法搞定90%需求
  2. 类型安全不操心 - query!宏在编译时帮你检查SQL错误
  3. 异步操作不卡顿 - 配合tokio运行时,高并发场景稳如老狗
  4. 事务保平安 - 关键操作加上事务,失败自动回滚不埋坑

实际项目中记住三点:

  • 小文件用query_file!
  • 大迁移用Migrator
  • 关键操作包事务

最后提醒:配置好DATABASE_URL环境变量,你的SQL才能在编译期被检查。别嫌麻烦,这步能省你一半调试时间。

最近发表
标签列表