
Mysql编程
1.MySQL环境
启动Msql Server容器
➜ ~ docker run --name MysqlGolang -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root1234 -d mysql:5.7启动Msql Client容器
➜ ~ docker run -it --network host --rm mysql:5.7 mysql -h127.0.0.1 -P3306 -uroot -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 5 Server version: 5.7.32 MySQL Community Server (GPL) Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>创建一个数据库表结构:数据库s1,用户表userinfo,关联用户信息表userdetail。
mysql> CREATE DATABASE s1; Query OK, 1 row affected (0.00 sec) mysql> CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, `username` VARCHAR(64) NULL DEFAULT NULL, `departname` VARCHAR(64) NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL, PRIMARY KEY (`uid`) ); Query OK, 0 rows affected (0.07 sec) mysql> CREATE TABLE `userdetail` ( `uid` INT(10) NOT NULL DEFAULT '0', `intro` TEXT NULL, `profile` TEXT NULL, PRIMARY KEY (`uid`) ); Query OK, 0 rows affected (0.07 sec)
2.MySQL驱动
Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持database/sql标准,而有些是采用了自己的实现接口,常用的有如下几种:
https://github.com/go-sql-driver/mysql支持database/sql,全部采用go写。https://github.com/ziutek/mymysql支持database/sql,也支持自定义的接口,全部采用go写。https://github.com/Philio/GoMySQL不支持database/sql,自定义接口,全部采用go写接下来我们主要以第一个驱动为例,也推荐大家采用它,主要理由:
- 这个驱动比较新,维护的比较好
- 完全支持database/sql接口
- 支持keepalive,保持长连接和连接稳定
g执行下面命令安装
go get github.com/go-sql-driver/mysql
3.连接数据库
3.1 首先导入包
import ( "database/sql" _ "github.com/go-sql-driver/mysql" )
- database/sql,是golang的标准库之一,它提供了一系列接口方法,用于访问关系数据库。它并不会提供数据库特有的方法,那些特有的方法交给数据库驱动去实现。
- 我们正在加载的驱动是匿名的,将其限定符别名为_。
当导入了一个数据库驱动后, 此驱动会自行初始化并注册自己到Golang的database/sql上下文中, 因此我们就可以通过 database/sql 包提供的方法访问数据库了。
3.2 建立连接
使用Open函数
func Open(driverName, dataSourceName string) (*DB, error) /* 其中的两个参数: drvierName,这个名字其实就是数据库驱动注册到 database/sql 时所使⽤用的名字. "mysql" dataSourceName,数据库连接信息,这个连接包含了数据库的用户名, 密码, 数据库主机以及需要 连接的数据库名等信息. 用户名:密码@协议(地址:端口)/数据库?参数=参数值 */ db, err := sql.Open("mysql", "⽤用户名:密码@tcp(IP:端口)/数据库?charset=utf8mb4")例如: db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True")
说明:
- sql.Open 并不会立即建立一个数据库的网络连接, 也不会对数据库链接参数的合法性做检验, 它仅仅是初始化一个sql.DB对象. 当真正进行第一次数据库查询操作时, 此时才会真正建立网络连接;
- sql.DB 表示操作数据库的抽象接口的对象,但不是所谓的数据库连接对象, sql.DB 对象只有当需要使用时才会创建连接,如果想立即验证连接,需要用 Ping() 方法;
- sql.Open 返回的 sql.DB 对象是协程并发安全的.
- sql.DB 的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open, Close。
package main import ( "database/sql" "fmt" ) // DB 声明全局的DB对象 var DB *sql.DB // initMySQL 初始化全局的DB对象 func initMySQL() error { dsn := "root:root1234@tcp(127.0.0.1:3306)/s2?charset=utf8mb4&parseTime=True" var err error DB, err = sql.Open("mysql", dsn) // 不会真正连接MySQL,也不会校验用户名密码 if err != nil { fmt.Printf("open MySQL failed, err:%v\n", err) return err } // 通过Ping()去判断连通性 if err := DB.Ping(); err != nil { fmt.Printf("connect to MySQL failed, err:%v\n", err) return err } fmt.Println("connect to MySQL success") return nil }
4.DML操作:增删改
4.1增-有两种方法:
1.直接使用Exec函数添加
函数:
func (db *DB) Exec(query string, args ...interface{}) (Result, error)示例代码
package main import ( "fmt" _ "github.com/go-sql-driver/mysql" // 执行init() ) func main() { err := initMySQL() if err != nil { fmt.Printf("initMySQL failed, err:%\n", err) return } defer DB.Close() // 连接成功,使用sql操作... // 增加记录1 sqlStr := "INSERT INTO userinfo(username,departname,created) values(?,?,?)" result, err := DB.Exec(sqlStr, "王一博", "行政部", "2019-11-21") if err != nil { fmt.Printf("insert into userinfo failed, err:%v\n", err) return } fmt.Println(result.LastInsertId()) }2.首先使用Prepare获得stmt,然后调用Exec添加。
建立连接后,通过操作DB对象的 Prepare() 方法,可以进行
函数:
func (db *DB) Prepare(query string) (*Stmt, error) { return db.PrepareContext(context.Background(), query) }示例代码
package main import ( "fmt" _ "github.com/go-sql-driver/mysql" // 执行init() ) func main() { err := initMySQL() if err != nil { fmt.Printf("initMySQL failed, err:%\n", err) return } defer DB.Close() // 连接成功,使用sql操作... // 增加记录2 stmt, err := DB.Prepare("INSERT INTO userinfo(username,departname,created) values(?,?,?)") if err != nil { fmt.Printf("prepare failed, err:%v\n", err) return } // 补充完整sql语句,并执行 result, err := stmt.Exec("程潇", "市场部", "2020-05-01") lid, err := result.LastInsertId() if err != nil { fmt.Printf("get lastInsertID failed, err:%v\n", err) return } fmt.Println(lid) }预编译语句(Prepared Statement) 提供了诸多好处, 因此我们在开发中尽量使用它. 下面列出了使用预编译语句所提供的功能:
- PreparedStatement 可以实现自定义参数的查询
- PreparedStatement 通常比手动拼接字符串 SQL 语句高效
- PreparedStatement 可以防止SQL注入攻击
3.处理结果:影响数据库的行数
获取影响数据库的行数,可以根据该数值判断是否插入或删除或修改成功。
count, err := result.RowsAffected()获得 刚刚添加数据的自增ID
id, err := result.LastInsertId()打开数据库,可以看到加入的数据。
4.2 改-更新操作
修改表内一行数据
package main import ( "fmt" _ "github.com/go-sql-driver/mysql" // 执行init() ) func main() { err := initMySQL() if err != nil { fmt.Printf("initMySQL failed, err:%\n", err) return } defer DB.Close() // 连接成功,使用sql操作... // 修改一条数据,直接使用Exec()方法 result, err := DB.Exec("UPDATE userinfo SET username = ?, departname = ? WHERE uid = ?", "wuqun", "cg", 2) if err != nil { fmt.Println("更新数据失败。。 ", err) } //处理sql操作后的结果 lastInsertId, err := result.LastInsertId() rowsAffected, err := result.RowsAffected() fmt.Println("lastInsertId", lastInsertId) fmt.Println("影响的行数: ", rowsAffected) }关键语句
result, err := DB.Exec("UPDATE userinfo SET username = ?, departname = ? WHERE uid = ?", "王一博", "行政部", 2) if err != nil { fmt.Println("更新数据失败。。 ", err) } // 处理理sql操作后的结果 lastInsertId, err := result.LastInsertId() rowsAffected, err := result.RowsAffected() fmt.Println("lastInsertId", lastInsertId) fmt.Println("影响的行数: ", rowsAffected)4.3 删-DELETE操作
DELETEdelete
5.DQL操作:查询
1. 查询一条
查询单条数据, QueryRow 函数
func (db *DB) QueryRow(query string, args ...interface{}) *Row示例代码
var username, departname, created string row := db.QueryRow("SELECT username,departname,created FROM userinfo WHERE uid=?", 3) err := row.Scan(&username, &departname, &created) //也可以简写: err := db.QueryRow("SELECT username,departname,created FROM userinfo WHERE uid=?", 3).Scan(&username, &departname, &created)扫描并复制当前行中每一列的值,但是要求行必须与行中的列数相同
func (rs *Rows) Scan(dest ...interface{}) error
- rows.Scan 参数的顺序很重要, 需要和查询的结果的column对应. 例如 “SELECT * From user where age >=20 AND age < 18” 查询的行的 column 顺序是 “id, name, age” 和插入操作顺序相同, 因此 rows.Scan 也需要按照此顺序 rows.Scan(&id, &name, &age), 不然会造成数据读取的错位.
- 因为golang是强类型语言,所以查询数据时先定义数据类型,但是查询数据库中的数据存在三种可能:存在值,存在零值,未赋值NULL 三种状态, 因为可以将待查询的数据类型定义为 sql.Nullxxx类型,可以通过判断Valid值来判断查询到的值是否为赋值状态还是未赋值NULL状态.
2. 查询多条数据,并遍历
Query 获取数据, for xxx.Next() 遍历数据:
首先使用Query()方法进行查询,如果查询无误,返回Rows,就是所有行的信息,类似结果集。func (db *DB) Query(query string, args ...interface{}) (*Rows, error)我们可以通过Next()方法判断是否存在下一条数据,如果有,可以使用之前的Scan()⽅方法读取一行,然后继续判断,继续获取。这个过程的本质就是迭代,所以通常要配合循环使用
func (rs *Rows) Next() bool每次 db.Query 操作后,都建议调用 rows.Close() 。
因为 db.Query() 会从数据库连接池中获取一个连接, 这个底层连接在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close(),但如果提前退出循环, rows不会关闭,连接不会回到连接池中,连接也不会关闭,则此连接会一直被占用。
因此通常我们使⽤用 defer rows.Close() 来确保数据库连接可以正确放回到连接池中。
示例
package main import ( "fmt" _ "github.com/go-sql-driver/mysql" // 执行init() ) func main() { err := initMySQL() if err != nil { fmt.Printf("initMySQL failed, err:%\n", err) return } defer DB.Close() // 连接成功,使用sql操作... // 查询单条 var username, departname, created string err = DB.QueryRow("SELECT username,departname,created FROM userinfo WHERE uid=?", 2). Scan(&username, &departname, &created) // 传指针 if err != nil { fmt.Printf("QueryRow failed, err:%v\n", err) return } fmt.Println(username, departname, created) // 查询多条 rows, err := DB.Query("SELECT uid, username, departname, created FROM userinfo") if err != nil { fmt.Println("查询有误。。") return } // fmt.Println(rows.Columns()) //[uid username departname created] defer rows.Close() type User struct { UID int UserName string DepartName string Created string } // 创建slice,存入struct, var data []*User // step4:操作结果集获取数据 for rows.Next() { var ( uid int username string departname string created string ) if err := rows.Scan(&uid, &username, &departname, &created); err != nil { fmt.Println("获取失败。。") } //每读取一行,创建一个user对象,存入datas2中 user := &User{uid, username, departname, created} data = append(data, user) } for _, v := range data { fmt.Printf("%#v\n", v) } }两个关键点
- 优先使用预编译执行SQL语句
- 查询的时候
- QueryRow().Scan()
- Query()返回的Rows,遍历完遇到EOF才会释放连接
6.事务
事务操作是通过三个方法实现:
- Begin():开启事务
- Commit():提交事务(执行sql)
- Rollback():回滚
在创建一个表,并插入两条数据
CREATE TABLE `account` ( `id` int(4) NOT NULL, `name` varchar(30) DEFAULT NULL, `money` float(8,2) DEFAULT NULL, PRIMARY KEY (`id`) ) INSERT INTO `account` VALUES ('1', 'ycy', '3000.00'); INSERT INTO `account` VALUES ('2', 'wyb', '1000.00');现在我们想让杨超越转账给王一博2000元。
如果转账成功,修改杨超越和王一博的金额。否则金额保持不变。新建go文件(demo08_mysql_transaction.go),示例代码:
package main import "fmt" func main() { err := initMySQL() if err != nil { fmt.Printf("initMySQL failed, err:%\n", err) return } defer DB.Close() /* 事务4大特性: ACID 原子性: 一致性: 隔离性: 永久性: */ // 杨超越 --> 王一博,2000元 tx, _ := DB.Begin() // 提供一组sql操作 var aff1, aff2 int64 = 0, 0 result1, _ := tx.Exec("UPDATE account SET money=3000 WHERE id=?", 1) result2, _ := tx.Exec("UPDATE account SET money=2000 WHERE id=?", 2) if result1 != nil { aff1, _ = result1.RowsAffected() } if result2 != nil { aff2, _ = result2.RowsAffected() } fmt.Println(aff1) fmt.Println(aff2) if aff1 == 1 && aff2 == 1 { //提交事务 tx.Commit() fmt.Println("操作成功。。 ") } else { //回滚 tx.Rollback() fmt.Println("操作失败... 回滚... ") } }由于事务是一个一直连接的状态,所以Tx对象必须绑定和控制单个连接。一个Tx会在整个生命周期中保存一个连接,然后在调用 commit() 或 Rollback() 的时候释放掉。在调用这几个函数的时候必须十分小心,否则连接会一直被占用直到被垃圾回收。
7.sqlx扩展包
sqlx扩展包比原生SQL好用
sqlx包是作为 database/sql 包的一个额外扩展包,在原有的 database/sql 加了很多扩展,如直接将查询的数据转为结构体,大大简化了代码书写,当然database/sql包中的方法同样起作用。
安装:
go get "github.com/jmoiron/sqlx"连接数据库:
var DB *sqlx.DB DB, err = sqlx.Open("mysql","username:password@tcp(ip:port)/database? charset=utf8")处理类型(Handle Types)
- sqlx设计和database/sql使用方法是一样的。包含有4种主要的handle types:
- sqlx.DB - 和sql.DB相似,表示数据库。
- sqlx.Tx - 和sql.Tx相似,表示事物。
- sqlx.Stmt - 和sql.Stmt相似,表示prepared statement。
- sqlx.NamedStmt - 表示prepared statement(支持named parameters)
所有的handler types都提供了对database/sql的兼容,意味着当你调用sqlx.DB.Query时,可以直接替换为sql.DB.Query.这就使得sqlx可以很容易的加入到已有的数据库项目中。
此外, sqlx还有两个cursor类型:
- sqlx.Rows - 和sql.Rows类似, Queryx返回。
- sqlx.Row - 和sql.Row类似, QueryRowx返回
相比database/sql方法还多了新语法,也就是实现将获取的数据直接转换结构体实现。
- Get(dest interface{}, …) error
- Select(dest interface{}, …) error
Get 和 Select(非常常用)
Get和Select是一个非常省时的扩展,可直接将结果赋值给结构体,其内部封装了StructScan进行转化。
Get用于获取单个结果然后Scan, Select用来获取结果切片。新建go文件(demo09_kuozhan.go),示例代码:
package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var DB *sqlx.DB // initMySQL 初始化全局的DB对象 func initMySQL() error { dsn := "root:root1234@tcp(127.0.0.1:3306)/s2?charset=utf8mb4&parseTime=True" var err error DB, err = sqlx.Connect("mysql", dsn) if err != nil { fmt.Printf("connect to MySQL failed, err:%v\n", err) return err } fmt.Println("connect to MySQL success") return nil } func main() { if err := initMySQL(); err != nil { fmt.Println(err.Error()) return } type User struct { UID int `db:"uid"` Username string `db:"username"` Departname string `db:"departname"` Created string `db:"created"` } // 使⽤用Get查询单条记录 var user User sqlStr := "SELECT uid,username,departname,created FROM userinfo where uid =?" if err := DB.Get( &user, sqlStr, 1); err != nil { fmt.Println("GET error :", err) return } fmt.Printf("this is GET res:%v\n", user) // 查询多条记录 var users []User if err := DB.Select(&users, "SELECT uid,username,departname,created FROM userinfo"); err != nil { fmt.Println("Select error", err) return } fmt.Printf("this is Select res:%v\n", users) }运行结果:
this is GET res:{1 王一博 行政部 2019-11-21T00:00:00Z} this is Select res:[{1 王一博 行政部 2019-11-21T00:00:00Z} {2 杨超越 技术部 2020- 05-17T00:00:00Z}]
8.sqlx.In
sqlx.In
实现部分:
// QueryByIDs 根据给定ID查询 func QueryByIDs(ids []int) (users []User, err error) { // 动态填充id // sqlx.In 相当于sqlx帮我们拼接SQL语句的占位符 // SELECT username, departname from userinfo where id in (1,3) query, args, err := sqlx.In("SELECT uid,username,departname,created FROM userinfo WHERE uid IN (?)", ids) if err != nil { return } // sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它 query = DB.Rebind(query) err = DB.Select(&users, query, args...) return }动态传入id自动进行占位符拼接查询数据表:
package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var DB *sqlx.DB // initMySQL 初始化全局的DB对象 func initMySQL() error { dsn := "root:root1234@tcp(127.0.0.1:3306)/s2?charset=utf8mb4&parseTime=True" var err error DB, err = sqlx.Connect("mysql", dsn) if err != nil { fmt.Printf("connect to MySQL failed, err:%v\n", err) return err } fmt.Println("connect to MySQL success") return nil } // QueryByIDs 根据给定ID查询 func QueryByIDs(ids []int) (users []User, err error) { // 动态填充id // sqlx.In 相当于sqlx帮我们拼接SQL语句的占位符 // SELECT username, departname from userinfo where id in (1,3) query, args, err := sqlx.In("SELECT uid,username,departname,created FROM userinfo WHERE uid IN (?)", ids) if err != nil { return } // sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它 query = DB.Rebind(query) err = DB.Select(&users, query, args...) return } type User struct { UID int `db:"uid"` Username string `db:"username"` Departname string `db:"departname"` Created string `db:"created"` } func main() { if err := initMySQL(); err != nil { fmt.Println(err.Error()) return } // 根据指定ID列表查询数据 ids := []int{2, 3} res, err := QueryByIDs(ids) if err != nil { fmt.Printf("QueryByIDs failed, err:%v\n", err) return } fmt.Println(res) }
9.go-sql-driver包SQL操作
建立表结构:
CREATE TABLE t_article_cate( `cid` int(10) NOT NULL AUTO_INCREMENT, `cname` varchar(60) NOT NULL, `ename` varchar(100), `cateimg` varchar(255), `addtime` int(10) unsigned NOT NULL DEFAULT'0', `publishtime` int(10) unsigned NOT NULL DEFAULT'0', `scope` int(10) unsigned NOT NULL DEFAULT'1000', `status` tinyint(1) unsigned NOT NULL DEFAULT'0', PRIMARY KEY(`cid`), UNIQUE KEY catename(`cname`) )ENGINE = InnoDB AUTO_INCREMENT=99 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;一般查询数据时先定义数据类型,但是查询数据库中的数据时有三种状态:存在值、存在零值、未赋值NULL,因此可以将待查询的数据类型定义为sql.Nullxxx类型,可以通过Valid值来判断查询到的值是赋值状态还是未赋值NULL状态,例如sql.NullInt64、sql.NullString
package main import ( "database/sql" "fmt" "strings" "time" _ "github.com/go-sql-driver/mysql" ) type DbWorker struct { Dsn string Db *sql.DB } type Cate struct { cid int cname string addtime int scope int } func main() { // 注意修改mydb为自己的数据库名 dbw := DbWorker{Dsn: "root:123456@tcp(localhost:3306)/mydb?charset=utf8mb4"} // 支持下面几种DSN写法,具体看mysql服务端配置,常见为第2种 // user@unix(/path/to/socket)/dbname?charset=utf8 // user:password@tcp(localhost:5555)/dbname?charset=utf8 // user:password@/dbname // user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname dbtemp, err := sql.Open("mysql", dbw.Dsn) dbw.Db = dbtemp if err != nil { panic(err) return } defer dbw.Db.Close() // 插入数据测试 dbw.insertData() // 删除数据测试 dbw.deleteData() // 修改数据测试 dbw.editData() // 查询数据测试 dbw.queryData() // 事务操作测试 dbw.transaction() } // 插入数据,sql预编译 func (dbw *DbWorker) insertData() { stmt, _ := dbw.Db.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`) defer stmt.Close() ret, err := stmt.Exec("栏目1", time.Now().Unix(), 10) // 通过返回的ret可以进一步查询本次插入数据影响的行数 // RowsAffected和最后插入的Id(如果数据库支持查询最后插入Id) if err != nil { fmt.Printf("插入数据出错: %v\n", err) return } if LastInsertId, err := ret.LastInsertId(); nil == err { fmt.Println("最后插入记录的ID:", LastInsertId) } if RowsAffected, err := ret.RowsAffected(); nil == err { fmt.Println("插入有效的行数:", RowsAffected) } } // 删除数据,预编译 func (dbw *DbWorker) deleteData() { stmt, err := dbw.Db.Prepare(`DELETE FROM t_article_cate WHERE cid=?`) ret, err := stmt.Exec(122) // 通过返回的ret可以进一步查询本次删除数据影响的行数RowsAffected if err != nil { fmt.Printf("删除数据出错: %v\n", err) return } if RowsAffected, err := ret.RowsAffected(); nil == err { fmt.Println("删除有效的行数::", RowsAffected) } } // 修改数据,预编译 func (dbw *DbWorker) editData() { stmt, err := dbw.Db.Prepare(`UPDATE t_article_cate SET scope=? WHERE cid=?`) ret, err := stmt.Exec(111, 123) // 通过返回的ret可以进一步查询本次修改数据影响的行数RowsAffected if err != nil { fmt.Printf("修改数据出错: %v\n", err) return } if RowsAffected, err := ret.RowsAffected(); nil == err { fmt.Println("修改有效的行数::", RowsAffected) } } // 查询数据,预编译 func (dbw *DbWorker) queryData() { // 如果方法包含Query,那么这个方法是用于查询并返回rows的。其他用Exec() // 另外一种写法 // rows, err := db.Query("select id, name from users where id = ?", 1) stmt, _ := dbw.Db.Prepare(`SELECT cid, cname, addtime, scope From t_article_cate where status=?`) // err = db.QueryRow("select name from users where id = ?", 1).Scan(&name) // 单行查询,直接处理 defer stmt.Close() rows, err := stmt.Query(0) defer rows.Close() if err != nil { fmt.Printf("查询数据出错: %v\n", err) return } columns, _ := rows.Columns() // 得到字段名 fmt.Println("字段名:", columns) // 下面两种取数据方式都比较原生,实际中一般都选择ORM框架 // 1.数据保存到结构体 cate := Cate{} for rows.Next() { //将行数据保存到结构体 err = rows.Scan(&cate.cid, &cate.cname, &cate.addtime, &cate.scope) fmt.Println(cate) } /* // 2.通过slice取数据 rowMaps := make([]map[string]string, 4) // 数据key:value的slice values := make([]sql.RawBytes, len(columns)) scans := make([]interface{}, len(columns)) for i := range values { scans[i] = &values[i] } i := 0 for rows.Next() { //将行数据保存到字典 err = rows.Scan(scans...) // 保存当前数据行row的值到slice each := make(map[string]string, 4) // 定义map(key:value) for i, col := range values { each[columns[i]] = string(col) // 写k/v 数据 } // rowMaps切片追加数据,索引位置有意思。不这样写就不是希望的样子。 rowMaps = append(rowMaps[:i], each) fmt.Println(each) i++ } for i, col := range rowMaps { fmt.Println(i, col) } */ err = rows.Err() if err != nil { fmt.Printf(err.Error()) } err = rows.Err() if err != nil { fmt.Printf(err.Error()) } } // 事务处理,预编译 func (dbw *DbWorker) transaction() { tx, err := dbw.Db.Begin() if err != nil { fmt.Printf("事务处理数据出错: %v\n", err) return } defer tx.Rollback() stmt, err := tx.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`) if err != nil { fmt.Printf("事务处理数据出错: %v\n", err) return } for i := 100; i < 110; i++ { cname := strings.Join([]string{"栏目-", string(i)}, "-") _, err = stmt.Exec(cname, time.Now().Unix(), i+20) if err != nil { fmt.Printf("事务处理插入数据出错: %v\n", err) return } } err = tx.Commit() if err != nil { fmt.Printf("事务处理提交出错: %v\n", err) return } stmt.Close() fmt.Println("事务处理成功完成\n") }每次db.Query操作后,建议调用rows.Close()。因为db.Query()会从数据库连接池中获取一个连接,这个底层连接在结果集(rows)未关闭前会被标记为处理繁忙状态,当遍历读取到最后一条记录时。会发生一个内部EOF错误,自动调用rows.Close(),但如果提前退出循环,rows不会关闭,连接不会回到连接池中,也不会关闭,则此连接会一直被占用,因此通常使用defer rows.Close()来确保数据库连接可以正确的放回到连接池中。
db.Begin()开始事务,Commit()或Robllback()关闭事务