database/sql 包提供了保证SQL或类SQL数据库的泛用接口,常用的数据库驱动有 github.com/go-sql-driver/mysql
https://pkg.go.dev/database/sql
1、初始化数据库连接 首先得初始化一个数据库连接,我们可以通过 sql.Open
来连接数据库:
1 2 3 4 5 DB, err := sql.Open("mysql" , "root:123456@tcp(127.0.0.1:3306)/test" )if err != nil { fmt.Println("Error opening DB:" , err) return }
"root:123456@tcp(127.0.0.1:3306)/test"
包含了数据库用户名、密码、MySQL 服务器地址、端口以及要连接的数据库名称。注意,如果你的密码是空的,那记得跳过密码部分。
但这里只是打开了一个数据库连接。为了让连接更加高效,我们要配置一些连接池的参数,比如最大连接数、最大空闲连接数和连接的最大生命周期:
1 2 3 DB.SetConnMaxLifetime(time .Minute * 5) DB.SetMaxOpenConns(50) DB.SetMaxIdleConns(10)
我个人建议设置合理的连接池大小,避免连接过多导致 MySQL 服务器压力过大。连接池的大小根据你的系统负载来调节,不要让数据库连接成为瓶颈。
最后,检查一下是否能成功连接到数据库:
1 2 3 4 5 if err := DB.Ping(); err != nil { fmt.Println("Connection failed:" , err) return } fmt.Println("Connected successfully" )
Ping()
方法会尝试与数据库建立连接,如果失败就直接报错。至此,数据库连接初始化完成,接下来就可以进行数据查询和操作了。
2、查询 Go 提供了 DB.Query
和 DB.QueryRow
两种方法来查询数据。
DB.Query
DB.Query
用于查询多行数据,返回的是一个 *Rows
类型的对象,你需要通过 rows.Next()
来循环遍历每一行数据,然后使用 rows.Scan
将当前行的数据扫描到相应的变量中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 rows, err := DB.Query("SELECT id, name FROM users WHERE age > ?" , 18 ) if err != nil { fmt.Println("Query failed:" , err) return } defer rows.Close() for rows.Next() { var id int var name string if err := rows.Scan(&id, &name); err != nil { fmt.Println("Scan failed:" , err) return } fmt.Printf("ID: %d, Name: %s\n" , id, name) }
这里的 rows.Scan
就是把查询结果扫描到变量中,注意传递的是变量的地址。如果你在这步出现了 Scan
错误,通常是查询字段的类型和数据库字段不匹配,或者是查询结果为 NULL
。
DB.QueryRow
DB.QueryRow
用于查询单行数据,返回的是一个单独的 Row
对象。和 Query
不同,它只返回一行结果,适合用于查询根据条件唯一的数据。
如果没有找到结果,它不会返回 nil,而是返回一个实现了 Row 接口的非空值。
在调用 QueryRow()
后,通常会使用 Scan()
方法从结果中提取数据。Scan()
方法负责将结果的列映射到相应的变量,并在需要时触发底层查询的执行。在调用 Scan()
之前,QueryRow()
不会执行实际的查询。
1 2 3 4 5 6 7 var user User err := DB.QueryRow("SELECT id, name FROM users WHERE id = ?" , 1).Scan(&user.ID, &user.Name) if err != nil { fmt.Println("QueryRow failed:" , err) return } fmt.Printf("User: %+v\n" , user)
3、增、删、改 Go 提供了 Exec
方法来执行增、删、改等操作。这些操作不会返回行数据,而是返回一个 Result
类型
你可以通过 RowsAffected
来查看受影响的行数,如果是新增,可以使用 LastInsertId()
获取最后插入的 ID,通常在表中有一个自增的主键时使用
有两种方式
一种是直接使用 db.Exec()
一种是使用预编译的方式
把SQL语句分成两部分,命令部分与数据部分。
先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
MySQL 服务端执行完整的SQL语句并将结果返回给客户端。
直接使用 db.Exec()` 插入 1 2 3 4 sqlStr := "insert into user (name ,age) values (?,?)" ret, err := db.Exec(sqlStr, " 王五", 18) theID, err := ret.LastInsertId() fmt.Println(" insert success ,the id is ", theID)
更新 1 2 3 4 sqlStr := "update user set age=? where id =?" ret, err := db.Exec(sqlStr, 100, 5) n, err := ret.RowsAffected() // 操作影响的行数 fmt.Printf(" update success , affected rows :%d\n", n)
删除 1 2 3 4 sqlStr := "delete from user where id=?" ret, err := db.Exec(sqlStr, 5 ) n, err := ret.RowsAffected() fmt.Printf("delete success,affected rows:%d\n" , n)
使用预编译 插入 1 2 3 4 5 6 7 8 9 10 11 12 stmt, err := DB .Prepare("INSERT INTO users(name, age) VALUES(?, ?)" ) if err != nil { fmt.Println("Prepare failed:" , err ) return } res, err := stmt.Exec("John Doe" , 30) if err != nil { fmt.Println("Exec failed:" , err ) return } lastID, _ := res.LastInsertId() fmt.Printf("Inserted user with ID: %d\n" , lastID)
更新 1 2 3 4 5 6 7 8 9 10 11 12 stmt, err := DB .Prepare("UPDATE users SET age=?" ) if err != nil { fmt.Println("Prepare failed:" , err ) return } res, err := stmt.Exec(30) if err != nil { fmt.Println("Exec failed:" , err ) return } lastID, _ := res.RowsAffected() fmt.Printf("update success, affected rows:%d\n" , n )
删 1 2 3 4 5 6 7 8 9 10 11 12 stmt, err := DB.Prepare("DELETE FROM users WHERE id = ?" ) if err != nil { fmt.Println("Prepare failed:" , err) return } res, err := stmt.Exec(1 ) if err != nil { fmt.Println("Exec failed:" , err) return } affected, _ := res.RowsAffected() fmt.Printf("Deleted %d rows\n" , affected)
4、事务处理 在执行多个数据库操作时,事务处理是保证数据一致性的重要手段。Go 的 sql.DB
和 sql.Tx
都提供了非常方便的事务支持。常见的操作是:Begin
开始事务,Commit
提交事务,Rollback
回滚事务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 tx, err := DB.Begin() if err != nil { fmt.Println("Begin transaction failed:" , err) return } _, err = tx.Exec("UPDATE users SET age = ? WHERE id = ?" , 35 , 1 ) if err != nil { tx.Rollback() fmt.Println("Exec failed:" , err) return } err = tx.Commit() if err != nil { fmt.Println("Commit failed:" , err) return } fmt.Println("Transaction committed successfully" )
5、完整示例 通过之前的拆解,我们已经掌握了如何优雅地与数据库交互。现在来个完整的示例,看看我们如何在一个简单的应用中完成初始化、查询、插入、删除操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func main () { DB, err := sql.Open("mysql" , "root:123456@tcp(127.0.0.1:3306)/test" ) if err != nil { fmt.Println("Error opening DB:" , err) return } defer DB.Close() DB.SetConnMaxLifetime(time.Minute * 5 ) DB.SetMaxOpenConns(50 ) DB.SetMaxIdleConns(10 ) if err := DB.Ping(); err != nil { fmt.Println("Connection failed:" , err) return } fmt.Println("Connected successfully" ) rows, _ := DB.Query("SELECT id, name FROM users" ) defer rows.Close() for rows.Next() { var id int var name string rows.Scan(&id, &name) fmt.Printf("ID: %d, Name: %s\n" , id, name) } stmt, _ := DB.Prepare("INSERT INTO users(name, age) VALUES(?, ?)" ) stmt.Exec("Jane Doe" , 28 ) stmt, _ = DB.Prepare("DELETE FROM users WHERE id = ?" ) stmt.Exec(1 ) }