Go语言连接数据库
入门连接
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" //init()
)
func main() {
//DSN : Data Soruce Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_demo" //若没有,在mysql新建一个sql_demo数据库
db, err := sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
//做完确认检查之后,确保db不为nil
//Close()用来释放掉数据库连接相关的资源
defer db.Close() //注意这行代码要写在err判断的下面
//尝试与数据库建立连接(校验dsn是否正确)
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
}
fmt.Println("connect to db success")
}
模块化连接
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" //init()
"time"
)
var db *sql.DB
func initMySQL() (err error) {
//DSN : Data Soruce Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_demo"
//去初始化全局的db对象,而不是新声明一个db变量
db, err = sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
//尝试与数据库建立连接(校验dsn是否正确)
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
return
}
//数值根据业务情况来确定
db.SetConnMaxLifetime(time.Second * 10)
db.SetMaxOpenConns(200) // 最大连接数
db.SetMaxIdleConns(10) //最大空闲连接数
return
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
}
//Close()用来释放掉数据库连接相关的资源
defer db.Close() //注意这行代码要写在err判断的下面
fmt.Println("connect to db success")
//db.xx()去使用数据库操作
}
Go语言操作MySQL执行增删改查
① 单行查询
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" //init()
"time"
)
var db *sql.DB
func initMySQL() (err error) {
//DSN : Data Soruce Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_demo"
//去初始化全局的db对象,而不是新声明一个db变量
db, err = sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
//尝试与数据库建立连接(校验dsn是否正确)
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
return
}
//数值根据业务情况来确定
db.SetConnMaxLifetime(time.Second * 10)
db.SetMaxOpenConns(200) // 最大连接数
db.SetMaxIdleConns(10) //最大空闲连接数
return
}
type user struct {
id int
age int
name string
}
// 查询单条示例
func queryRowDemo() {
sqlStr := "select id, name, age from user where id=?"
var u user
//非常重要,确保QueryRow之后调用Scan方法,否则持有的数据库连接不会被释放
err := db.QueryRow(sqlStr, 1).Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed,err:%v", err)
return
}
fmt.Printf("id:%d, name:%s, age:%d\n", u.id, u.name, u.age)
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
}
//Close()用来释放掉数据库连接相关的资源
defer db.Close() //注意这行代码要写在err判断的下面
fmt.Println("connect to db success")
//db.xx()去使用数据库操作
queryRowDemo()
}
②多行查询
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" //init()
"time"
)
var db *sql.DB
func initMySQL() (err error) {
//DSN : Data Soruce Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_demo"
//去初始化全局的db对象,而不是新声明一个db变量
db, err = sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
//尝试与数据库建立连接(校验dsn是否正确)
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
return
}
//数值根据业务情况来确定
db.SetConnMaxLifetime(time.Second * 10)
db.SetMaxOpenConns(200) // 最大连接数
db.SetMaxIdleConns(10) //最大空闲连接数
return
}
type user struct {
id int
age int
name string
}
// 查询多条示例
func queryRowDemo() {
sqlStr := "select id, name, age from user where id>?"
rows, err := db.Query(sqlStr, 0)
if err != nil {
fmt.Printf("scan failed,err:%v", err)
return
}
//非常重要:关闭rows释放是有的数据库链接
defer rows.Close()
//循环读取结果中集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d, name:%s, age:%d\n", u.id, u.name, u.age)
}
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
}
//Close()用来释放掉数据库连接相关的资源
defer db.Close() //注意这行代码要写在err判断的下面
fmt.Println("connect to db success")
//db.xx()去使用数据库操作
queryRowDemo()
fmt.Println("...查询结束了...")
}
③插入数据
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" //init()
"time"
)
var db *sql.DB
func initMySQL() (err error) {
//DSN : Data Soruce Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_demo"
//去初始化全局的db对象,而不是新声明一个db变量
db, err = sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
//尝试与数据库建立连接(校验dsn是否正确)
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
return
}
//数值根据业务情况来确定
db.SetConnMaxLifetime(time.Second * 10)
db.SetMaxOpenConns(200) // 最大连接数
db.SetMaxIdleConns(10) //最大空闲连接数
return
}
type user struct {
id int
age int
name string
}
// 多行查询
func queryRowDemo() {
sqlStr := "select id, name, age from user where id>?"
rows, err := db.Query(sqlStr, 0)
if err != nil {
fmt.Printf("scan failed,err:%v", err)
return
}
//非常重要:关闭rows释放是有的数据库链接
defer rows.Close()
//循环读取结果中集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d, name:%s, age:%d\n", u.id, u.name, u.age)
}
}
// 插入数据
func insertRowDemo() {
sqlStr := "insert into user (name, age) values(?,?)"
ret, err := db.Exec(sqlStr, "Chen", 23)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
var theID int64
theID, err = ret.LastInsertId() //新插入的id
if err != nil {
fmt.Printf("get lastinsert ID failed, err:%v\n", err)
return
}
fmt.Printf("insert success, the id is %d.\n", theID)
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
}
//Close()用来释放掉数据库连接相关的资源
defer db.Close() //注意这行代码要写在err判断的下面
fmt.Println("connect to db success")
//db.xx()去使用数据库操作
insertRowDemo() //insert success, the id is 4.
queryRowDemo() //输出:
//id:1, name:qimi, age:18
//id:2, name:Mike, age:20
//id:3, name:Jack, age:21
//id:4, name:Chen, age:23
fmt.Println("...查询结束了...")
}
④更新数据
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" //init()
"time"
)
var db *sql.DB
func initMySQL() (err error) {
//DSN : Data Soruce Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_demo"
//去初始化全局的db对象,而不是新声明一个db变量
db, err = sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
//尝试与数据库建立连接(校验dsn是否正确)
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
return
}
//数值根据业务情况来确定
db.SetConnMaxLifetime(time.Second * 10)
db.SetMaxOpenConns(200) // 最大连接数
db.SetMaxIdleConns(10) //最大空闲连接数
return
}
type user struct {
id int
age int
name string
}
// 多行查询
func queryRowDemo() {
sqlStr := "select id, name, age from user where id>?"
rows, err := db.Query(sqlStr, 0)
if err != nil {
fmt.Printf("scan failed,err:%v", err)
return
}
//非常重要:关闭rows释放是有的数据库链接
defer rows.Close()
//循环读取结果中集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d, name:%s, age:%d\n", u.id, u.name, u.age)
}
}
//更新数据
func updateRowDemo() {
sqlStr := "update user set age=? where id=?"
ret, err := db.Exec(sqlStr, 44, 4)
if err != nil {
fmt.Printf("update failed, err:%v\n", err)
return
}
var n int64
n, err = ret.RowsAffected() //操作影响的行数
if err != nil {
fmt.Printf("Get RowsAffectd failed, err:%v\n", err)
return
}
fmt.Printf("update success, affected rows:%d\n", n)
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
}
//Close()用来释放掉数据库连接相关的资源
defer db.Close() //注意这行代码要写在err判断的下面
fmt.Println("connect to db success")
//db.xx()去使用数据库操作
updateRowDemo()
queryRowDemo() //输出:
//update success, affected rows:1
//id:1, name:qimi, age:18
//id:2, name:Mike, age:20
//id:3, name:Jack, age:21
//id:4, name:Chen, age:44
fmt.Println("...查询结束了...")
}
⑤删除数据
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" //init()
"time"
)
var db *sql.DB
func initMySQL() (err error) {
//DSN : Data Soruce Name
dsn := "root:123456@tcp(127.0.0.1:3306)/sql_demo"
//去初始化全局的db对象,而不是新声明一个db变量
db, err = sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
//尝试与数据库建立连接(校验dsn是否正确)
err = db.Ping()
if err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
return
}
//数值根据业务情况来确定
db.SetConnMaxLifetime(time.Second * 10)
db.SetMaxOpenConns(200) // 最大连接数
db.SetMaxIdleConns(10) //最大空闲连接数
return
}
type user struct {
id int
age int
name string
}
// 多行查询
func queryRowDemo() {
sqlStr := "select id, name, age from user where id>?"
rows, err := db.Query(sqlStr, 0)
if err != nil {
fmt.Printf("scan failed,err:%v", err)
return
}
//非常重要:关闭rows释放是有的数据库链接
defer rows.Close()
//循环读取结果中集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d, name:%s, age:%d\n", u.id, u.name, u.age)
}
}
// 删除数据
func deleteRowDemo() {
sqlStr := "delete from user where id=?"
ret, err := db.Exec(sqlStr, 3)
if err != nil {
fmt.Printf("delete failed, err:%v\n", err)
return
}
var n int64
n, err = ret.RowsAffected() //操作影响的行数
if err != nil {
fmt.Printf("Get RowsAffectd failed, err:%v\n", err)
return
}
fmt.Printf("delete success, affected rows:%d\n", n)
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
}
//Close()用来释放掉数据库连接相关的资源
defer db.Close() //注意这行代码要写在err判断的下面
fmt.Println("connect to db success")
//db.xx()去使用数据库操作
deleteRowDemo()
queryRowDemo() //输出:
//delete success, affected rows:1
//id:1, name:qimi, age:18
//id:2, name:Mike, age:20
//id:4, name:Chen, age:44
fmt.Println("...查询结束了...")
}
MySQL预处理
什么是预处理?
普通SQL语句执行过程:
①客户端对SQL语句进行占位符替换得到完整的SQL语句
②客户端发送完整SQL语句到MySQL服务端
③MySQL服务端执行完整的SQL语句并将结果返回给客户端
预处理执行过程:
①把SQL语句分成两部分,命令部分与数据部分
②先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理
③然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换
④MySQL服务端执行完整的SQL语句并将结果返回给客户端
为什么要预处理?
1、优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
2、避免SQL注入问题。
Go语言实现MySQL预处理
func (db *DB) Prepare(query string)(*Stmt, error)
Prepare方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。
查询示例代码
// 预处理查询示例
func prepareQueryDemo() {
sqlStr := "select id, name, age from user where id > ?"
stmt, err := db.Prepare(sqlStr)
if err != nil {
fmt.Printf("prepare failed, err:%v\n", err)
return
}
defer stmt.Close()
rows, err := stmt.Query(0) //查询参数
if err != nil {
fmt.Printf("query failed, err:%v\n", err)
return
}
defer rows.Close()
//循环读取结果集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d, name:%v, age:%d\n", u.id, u.name, u.age)
}
}
插入、更新和删除操作的预处理十分类似,这里以插入操作的预处理为例:
// 预处理插入示例
func prepareInsertDemo() {
sqlStr := "insert into user (name, age) values (?,?)"
stmt, err := db.Prepare(sqlStr)
if err != nil {
fmt.Printf("prepare failed, err:%v\n", err)
return
}
defer stmt.Close()
_, err = stmt.Exec("江户川柯南", 8) //查询参数
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
_, err = stmt.Exec("毛利兰", 17) //查询参数
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
fmt.Println("insert success")
}
SQL注入问题
我们任何时候都不应该自己拼接SQL语句!
任何时候都不应该相信用户输入的内容是安全的,合法的!
// sql注入示例
func sqlInjectDemo(name string) {
sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'", name)
fmt.Printf("SQL:%s\n", sqlStr)
var u user
err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("exec failed, err:%v\n", err)
return
}
fmt.Printf("user:%#v\n", u)
}
以下输入字符串都可以引发SQL注入问题:
sqlInjectDemo("xxx ' or 1=1#")
sqlInjectDemo("xxx ' union select * from user#")
sqlInjectDemo("xxx ' and (select count(*) from user)<10#")
补充:不同的数据库中,SQL语句使用的占位符语法不尽相同
数据库 | 占位符语法 |
MySQL | ? |
PostgreSQL | $1,$2等 |
SQLite | ?和$1 |
Oracle | :name |
Go实现MySQL事务
事务:一个最小的不可再分的工作单元
在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行。
事务的ACID
事务必须满足的4个条件(ACID):
①原子性(Atomicity,或称不可分割性)
②一致性(Consistency)
③隔离性(Isolation,又称独立性)
④持久性(Durability)
事务相关方法
Go语言中使用以下三个方法实现MySQL中的事务操作。
开始事务
func (db *DB) Begin() (*Tx, error)
提交事务
func (tx *Tx) Commit() error
回滚事务
func (tx *Tx) Rollback() error
下面的代码演示了一个简单的事务操作,该事物操作能够确保两次更新操作要么同时成功要么同时失败,不会存在中间状态。
// 事务操作示例
func transactionDemo() {
tx, err := db.Begin() // 开启事务
if err != nil {
if tx != nil {
tx.Rollback() // 回滚
}
fmt.Printf("begin trans failed, err:%v\n", err)
return
}
sqlStr1 := "Update user set age=30 where id=?"
ret1, err := tx.Exec(sqlStr1, 2)
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec sql1 failed, err:%v\n", err)
return
}
affRow1, err := ret1.RowsAffected()
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
return
}
sqlStr2 := "Update user set age=40 where id=?"
ret2, err := tx.Exec(sqlStr2, 3)
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec sql2 failed, err:%v\n", err)
return
}
affRow2, err := ret2.RowsAffected()
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
return
}
fmt.Println(affRow1, affRow2)
if affRow1 == 1 && affRow2 == 1 {
fmt.Println("事务提交啦...")
tx.Commit() // 提交事务
} else {
tx.Rollback()
fmt.Println("事务回滚啦...")
}
fmt.Println("exec trans success!")
}
参考李文周的博客:https://www.liwenzhou.com/posts/Go/mysql/