1.导入mysql数据库驱动mysql
1 2 3 4 |
import ( "database/sql" _ "github.com/go-sql-driver/mysql" ) |
一般来讲, 不该该直接使用驱动所提供的方法, 而是应该使用 sql.DB, 所以在导入 mysql 驱动时, 这里使用了匿名导入的方式(在包路径前添加 _), 当导入了一个数据库驱动后, 此驱动会自行初始化并注册本身到Golang的database/sql上下文中, 所以咱们就能够经过 database/sql 包提供的方法访问数据库了.git
2.链接数据库github
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
type DbWorker struct { //mysql data source name Dsn string } func main() { dbw := DbWorker{ Dsn: "user:password@tcp(127.0.0.1:3306)/test", } db, err := sql.Open("mysql", dbw.Dsn) if err != nil { panic(err) return } defer db.Close() } |
经过调用sql.Open函数返回一个sql.DB指针
; sql.Open函数原型以下:golang
1 |
func Open(driverName, dataSourceName string) (*DB, error) |
driverName
: 使用的驱动名. 这个名字其实就是数据库驱动注册到 database/sql 时所使用的名字.dataSourceName
: 数据库链接信息,这个链接包含了数据库的用户名, 密码, 数据库主机以及须要链接的数据库名等信息.
- 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。
3.数据库基本操做sql
数据库查询的通常步骤以下:数据库
现有user
数据库表以下:安全
1 2 3 4 5 6 |
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT '', `age` int(11) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 |
MySQL 5.5 以前, UTF8 编码只支持1-3个字节,从MYSQL5.5开始,可支持4个字节UTF编码utf8mb4,一个字符最多能有4字节,utf8mb4兼容utf8,因此能支持更多的字符集;关于emoji表情的话mysql的utf8是不支持,须要修改设置为utf8mb4,才能支持。
查询数据网络
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 |
func (dbw *DbWorker) QueryData() { dbw.QueryDataPre() rows, err := dbw.Db.Query(`SELECT * From user where age >= 20 AND age < 30`) defer rows.Close() if err != nil { fmt.Printf("insert data error: %v\n", err) return } for rows.Next() { rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age) if err != nil { fmt.Printf(err.Error()) continue } if !dbw.UserInfo.Name.Valid { dbw.UserInfo.Name.String = "" } if !dbw.UserInfo.Age.Valid { dbw.UserInfo.Age.Int64 = 0 } fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64)) } err = rows.Err() if err != nil { fmt.Printf(err.Error()) } } |
- rows.Scan 参数的顺序很重要, 须要和查询的结果的column对应. 例如 “SELECT * From user where age >=20 AND age < 30” 查询的行的 column 顺序是 “id, name, age” 和插入操做顺序相同, 所以 rows.Scan 也须要按照此顺序 rows.Scan(&id, &name, &age), 否则会形成数据读取的错位.
- 由于golang是强类型语言,因此查询数据时先定义数据类型,可是查询数据库中的数据存在三种可能:存在值,存在零值,未赋值NULL 三种状态, 由于能够将待查询的数据类型定义为sql.Nullxxx类型,能够经过判断Valid值来判断查询到的值是否为赋值状态仍是未赋值NULL状态.
- 每次db.Query操做后, 都建议调用rows.Close(). 由于 db.Query() 会从数据库链接池中获取一个链接, 这个底层链接在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close(),但若是提早退出循环,rows不会关闭,链接不会回到链接池中,链接也不会关闭, 则此链接会一直被占用. 所以一般咱们使用 defer rows.Close() 来确保数据库链接能够正确放回到链接池中; 不过阅读源码发现rows.Close()操做是幂等操做,即一个幂等操做的特色是其任意屡次执行所产生的影响均与一次执行的影响相同, 因此即使对已关闭的rows再执行close()也不要紧.
单行查询并发
1 2 3 4 5 6 |
var name string err = db.QueryRow("select name from user where id = ?", 1).Scan(&name) if err != nil { log.Fatal(err) } fmt.Println(name) |
- err在Scan后才产生,上述链式写法是对的
- 须要注意Scan()中变量和顺序要和前面Query语句中的顺序一致,不然查出的数据会映射不一致.
插入数据tcp
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func (dbw *DbWorker) insertData() { ret, err := dbw.Db.Exec(`INSERT INTO user (name, age) VALUES ("xys", 23)`) if err != nil { fmt.Printf("insert data error: %v\n", err) return } if LastInsertId, err := ret.LastInsertId(); nil == err { fmt.Println("LastInsertId:", LastInsertId) } if RowsAffected, err := ret.RowsAffected(); nil == err { fmt.Println("RowsAffected:", RowsAffected) } } |
经过db.Exec()
插入数据,经过返回的err
可知插入失败的缘由,经过返回的ret
能够进一步查询本次插入数据影响的行数RowsAffected
和最后插入的Id(若是数据库支持查询最后插入Id).
4.预编译语句(Prepared Statement)
预编译语句(PreparedStatement)提供了诸多好处, 所以咱们在开发中尽可能使用它. 下面列出了使用预编译语句所提供的功能:
通常用Prepared Statements
和Exec()
完成INSERT
, UPDATE
, DELETE
操做。
下面是将上述案例用Prepared Statement 修改以后的完整代码
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) type DbWorker struct { Dsn string Db *sql.DB UserInfo userTB } type userTB struct { Id int Name sql.NullString Age sql.NullInt64 } func main() { var err error dbw := DbWorker{ Dsn: "root:123456@tcp(localhost:3306)/sqlx_db?charset=utf8mb4", } dbw.Db, err = sql.Open("mysql", dbw.Dsn) if err != nil { panic(err) return } defer dbw.Db.Close() dbw.insertData() dbw.queryData() } func (dbw *DbWorker) insertData() { stmt, _ := dbw.Db.Prepare(`INSERT INTO user (name, age) VALUES (?, ?)`) defer stmt.Close() ret, err := stmt.Exec("xys", 23) if err != nil { fmt.Printf("insert data error: %v\n", err) return } if LastInsertId, err := ret.LastInsertId(); nil == err { fmt.Println("LastInsertId:", LastInsertId) } if RowsAffected, err := ret.RowsAffected(); nil == err { fmt.Println("RowsAffected:", RowsAffected) } } func (dbw *DbWorker) QueryDataPre() { dbw.UserInfo = userTB{} } func (dbw *DbWorker) queryData() { stmt, _ := dbw.Db.Prepare(`SELECT * From user where age >= ? AND age < ?`) defer stmt.Close() dbw.QueryDataPre() rows, err := stmt.Query(20, 30) defer rows.Close() if err != nil { fmt.Printf("insert data error: %v\n", err) return } for rows.Next() { rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age) if err != nil { fmt.Printf(err.Error()) continue } if !dbw.UserInfo.Name.Valid { dbw.UserInfo.Name.String = "" } if !dbw.UserInfo.Age.Valid { dbw.UserInfo.Age.Int64 = 0 } fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64)) } err = rows.Err() if err != nil { fmt.Printf(err.Error()) } } |
db.Prepare()返回的statement使用完以后须要手动关闭,即defer stmt.Close()