引言
在软件开发中,数据库是管理和存储数据的关键组件。SQLite 作为一款轻量级的嵌入式数据库,因其零配置、高性能和易于集成等特性,成为众多小型项目和嵌入式系统的理想选择。而 Go 语言以其高效、简洁的特点,为操作 SQLite 数据库提供了强大的支持。本文将结合实际代码,深入介绍如何在 Go 语言中使用 SQLite 数据库,包括数据库和表的创建、增删改查操作以及操作的优化。
环境准备
在开始使用 Go 语言操作 SQLite 数据库之前,我们需要安装必要的库。可以使用以下命令安装github.com/mattn/go-sqlite3
库:
go get github.com/mattn/go-sqlite3
创建数据库和表
创建数据库
在 Go 语言中,使用database/sql
包结合github.com/mattn/go-sqlite3
驱动来操作 SQLite 数据库。通过sql.Open
函数可以打开或创建一个 SQLite 数据库文件。以下是一个简单的示例:
package main
import (
"database/sql"
"fmt"
"path/filepath"
_ "github.com/mattn/go-sqlite3"
)
const defDBPath = "./data"
func initContDB() error {
dbFile := filepath.Join(defDBPath, "test.db")
var err error
gDB, err := sql.Open("sqlite3", dbFile)
if err != nil {
fmt.Println("open test db ", err)
return err
}
fmt.Println("open test db ok")
_, err = gDB.Query("SELECT COUNT(*) FROM sqlite_master")
if err != nil {
fmt.Println("test db query ", err)
return err
}
return nil
}
在上述代码中,sql.Open
函数接受两个参数:驱动名(这里是sqlite3
)和数据库文件的路径。如果文件不存在,SQLite 会自动创建该文件。
创建表
在创建数据库之后,我们需要创建表来存储数据。可以使用Exec
方法执行 SQL 语句来创建表。为了避免重复创建表,我们可以先检查表是否已经存在。以下是创建表的示例代码:
func existTable(db *sql.DB, tbl string) bool {
stmt, err := db.Prepare("SELECT COUNT(*) FROM sqlite_master where tbl_name=?")
if err != nil {
fmt.Println(err)
return false
}
defer stmt.Close()
row := stmt.QueryRow(tbl)
if row == nil {
return false
}
var val int
row.Scan(&val)
if val == 0 {
return false
}
return true
}
func createTable() {
if existTable(gDB, "t_cont_info") {
return
}
tbl := `CREATE TABLE [t_cont_info](
[name] VARCHAR PRIMARY KEY NOT NULL UNIQUE,
[ver] VARCHAR,
[image] VARCHAR NOT NULL,
[cmd] INT(2) NOT NULL,
[opt_cmd] INT(2) NOT NULL,
[cpus] INT(2) NOT NULL,
[cpu_threshold] INT(4) NOT NULL,
[memory] INT(4) NOT NULL,
[mem_threshold] INT(4) NOT NULL,
[store_cap] INT(4) NOT NULL,
[store_threshold] INT(4) NOT NULL,
[ip] VARCHAR,
[create_time] VARCHAR,
[start_time] VARCHAR,
[runtimes] BIGINT,
[backup_path] VARCHAR,
[opt_time] VARCHAR);
`
_, err := gDB.Exec(tbl)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("creatTable t_cont_info")
}
在上述代码中,existTable
函数用于检查指定的表是否存在。createTable
函数根据检查结果决定是否创建t_cont_info
表。
增删改查操作
插入数据
插入数据可以使用Prepare
和Exec
方法。以下是插入的示例代码:
func insertWarnMsg(name, msg string) error {
stmt, err := gWarnDB.Prepare(`INSERT INTO t_cont_info
([name], [msg], [opt_time]) VALUES (?,?,?)`)
if err != nil {
fmt.Println(err)
return err
}
_, err = stmt.Exec(name, msg, getLocalTime())
if err != nil {
stmt.Close()
fmt.Println(err)
return err
}
stmt.Close()
checkWarnLimit(name)
return nil
}
在上述代码中,Prepare
方法用于准备 SQL 语句,Exec
方法用于执行插入操作。
更新数据
更新数据的操作与插入数据类似,同样使用Prepare
和Exec
方法。以下是更新的示例代码:
func updContInfo(ct ContItem) {
smt, err := gDB.Prepare("SELECT [name] FROM t_cont_info WHERE name=?")
if err != nil {
fmt.Println(err)
return
}
defer smt.Close()
upd := false
row := smt.QueryRow(ct.Name)
if row != nil {
var name string
err = row.Scan(&name)
if err != nil {
} else if name == ct.Name {
upd = true
}
}
if upd {
sqlCmd := `UPDATE t_cont_info SET [ver]=?, [image]=?, [cmd]=?, [opt_cmd]=?, [cpus]=?, [cpu_threshold]=?,
[memory]=?, [mem_threshold]=?, [store_cap]=?, [store_threshold]=?, [ip]=?, [create_time]=?,
[start_time]=?, [runtimes]=?, [backup_path]=?, [opt_time]=? WHERE [name]=?`
stmt, err := gDB.Prepare(sqlCmd)
if err != nil {
fmt.Println(err, ", sql: ", sqlCmd)
return
}
defer stmt.Close()
_, err = stmt.Exec(ct.Ver, ct.Img, ct.Cmd, ct.OptCmd, ct.CPUs, ct.CPUThreshold,
ct.Memory, ct.MemThreshold, ct.StoreCap, ct.StoreThreshold, ct.IP, ct.CreateTime,
ct.StartTime, ct.RunTimes, ct.BackupPath, getLocalTime(), ct.Name)
if err != nil {
fmt.Println(err)
return
}
} else {
stmt, err := gDB.Prepare(`INSERT INTO t_cont_info
([name] , [ver], [image], [cmd], [opt_cmd], [cpus], [cpu_threshold],
[memory], [mem_threshold], [store_cap], [store_threshold], [ip], [create_time],
[start_time], [runtimes], [backup_path], [opt_time]
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`)
if err != nil {
fmt.Println(err)
return
}
defer stmt.Close()
_, err = stmt.Exec(ct.Name, ct.Ver, ct.Img, ct.Cmd, ct.OptCmd, ct.CPUs, ct.CPUThreshold,
ct.Memory, ct.MemThreshold, ct.StoreCap, ct.StoreThreshold, ct.IP, ct.CreateTime,
ct.StartTime, ct.RunTimes, ct.BackupPath, getLocalTime())
if err != nil {
fmt.Println(err)
return
}
}
}
在上述代码中,首先检查数据是否存在,如果存在则更新数据,否则插入新数据。
删除数据
删除数据可以使用Prepare
和Exec
方法执行DELETE
语句。以下是删除的示例代码:
func deleteContItem(name string) {
del := func(tbl, name string) {
stmt, err := gDB.Prepare(fmt.Sprintf("DELETE FROM %s WHERE [name]=?", tbl))
if err != nil {
fmt.Println(err)
return
}
defer stmt.Close()
_, err = stmt.Exec(name)
if err != nil {
fmt.Println(err)
return
}
}
del("t_cont_info", name)
del("t_cont_res_map", name)
fmt.Println("deleteContItem: ", name)
}
在上述代码中,定义了一个匿名函数del
来执行删除操作,分别删除t_cont_info
和t_cont_res_map
表中指定名称的数据。
查询数据
查询数据可以使用Query
方法执行SELECT
语句,并使用Scan
方法将结果扫描到变量中。以下是查询所有数据的示例代码:
func loadFromDB() (lst []ContItem, err error) {
rows, err := gDB.Query(`SELECT [name] , [ver], [image], [cmd], [opt_cmd], [cpus],
[cpu_threshold], [memory], [mem_threshold], [store_cap], [store_threshold],
[ip], [create_time], [start_time], [runtimes], [backup_path] FROM t_cont_info`)
if err != nil {
fmt.Println(err)
return
}
defer rows.Close()
for rows.Next() {
var ct ContItem
rows.Scan(&ct.Name, &ct.Ver, &ct.Img, &ct.Cmd, &ct.OptCmd, &ct.CPUs, &ct.CPUThreshold,
&ct.Memory, &ct.MemThreshold, &ct.StoreCap, &ct.StoreThreshold, &ct.IP,
&ct.CreateTime, &ct.StartTime, &ct.RunTimes, &ct.BackupPath)
ct.DevMapList, _ = loadContResMap(ct.Name, CONT_RES_DEV)
ct.VolMapList, _ = loadContResMap(ct.Name, CONT_RES_VOL)
ct.PortMapList, _ = loadContResMap(ct.Name, CONT_RES_PORT)
lst = append(lst, ct)
fmt.Println("loadFromDB: ", ct)
}
return
}
在上述代码中,Query
方法返回一个Rows
对象,通过Next
方法遍历结果集,并使用Scan
方法将数据存储到ContItem
结构体中。
操作优化
批量操作
在进行大量数据的插入或更新操作时,可以使用事务来提高性能。事务可以将多个操作合并为一个原子操作,减少数据库的开销。以下是一个使用事务进行批量插入的示例代码:
func batchInsert(data []interface{}) error {
tx, err := gDB.Begin()
if err != nil {
return err
}
stmt, err := tx.Prepare("INSERT INTO table_name (column1, column2) VALUES (?,?)")
if err != nil {
tx.Rollback()
return err
}
defer stmt.Close()
for _, item := range data {
_, err = stmt.Exec(item.Value1, item.Value2)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
在上述代码中,Begin
方法开始一个事务,Prepare
方法准备 SQL 语句,Exec
方法执行插入操作,最后使用Commit
方法提交事务。如果发生错误,使用Rollback
方法回滚事务。
索引优化
为经常用于查询条件的列创建索引可以提高查询性能。例如,在t_cont_info
表中,如果经常根据name
列进行查询,可以为name
列创建索引:
func createIndex() {
_, err := gDB.Exec("CREATE INDEX idx_name ON t_cont_info (name)")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("create index idx_name")
}
在上述代码中,使用CREATE INDEX
语句为name
列创建索引。
总结
本文深入介绍了如何在 Go 语言中使用 SQLite 数据库,包括数据库和表的创建、增删改查操作以及操作的优化。通过使用database/sql
包和github.com/mattn/go-sqlite3
驱动,我们可以方便地操作 SQLite 数据库。在实际开发中,根据具体需求选择合适的操作方法和优化策略,可以提高程序的性能和稳定性。