Go 语言操作 SQLite 数据库指南 – wiki大全

“`markdown

Go 语言操作 SQLite 数据库指南

Go 语言以其简洁高效和强大的并发能力,在现代软件开发中越来越受欢迎。SQLite 作为一个轻量级、无服务器的文件型数据库,非常适合嵌入式应用、桌面应用或作为小型项目的本地存储。本文将详细介绍如何在 Go 语言中操作 SQLite 数据库,包括连接、CRUD (创建、读取、更新、删除) 操作以及一些最佳实践。

1. 为什么选择 Go 和 SQLite?

  • Go 语言优势: 编译型语言,性能高,并发模型(Goroutines 和 Channels)强大,部署简单(单个二进制文件)。
  • SQLite 优势:
    • 无服务器: 不需要独立的服务器进程,数据库就是一个文件。
    • 轻量级: 库文件非常小,内存占用低。
    • 易于部署: 复制数据库文件即可,无需复杂的安装配置。
    • 可靠性: 支持事务,ACID 特性完整。

2. 准备工作

在开始之前,确保您已经安装了 Go 语言环境。

2.1 安装 SQLite 驱动

Go 语言通过标准库 database/sql 包提供数据库操作的通用接口,但需要为特定的数据库安装驱动。对于 SQLite,最常用且推荐的驱动是 mattn/go-sqlite3

打开终端或命令行,执行以下命令安装驱动:

bash
go get github.com/mattn/go-sqlite3

3. 连接到 SQLite 数据库

连接到 SQLite 数据库非常简单。如果指定的文件不存在,它会自动创建。

“`go
package main

import (
“database/sql”
“fmt”
“log”

_ "github.com/mattn/go-sqlite3" // 导入 SQLite 驱动

)

func main() {
// 连接到 SQLite 数据库
// 如果数据库文件不存在,会自动创建
db, err := sql.Open(“sqlite3”, “./test.db”)
if err != nil {
log.Fatal(err)
}
defer db.Close() // 确保在程序结束时关闭数据库连接

fmt.Println("成功连接到 SQLite 数据库!")

}
“`

解释:
* _ "github.com/mattn/go-sqlite3": 使用下划线 _ 导入包,表示我们只使用其 init() 函数来注册驱动,而不直接使用包中的任何导出标识符。
* sql.Open("sqlite3", "./test.db"): 第一个参数是驱动名称(在 init() 中注册),第二个参数是数据库文件的路径。

4. 创建表 (CREATE TABLE)

连接成功后,下一步通常是创建表来存储数据。

“`go
package main

import (
“database/sql”
“fmt”
“log”

_ "github.com/mattn/go-sqlite3"

)

func main() {
db, err := sql.Open(“sqlite3”, “./test.db”)
if err != nil {
log.Fatal(err)
}
defer db.Close()

// 创建一个名为 'users' 的表
sqlStmt := `
CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE
);
`
_, err = db.Exec(sqlStmt)
if err != nil {
    log.Printf("%q: %s\n", err, sqlStmt)
    return
}
fmt.Println("表 'users' 创建或已存在。")

}
“`

解释:
* db.Exec(sqlStmt): 用于执行不返回结果集的 SQL 语句,例如 CREATE TABLE, INSERT, UPDATE, DELETE
* CREATE TABLE IF NOT EXISTS: 如果表不存在则创建,避免重复创建表时报错。
* INTEGER PRIMARY KEY AUTOINCREMENT: 定义一个自增的主键。
* TEXT NOT NULL: 定义一个非空的文本字段。
* TEXT UNIQUE: 定义一个唯一的文本字段。

5. 插入数据 (INSERT)

向表中插入数据。

“`go
package main

import (
“database/sql”
“fmt”
“log”

_ "github.com/mattn/go-sqlite3"

)

func main() {
db, err := sql.Open(“sqlite3”, “./test.db”)
if err != nil {
log.Fatal(err)
}
defer db.Close()

// 插入多条数据
stmt, err := db.Prepare("INSERT INTO users(name, email) VALUES(?, ?)")
if err != nil {
    log.Fatal(err)
}
defer stmt.Close() // 确保在函数结束时关闭预处理语句

names := []string{"Alice", "Bob", "Charlie"}
emails := []string{"[email protected]", "[email protected]", "[email protected]"}

for i := 0; i < len(names); i++ {
    res, err := stmt.Exec(names[i], emails[i])
    if err != nil {
        log.Printf("插入数据失败: %v\n", err)
        continue
    }
    id, _ := res.LastInsertId()
    fmt.Printf("插入用户 %s 成功,ID: %d\n", names[i], id)
}

// 尝试插入重复的email,会报错
_, err = stmt.Exec("David", "[email protected]")
if err != nil {
    fmt.Printf("尝试插入重复 email 失败 (预期错误): %v\n", err)
}

}
“`

解释:
* db.Prepare(...): 创建一个预处理语句 (prepared statement)。这对于插入多条数据或防止 SQL 注入攻击非常重要。
* ?: 占位符,由 stmt.Exec() 的参数按顺序填充。
* stmt.Exec(args...): 执行预处理语句。
* res.LastInsertId(): 返回最后插入行的 ID。
* res.RowsAffected(): 返回受操作影响的行数。

6. 查询数据 (SELECT)

从表中查询数据。

“`go
package main

import (
“database/sql”
“fmt”
“log”

_ "github.com/mattn/go-sqlite3"

)

type User struct {
ID int
Name string
Email string
}

func main() {
db, err := sql.Open(“sqlite3”, “./test.db”)
if err != nil {
log.Fatal(err)
}
defer db.Close()

// 查询所有用户
rows, err := db.Query("SELECT id, name, email FROM users")
if err != nil {
    log.Fatal(err)
}
defer rows.Close() // 确保关闭结果集

fmt.Println("所有用户:")
for rows.Next() {
    var u User
    err = rows.Scan(&u.ID, &u.Name, &u.Email)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID: %d, Name: %s, Email: %s\n", u.ID, u.Name, u.Email)
}
if err = rows.Err(); err != nil {
    log.Fatal(err)
}

// 查询单个用户 (根据 ID)
var user User
err = db.QueryRow("SELECT id, name, email FROM users WHERE id = ?", 1).Scan(&user.ID, &user.Name, &user.Email)
switch {
case err == sql.ErrNoRows:
    fmt.Println("ID 为 1 的用户不存在。")
case err != nil:
    log.Fatal(err)
default:
    fmt.Printf("\n查询到 ID 为 1 的用户: ID: %d, Name: %s, Email: %s\n", user.ID, user.Name, user.Email)
}

}
“`

解释:
* db.Query(...): 执行返回多行结果的查询。返回一个 *sql.Rows 对象。
* rows.Next(): 迭代结果集中的每一行。
* rows.Scan(...): 将当前行的列数据扫描到指定的变量中。注意:变量的顺序必须与 SELECT 语句中的列顺序一致。
* rows.Close(): 在处理完所有结果或提前结束循环时,务必关闭 rows 对象,以释放底层数据库连接。
* rows.Err(): 在循环结束后检查是否有迭代错误。
* db.QueryRow(...): 用于查询只返回单行结果的场景。
* QueryRow().Scan(...): 直接将单行结果扫描到变量中。
* sql.ErrNoRows: 如果 QueryRow 没有找到匹配的行,会返回这个错误。

7. 更新数据 (UPDATE)

更新表中已有的数据。

“`go
package main

import (
“database/sql”
“fmt”
“log”

_ "github.com/mattn/go-sqlite3"

)

func main() {
db, err := sql.Open(“sqlite3”, “./test.db”)
if err != nil {
log.Fatal(err)
}
defer db.Close()

// 更新用户 ID 为 1 的邮箱
result, err := db.Exec("UPDATE users SET email = ? WHERE id = ?", "[email protected]", 1)
if err != nil {
    log.Fatal(err)
}

rowsAffected, _ := result.RowsAffected()
fmt.Printf("更新了 %d 行数据。\n", rowsAffected)

// 再次查询 ID 为 1 的用户,验证更新
var user User
err = db.QueryRow("SELECT id, name, email FROM users WHERE id = ?", 1).Scan(&user.ID, &user.Name, &user.Email)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("更新后用户 ID: %d, Name: %s, Email: %s\n", user.ID, user.Name, user.Email)

}

// User 结构体定义 (同上)
type User struct {
ID int
Name string
Email string
}
“`

解释:
* db.Exec("UPDATE ..."): 用于执行 UPDATE 语句。
* result.RowsAffected(): 获取受 UPDATE 操作影响的行数。

8. 删除数据 (DELETE)

从表中删除数据。

“`go
package main

import (
“database/sql”
“fmt”
“log”

_ "github.com/mattn/go-sqlite3"

)

func main() {
db, err := sql.Open(“sqlite3”, “./test.db”)
if err != nil {
log.Fatal(err)
}
defer db.Close()

// 删除用户 ID 为 2 的数据
result, err := db.Exec("DELETE FROM users WHERE id = ?", 2)
if err != nil {
    log.Fatal(err)
}

rowsAffected, _ := result.RowsAffected()
fmt.Printf("删除了 %d 行数据。\n", rowsAffected)

// 再次查询所有用户,验证删除
rows, err := db.Query("SELECT id, name, email FROM users")
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

fmt.Println("\n删除后所有用户:")
for rows.Next() {
    var u User
    err = rows.Scan(&u.ID, &u.Name, &u.Email)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID: %d, Name: %s, Email: %s\n", u.ID, u.Name, u.Email)
}
if err = rows.Err(); err != nil {
    log.Fatal(err)
}

}

// User 结构体定义 (同上)
type User struct {
ID int
Name string
Email string
}
“`

解释:
* db.Exec("DELETE ..."): 用于执行 DELETE 语句。
* result.RowsAffected(): 获取受 DELETE 操作影响的行数。

9. 事务处理

事务允许您将一组 SQL 操作视为一个单一的、不可分割的工作单元。要么所有操作都成功提交,要么所有操作都回滚。

“`go
package main

import (
“database/sql”
“fmt”
“log”

_ "github.com/mattn/go-sqlite3"

)

func main() {
db, err := sql.Open(“sqlite3”, “./test.db”)
if err != nil {
log.Fatal(err)
}
defer db.Close()

tx, err := db.Begin() // 开始一个事务
if err != nil {
    log.Fatal(err)
}

// 准备插入语句
stmt, err := tx.Prepare("INSERT INTO users(name, email) VALUES(?, ?)")
if err != nil {
    log.Fatal(err)
}
defer stmt.Close() // 事务提交或回滚后,语句会自动关闭

// 插入第一条数据
_, err = stmt.Exec("Frank", "[email protected]")
if err != nil {
    tx.Rollback() // 发生错误时回滚事务
    log.Fatal("插入 Frank 失败,事务回滚:", err)
}
fmt.Println("插入 Frank 成功。")

// 故意制造一个错误,例如插入重复的 email
_, err = stmt.Exec("Grace", "[email protected]") // email 已存在,会报错
if err != nil {
    tx.Rollback() // 发生错误时回滚事务
    log.Fatal("插入 Grace 失败,事务回滚:", err)
}
fmt.Println("插入 Grace 成功。") // 这行不会执行到

err = tx.Commit() // 提交事务
if err != nil {
    log.Fatal("事务提交失败:", err)
}
fmt.Println("事务成功提交!")

}

// User 结构体定义 (同上)
type User struct {
ID int
Name string
Email string
}
“`

解释:
* db.Begin(): 开始一个事务,返回一个 *sql.Tx 对象。
* tx.Prepare(...): 在事务中使用预处理语句,确保语句与事务绑定。
* tx.Exec(...): 在事务中执行 SQL 语句。
* tx.Rollback(): 当发生错误时,回滚事务,撤销所有在此事务中进行的操作。
* tx.Commit(): 如果所有操作都成功,提交事务,使所有更改持久化。

10. 最佳实践

  • 使用预处理语句 (Prepared Statements): 对于所有 INSERT, UPDATE, DELETESELECT 操作,都应该使用 db.Prepare()tx.Prepare() 创建预处理语句。
    • 优点 1: 安全性: 防止 SQL 注入攻击。
    • 优点 2: 性能: 数据库只需解析一次 SQL 语句。
  • 及时关闭资源:
    • db.Close(): 在应用程序关闭时关闭数据库连接。通常使用 defer db.Close()
    • rows.Close(): 在处理完 SELECT 结果集后,务必关闭 *sql.Rows 对象。
    • stmt.Close(): 如果一个 *sql.Stmt 对象只使用一次,可以在 ExecQuery 后立即关闭。如果多次使用,可以在所有操作完成后关闭。
  • 错误处理: 仔细检查所有 err 返回值,并进行适当的错误处理。log.Fatal() 在生产环境中通常不适用,应改为更细致的错误报告机制。
  • 上下文 (Context): 在实际应用中,database/sql 的操作应配合 context.Context 使用,以便在超时或取消请求时能够及时终止数据库操作。例如:db.QueryContext(ctx, "SELECT ...")
  • 连接池: database/sql 默认提供连接池管理。您可以通过 db.SetMaxOpenConns()db.SetMaxIdleConns() 来配置连接池的大小,以优化性能和资源利用。
    • db.SetMaxOpenConns(10): 设置打开的最大连接数。
    • db.SetMaxIdleConns(5): 设置池中最大的空闲连接数。

11. 总结

本文详细介绍了如何在 Go 语言中通过 database/sqlmattn/go-sqlite3 驱动操作 SQLite 数据库,涵盖了连接、CRUD 操作和事务处理。通过遵循文中的示例和最佳实践,您可以有效地在 Go 应用程序中集成和管理 SQLite 数据库。Go 语言的简洁性结合 SQLite 的轻量级特性,为许多项目提供了一个强大而灵活的数据存储解决方案。
“`

滚动至顶部