连接
步骤:
依赖下载: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```gofunc init() {sql.Register("mysql", &MySQLDriver{})}
database/sql.go
var (driversMu sync.RWMutexdrivers = 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 uint64mu sync.Mutex // protects following fieldsfreeConn []*driverConnconnRequests map[uint64]chan connRequestnextRequest 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 booldep map[finalCloser]depSetlastPut map[*driverConn]string // stacktrace of last conn's put; debug onlymaxIdleCount int // zero means defaultMaxIdleConns; negative means 0maxOpen int // <= 0 means unlimitedmaxLifetime time.Duration // maximum amount of time a connection may be reusedmaxIdleTime time.Duration // maximum amount of time a connection may be idle before being closedcleanerCh 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 Usererr := 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 Usererr := 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 todolisterr := 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 usererr := 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("事务回滚...")}}
