连接
步骤:
依赖下载:go get -u github.com/go-sql-driver/mysql
代码 ```go import ( “database/sql” _ “github.com/go-sql-driver/mysql” // init() )
var db *sql.DB
func initDB() (err error) { dsn := “root:204079790@tcp(127.0.0.1:3306)/demo” db, err = sql.Open(“mysql”, dsn) if err != nil { return }
err = db.Ping()
if err != nil {
return
}
return nil
}
func main() { err := initDB() if err != nil { panic(err) } }
<a name="cxvX6"></a>
# MySQL驱动
init()函数位于:github.com/go-sql-driver/mysql/driver.go
```go
func init() {
sql.Register("mysql", &MySQLDriver{})
}
database/sql.go
var (
driversMu sync.RWMutex
drivers = make(map[string]driver.Driver)
)
...
...
type DB struct {
// Atomic access only. At top of struct to prevent mis-alignment
// on 32-bit platforms. Of type time.Duration.
waitDuration int64 // 连接等待时间
connector driver.Connector
// numClosed is an atomic counter which represents a total number of
// closed connections. Stmt.openStmt checks it before cleaning closed
// connections in Stmt.css.
numClosed uint64
mu sync.Mutex // protects following fields
freeConn []*driverConn
connRequests map[uint64]chan connRequest
nextRequest uint64 // Next key to use in connRequests.
numOpen int // number of opened and pending open connections
// Used to signal the need for new connections
// a goroutine running connectionOpener() reads on this chan and
// maybeOpenNewConnections sends on the chan (one send per needed connection)
// It is closed during db.Close(). The close tells the connectionOpener
// goroutine to exit.
openerCh chan struct{}
closed bool
dep map[finalCloser]depSet
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
maxIdleCount int // zero means defaultMaxIdleConns; negative means 0
maxOpen int // <= 0 means unlimited
maxLifetime time.Duration // maximum amount of time a connection may be reused
maxIdleTime time.Duration // maximum amount of time a connection may be idle before being closed
cleanerCh chan struct{}
waitCount int64 // Total number of connections waited for.
maxIdleClosed int64 // Total number of connections closed due to idle count.
maxIdleTimeClosed int64 // Total number of connections closed due to idle time.
maxLifetimeClosed int64 // Total number of connections closed due to max connection lifetime limit.
stop func() // stop cancels the connection opener.
}
...
...
// 将驱动名注册到全局drivers中
func Register(name string, driver driver.Driver) {
driversMu.Lock()
defer driversMu.Unlock()
if driver == nil {
panic("sql: Register driver is nil")
}
if _, dup := drivers[name]; dup {
panic("sql: Register called twice for driver " + name)
}
drivers[name] = driver
}
...
...
func OpenDB(c driver.Connector) *DB {
ctx, cancel := context.WithCancel(context.Background())
db := &DB{
connector: c,
openerCh: make(chan struct{}, connectionRequestQueueSize),
lastPut: make(map[*driverConn]string),
connRequests: make(map[uint64]chan connRequest),
stop: cancel,
}
go db.connectionOpener(ctx)
return db
}
...
...
// 输入驱动数据库名和DSN,返回一个DB对象
func Open(driverName, dataSourceName string) (*DB, error) {
driversMu.RLock()
driveri, ok := drivers[driverName]
driversMu.RUnlock()
if !ok {
return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
}
if driverCtx, ok := driveri.(driver.DriverContext); ok {
connector, err := driverCtx.OpenConnector(dataSourceName)
if err != nil {
return nil, err
}
return OpenDB(connector), nil
}
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}
CRUD
Creat
func insertRow() {
sqlStr := `INSERT INTO user(age,name) values(?,?)`
ret, err := db.Exec(sqlStr, 1, "富贵🐷")
if err != nil {
fmt.Printf("insert failed, err: %v\n", err)
}
ID, err := ret.LastInsertId()
if err != nil {
fmt.Printf("get LastId failed, err: %v\n", err)
} else {
fmt.Printf("insert success, the id is: %d.\n", ID)
}
}
Retrieve
单行查询
func queryRow() {
sqlStr := `select id, name, age from user where id = ?`
var u User
err := db.QueryRow(sqlStr, 2).Scan(&u.Id, &u.Name, &u.Age)
if err != nil {
fmt.Printf("scan failed, err: %v\n", err)
} else {
fmt.Printf("id:%d, name:%s, age:%d\n", u.Id, u.Name, u.Age)
}
}
多行查询
func queryRows() {
sqlStr := `select id, name, age from user where ID > ?`
rows, err := db.Query(sqlStr, 0)
if err != nil {
fmt.Printf("query failed, err: %v\n", err)
}
// 关闭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)
}
}
Update
func updateRow() {
sqlStr := `update user set age=? where id=?`
ret, err := db.Exec(sqlStr, 3, 2)
if err != nil {
fmt.Printf("update failed, err: %v\n", err)
return
}
n, err := ret.RowsAffected() // 操作影响的行数
if err != nil {
fmt.Printf("get RowsAffected failed, err: %v\n", err)
return
}
fmt.Printf("update success, %d rows are affected\n", n)
}
Delete
func deleteRow() {
sqlErr := `DELETE FROM user WHERE id=?`
ret, err := db.Exec(sqlErr, 2)
if err != nil {
fmt.Printf("delete failed, err: %V\n", err)
return
}
n, err := ret.RowsAffected() // 操作影响的行数
if err != nil {
fmt.Printf("get RowsAffected failed, err: %v\n", err)
return
}
fmt.Printf("delete success, %d rows are affected\n", n)
}
MySQL预处理
什么是预处理?
普通SQL语句执行过程:
- 客户端对SQL语句进行占位符替换得到完整的SQL语句。
- 客户端发送完整SQL语句到MySQL服务端
- MySQL服务端执行完整的SQL语句并将结果返回给客户端。
预处理执行过程:
语法
// 预处理查询
func preparedQuery() {
sqlStr := "SELECT id, title, status FROM todolist WHERE id > ?"
stmt, err := db.Prepare(sqlStr)
if err != nil {
fmt.Printf("prepare failed, err: %v", err)
}
defer stmt.Close()
rows, err := stmt.Query(0)
if err != nil {
fmt.Printf("query failed, err: %v", err)
}
defer rows.Close()
for rows.Next() {
var l todolist
err := rows.Scan(&l.id, &l.title, &l.status)
if err != nil {
fmt.Printf("scan failed, err: %v", err)
}
fmt.Println(l)
}
}
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)
}
func main() {
sqlInjectDemo("xxx' or 1=1#")
sqlInjectDemo("xxx' union select * from user #")
sqlInjectDemo("xxx' and (select count(*) from user) <10 #")
}
事务
事务相关概念见:https://www.yuque.com/u21069870/htoiko/ccvl1x#PTbOn
// 事务操作示例
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
}
// 确保事务的一致性
if affRow1 == 1 && affRow2 == 1 {
fmt.Println("事务提交...")
tx.Commit() // 提交事务
} else {
tx.Rollback()
fmt.Println("事务回滚...")
}
}