Skip to content

Go SQLite CURD 完整示例

Go 操作 SQLite 最常用库:github.com/mattn/go‑sqlite3,这是 cgo 实现的 SQLite 驱动,配合标准库 database/sql 完成全部 CURD。

注意:该库依赖 cgo,编译环境需要 GCC;如果不想用 cgo,可以选用纯 Go 实现:modernc.org/sqlite,API 几乎完全一致。

1. 安装依赖

bash
# cgo版本
go get github.com/mattn/go-sqlite3

# 纯go无cgo版本(推荐跨平台编译)
go get modernc.org/sqlite

使用 modernc.org/sqlite 只需要把导入包改成 _ "modernc.org/sqlite",其余代码不用修改。

2. 完整 CURD 示例代码

go
package main

import (
	"database/sql"
	"fmt"
	"log"

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

// User 实体
type User struct {
	ID   int
	Name string
	Age  int
}

func main() {
	// 打开sqlite数据库,文件存在则打开,不存在自动创建
	db, err := sql.Open("sqlite3", "./test.db")
	if err != nil {
		log.Fatalf("打开数据库失败: %v", err)
	}
	defer db.Close()

	// 设置连接参数,sqlite单文件,一般设置1个连接
	db.SetMaxOpenConns(1)
	db.SetMaxIdleConns(1)

	// 检查表是否存在,不存在创建表
	err = createTable(db)
	if err != nil {
		log.Fatal(err)
	}

	// C 创建
	uid, err := createUser(db, "张三", 22)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("新增用户id: %d\n", uid)

	// R 查询单行
	u, err := getUserByID(db, int(uid))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("查询单个:%+v\n", u)

	// R 查询多行
	users, err := listUser(db)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("全部用户:%+v\n", users)

	// U 更新
	affect, err := updateUser(db, int(uid), "张三改名字", 25)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("更新影响行数:%d\n", affect)

	// D 删除
	delCnt, err := deleteUser(db, int(uid))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("删除行数:%d\n", delCnt)
}

// 创建表
func createTable(db *sql.DB) error {
	sqlStr := `
CREATE TABLE IF NOT EXISTS user (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    age INTEGER
);
`
	_, err := db.Exec(sqlStr)
	return err
}

// C 新增
func createUser(db *sql.DB, name string, age int) (int64, error) {
	stmt, err := db.Prepare(`INSERT INTO user(name,age) values (?,?)`)
	if err != nil {
		return 0, err
	}
	defer stmt.Close()

	res, err := stmt.Exec(name, age)
	if err != nil {
		return 0, err
	}
	// 获取自增id
	return res.LastInsertId()
}

// R 根据id查询单条
func getUserByID(db *sql.DB, id int) (*User, error) {
	row := db.QueryRow(`SELECT id,name,age FROM user WHERE id = ?`, id)
	var u User
	err := row.Scan(&u.ID, &u.Name, &u.Age)
	if err != nil {
		// 查询不到记录
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}
	return &u, nil
}

// R 查询全部列表
func listUser(db *sql.DB) ([]User, error) {
	rows, err := db.Query(`SELECT id,name,age FROM user`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var list []User
	for rows.Next() {
		var u User
		if err := rows.Scan(&u.ID, &u.Name, &u.Age); err != nil {
			return nil, err
		}
		list = append(list, u)
	}
	// 遍历结束检查错误
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return list, nil
}

// U 更新
func updateUser(db *sql.DB, id int, name string, age int) (int64, error) {
	res, err := db.Exec(`UPDATE user SET name=?, age=? WHERE id=?`, name, age, id)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

// D 删除
func deleteUser(db *sql.DB, id int) (int64, error) {
	res, err := db.Exec(`DELETE FROM user WHERE id=?`, id)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

核心API说明

  1. sql.Open:只是建立句柄,不会立刻连接数据库,真正验证连通可以执行 db.Ping()
go
if err := db.Ping(); err != nil {
	log.Fatal(err)
}
  1. Exec:执行无返回结果SQL:INSERT / UPDATE / DELETE / DDL,返回 Result,可以拿到LastInsertId()RowsAffected()
  2. Query:返回多行结果集,必须defer rows.Close(),否则会泄露连接
  3. QueryRow:只取一行,没有返回sql.ErrNoRows错误
  4. Prepare:预编译语句,防止SQL注入,重复执行时性能更好,用完defer stmt.Close()
  5. SQLite占位符统一用 ?,不要用 %s字符串拼接SQL,避免注入。

事务示例

go
func txDemo(db *sql.DB) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	defer func() {
		if r := recover(); r != nil {
			_ = tx.Rollback()
		}
	}()

	_, err = tx.Exec(`INSERT INTO user(name,age) values(?,?)`, "A", 18)
	if err != nil {
		_ = tx.Rollback()
		return err
	}
	_, err = tx.Exec(`INSERT INTO user(name,age) values(?,?)`, "B", 19)
	if err != nil {
		_ = tx.Rollback()
		return err
	}

	return tx.Commit()
}

modernc.org/sqlite 纯Go版本差异

  1. 不需要gcc,交叉编译友好,适合打包部署
  2. 导入包改为 _ "modernc.org/sqlite",业务代码完全一样
  3. dsn可以开启WAL模式(提升并发性能)
go
db, err := sql.Open("sqlite", "file:test.db?_journal=WAL&_timeout=5000")

SQLite 常用优化点(Go)

  1. 开启WAL模式,大幅提升读写并发
sql
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
  1. sqlite文件数据库不要设置大量连接池,SetMaxOpenConns=1是最佳实践,SQLite文件锁不支持多写并发。
  2. 批量插入优先使用事务包裹,不要单条提交。